package postgres

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"time"

	"github.com/acp/interceptor/internal/registry"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

// AgentStore is a Postgres-backed agent registry.
type AgentStore struct {
	pool *pgxpool.Pool
}

// NewAgentStore creates a registry store.
func NewAgentStore(pool *pgxpool.Pool) *AgentStore {
	return &AgentStore{pool: pool}
}

func (s *AgentStore) List(limit int) ([]registry.Agent, error) {
	if limit < 1 {
		limit = 100
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	rows, err := s.pool.Query(ctx, `
		SELECT agent_id, display_name, roles, tenant, status, metadata, created_at, updated_at
		FROM agents ORDER BY agent_id LIMIT $1`, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanAgents(rows)
}

func (s *AgentStore) Get(agentID string) (*registry.Agent, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	row := s.pool.QueryRow(ctx, `
		SELECT agent_id, display_name, roles, tenant, status, metadata, created_at, updated_at
		FROM agents WHERE agent_id = $1`, strings.TrimSpace(agentID))
	a, err := scanAgent(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, registry.ErrNotFound
	}
	if err != nil {
		return nil, err
	}
	return a, nil
}

func (s *AgentStore) Upsert(req registry.UpsertRequest) (*registry.Agent, error) {
	id := strings.TrimSpace(req.AgentID)
	if id == "" {
		return nil, errors.New("agent_id required")
	}
	roles, _ := json.Marshal(req.Roles)
	meta, _ := json.Marshal(req.Metadata)
	if meta == nil {
		meta = []byte("{}")
	}
	tenant := req.Tenant
	if tenant == "" {
		tenant = "default"
	}
	status := req.Status
	if status == "" {
		status = registry.StatusActive
	}
	now := time.Now().UTC()
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	_, err := s.pool.Exec(ctx, `
		INSERT INTO agents (agent_id, display_name, roles, tenant, status, metadata, created_at, updated_at)
		VALUES ($1,$2,$3,$4,$5,$6,$7,$7)
		ON CONFLICT (agent_id) DO UPDATE SET
			display_name = COALESCE(NULLIF(EXCLUDED.display_name,''), agents.display_name),
			roles = CASE WHEN EXCLUDED.roles::text = '[]' THEN agents.roles ELSE EXCLUDED.roles END,
			tenant = COALESCE(NULLIF(EXCLUDED.tenant,''), agents.tenant),
			status = COALESCE(NULLIF(EXCLUDED.status,''), agents.status),
			metadata = EXCLUDED.metadata,
			updated_at = $7`,
		id, req.DisplayName, roles, tenant, string(status), meta, now,
	)
	if err != nil {
		return nil, err
	}
	return s.Get(id)
}

func (s *AgentStore) Patch(agentID string, req registry.PatchRequest) (*registry.Agent, error) {
	cur, err := s.Get(agentID)
	if err != nil {
		return nil, err
	}
	up := registry.UpsertRequest{
		AgentID:     cur.AgentID,
		DisplayName: cur.DisplayName,
		Roles:       cur.Roles,
		Tenant:      cur.Tenant,
		Status:      cur.Status,
		Metadata:    cur.Metadata,
	}
	if req.DisplayName != nil {
		up.DisplayName = *req.DisplayName
	}
	if req.Roles != nil {
		up.Roles = req.Roles
	}
	if req.Tenant != nil {
		up.Tenant = *req.Tenant
	}
	if req.Status != nil {
		up.Status = *req.Status
	}
	if req.Metadata != nil {
		up.Metadata = req.Metadata
	}
	return s.Upsert(up)
}

func scanAgents(rows pgx.Rows) ([]registry.Agent, error) {
	var out []registry.Agent
	for rows.Next() {
		a, err := scanAgent(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, *a)
	}
	return out, rows.Err()
}

func scanAgent(row rowScanner) (*registry.Agent, error) {
	var a registry.Agent
	var roles, meta []byte
	var status string
	if err := row.Scan(
		&a.AgentID, &a.DisplayName, &roles, &a.Tenant, &status, &meta,
		&a.CreatedAt, &a.UpdatedAt,
	); err != nil {
		return nil, err
	}
	a.Status = registry.Status(status)
	_ = json.Unmarshal(roles, &a.Roles)
	_ = json.Unmarshal(meta, &a.Metadata)
	return &a, nil
}
