package registry

import (
	"errors"
	"strings"
	"sync"
	"time"
)

var (
	ErrNotFound      = errors.New("agent not found")
	ErrAlreadyExists = errors.New("agent already exists")
)

// MemoryStore is an in-process agent registry for dev without Postgres.
type MemoryStore struct {
	mu     sync.RWMutex
	agents map[string]*Agent
}

// NewMemoryStore creates an empty in-memory registry.
func NewMemoryStore() *MemoryStore {
	return &MemoryStore{agents: make(map[string]*Agent)}
}

// SeedDemoAgents registers optional platform sample agents (pip bundle; no mortgage demo).
func (s *MemoryStore) SeedDemoAgents() {
	samples := []UpsertRequest{
		{AgentID: "ops-agent", DisplayName: "Operations Agent", Roles: []string{"ops"}, Tenant: "default", Status: StatusActive},
		{AgentID: "refund-agent", DisplayName: "Refund Agent", Roles: []string{"refund"}, Tenant: "default", Status: StatusActive},
		{AgentID: "compliance-agent", DisplayName: "Compliance Officer", Roles: []string{"compliance"}, Tenant: "default", Status: StatusActive},
	}
	for _, d := range samples {
		if _, err := s.Get(d.AgentID); err == nil {
			continue
		}
		_, _ = s.Upsert(d)
	}
}

func (s *MemoryStore) List(limit int) ([]Agent, error) {
	if limit < 1 {
		limit = 100
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make([]Agent, 0, len(s.agents))
	for _, a := range s.agents {
		out = append(out, *a)
	}
	if len(out) > limit {
		out = out[:limit]
	}
	return out, nil
}

func (s *MemoryStore) Get(agentID string) (*Agent, error) {
	id := strings.TrimSpace(agentID)
	s.mu.RLock()
	defer s.mu.RUnlock()
	a, ok := s.agents[id]
	if !ok {
		return nil, ErrNotFound
	}
	cp := *a
	return &cp, nil
}

func (s *MemoryStore) Upsert(req UpsertRequest) (*Agent, error) {
	id := strings.TrimSpace(req.AgentID)
	if id == "" {
		return nil, errors.New("agent_id required")
	}
	now := time.Now().UTC()
	s.mu.Lock()
	defer s.mu.Unlock()
	a, ok := s.agents[id]
	if !ok {
		a = &Agent{AgentID: id, CreatedAt: now}
		s.agents[id] = a
	}
	applyUpsert(a, req, now)
	cp := *a
	return &cp, nil
}

func (s *MemoryStore) Patch(agentID string, req PatchRequest) (*Agent, error) {
	id := strings.TrimSpace(agentID)
	s.mu.Lock()
	defer s.mu.Unlock()
	a, ok := s.agents[id]
	if !ok {
		return nil, ErrNotFound
	}
	now := time.Now().UTC()
	applyPatch(a, req, now)
	cp := *a
	return &cp, nil
}

func applyUpsert(a *Agent, req UpsertRequest, now time.Time) {
	if req.DisplayName != "" {
		a.DisplayName = req.DisplayName
	}
	if len(req.Roles) > 0 {
		a.Roles = append([]string(nil), req.Roles...)
	}
	if req.Tenant != "" {
		a.Tenant = req.Tenant
	}
	if req.Status != "" {
		a.Status = req.Status
	}
	if req.Metadata != nil {
		a.Metadata = cloneMeta(req.Metadata)
	}
	if a.Tenant == "" {
		a.Tenant = "default"
	}
	if a.Status == "" {
		a.Status = StatusActive
	}
	a.UpdatedAt = now
}

func applyPatch(a *Agent, req PatchRequest, now time.Time) {
	if req.DisplayName != nil {
		a.DisplayName = *req.DisplayName
	}
	if req.Roles != nil {
		a.Roles = append([]string(nil), req.Roles...)
	}
	if req.Tenant != nil {
		a.Tenant = *req.Tenant
	}
	if req.Status != nil {
		a.Status = *req.Status
	}
	if req.Metadata != nil {
		a.Metadata = cloneMeta(req.Metadata)
	}
	a.UpdatedAt = now
}

func cloneMeta(m map[string]any) map[string]any {
	if m == nil {
		return nil
	}
	out := make(map[string]any, len(m))
	for k, v := range m {
		out[k] = v
	}
	return out
}
