package acp

import (
	"context"
	"encoding/json"

	"github.com/google/uuid"

	"github.com/yan1h/agent-coding-workflow/internal/audit"
	"github.com/yan1h/agent-coding-workflow/internal/infra/crypto"
	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
)

type agentKindService struct {
	repo       Repository
	enc        *crypto.Encryptor
	audit      audit.Recorder
	modelCheck ModelValidator // 校验 model_id 引用一个启用的 llm_model；可为 nil（跳过校验）
}

// ModelValidator 校验某 model_id 引用一个存在且启用的 llm_model。装配期由 app.go
// 注入一个委托给 chat 的 adapter，避免 acp 构造期直接依赖 chat。
type ModelValidator interface {
	IsEnabledModel(ctx context.Context, modelID uuid.UUID) (bool, error)
}

// NewAgentKindService 构造 AgentKindService。
func NewAgentKindService(repo Repository, enc *crypto.Encryptor, rec audit.Recorder) AgentKindService {
	return &agentKindService{repo: repo, enc: enc, audit: rec}
}

// SetModelValidator 注入 model 校验器（装配期回填）。
func (s *agentKindService) SetModelValidator(v ModelValidator) { s.modelCheck = v }

func (s *agentKindService) Create(ctx context.Context, c Caller, in CreateAgentKindInput) (*AgentKind, error) {
	if !c.IsAdmin {
		return nil, errs.New(errs.CodeForbidden, "admin only")
	}
	if in.Name == "" || in.DisplayName == "" || in.BinaryPath == "" {
		return nil, errs.New(errs.CodeInvalidInput, "name / display_name / binary_path required")
	}
	clientType := in.ClientType
	if clientType == "" {
		clientType = ClientGeneric
	}
	if !clientType.Valid() {
		return nil, errs.New(errs.CodeInvalidInput, "invalid client_type")
	}
	encrypted, err := encryptEnv(s.enc, in.Env)
	if err != nil {
		return nil, err
	}
	encryptedMCP, err := encryptMCPServers(s.enc, in.MCPServers)
	if err != nil {
		return nil, err
	}
	args := in.Args
	if args == nil {
		args = []string{}
	}
	allowlist := in.ToolAllowlist
	if allowlist == nil {
		allowlist = []string{}
	}
	if err := s.validateBudget(in.MaxCostUSD, in.MaxTokens, in.MaxWallClockSeconds); err != nil {
		return nil, err
	}
	if err := s.validateModel(ctx, in.ModelID); err != nil {
		return nil, err
	}
	k := &AgentKind{
		ID: uuid.New(), Name: in.Name, DisplayName: in.DisplayName, Description: in.Description,
		BinaryPath: in.BinaryPath, Args: args, EncryptedEnv: encrypted, Enabled: in.Enabled,
		ToolAllowlist:       allowlist,
		ClientType:          clientType,
		EncryptedMCPServers: encryptedMCP,
		CreatedBy:           c.UserID,
		ModelID:             in.ModelID,
		MaxCostUSD:          in.MaxCostUSD,
		MaxTokens:           in.MaxTokens,
		MaxWallClockSeconds: in.MaxWallClockSeconds,
	}
	out, err := s.repo.CreateAgentKind(ctx, k)
	if err != nil {
		if IsUniqueViolation(err) {
			return nil, errs.New(errs.CodeConflict, "agent kind name already exists")
		}
		return nil, err
	}
	s.recordAudit(ctx, c, "acp.agent_kind.create", out.ID.String(),
		map[string]any{"name": out.Name, "binary_path": out.BinaryPath})
	return out, nil
}

func (s *agentKindService) List(ctx context.Context, c Caller) ([]*AgentKind, error) {
	if c.IsAdmin {
		return s.repo.ListAgentKinds(ctx)
	}
	return s.repo.ListEnabledAgentKinds(ctx)
}

func (s *agentKindService) Get(ctx context.Context, c Caller, id uuid.UUID) (*AgentKind, error) {
	k, err := s.repo.GetAgentKindByID(ctx, id)
	if err != nil {
		return nil, err
	}
	if !c.IsAdmin && !k.Enabled {
		return nil, errs.New(errs.CodeAcpAgentKindNotFound, "agent kind not found")
	}
	return k, nil
}

func (s *agentKindService) Update(ctx context.Context, c Caller, id uuid.UUID, in UpdateAgentKindInput) (*AgentKind, error) {
	if !c.IsAdmin {
		return nil, errs.New(errs.CodeForbidden, "admin only")
	}
	cur, err := s.repo.GetAgentKindByID(ctx, id)
	if err != nil {
		return nil, err
	}
	changed := map[string]any{}
	if in.DisplayName != nil && *in.DisplayName != cur.DisplayName {
		cur.DisplayName = *in.DisplayName
		changed["display_name"] = "<changed>"
	}
	if in.Description != nil && *in.Description != cur.Description {
		cur.Description = *in.Description
		changed["description"] = "<changed>"
	}
	if in.BinaryPath != nil && *in.BinaryPath != cur.BinaryPath {
		cur.BinaryPath = *in.BinaryPath
		changed["binary_path"] = "<changed>"
	}
	if in.Args != nil {
		cur.Args = in.Args
		changed["args"] = "<changed>"
	}
	if in.Env != nil {
		encrypted, eerr := encryptEnv(s.enc, in.Env)
		if eerr != nil {
			return nil, eerr
		}
		cur.EncryptedEnv = encrypted
		changed["env"] = "<changed>"
	}
	if in.ToolAllowlist != nil {
		cur.ToolAllowlist = in.ToolAllowlist
		changed["tool_allowlist"] = "<changed>"
	}
	if in.ClientType != nil && *in.ClientType != cur.ClientType {
		if !in.ClientType.Valid() {
			return nil, errs.New(errs.CodeInvalidInput, "invalid client_type")
		}
		cur.ClientType = *in.ClientType
		changed["client_type"] = string(*in.ClientType)
	}
	if in.MCPServers != nil {
		encryptedMCP, eerr := encryptMCPServers(s.enc, in.MCPServers)
		if eerr != nil {
			return nil, eerr
		}
		cur.EncryptedMCPServers = encryptedMCP
		changed["mcp_servers"] = "<changed>"
	}
	if in.Enabled != nil && *in.Enabled != cur.Enabled {
		cur.Enabled = *in.Enabled
		changed["enabled"] = *in.Enabled
	}
	if in.SetModelID {
		if err := s.validateModel(ctx, in.ModelID); err != nil {
			return nil, err
		}
		cur.ModelID = in.ModelID
		changed["model_id"] = "<changed>"
	}
	if in.SetMaxCostUSD {
		if err := s.validateBudget(in.MaxCostUSD, nil, nil); err != nil {
			return nil, err
		}
		cur.MaxCostUSD = in.MaxCostUSD
		changed["max_cost_usd"] = "<changed>"
	}
	if in.SetMaxTokens {
		if err := s.validateBudget(nil, in.MaxTokens, nil); err != nil {
			return nil, err
		}
		cur.MaxTokens = in.MaxTokens
		changed["max_tokens"] = "<changed>"
	}
	if in.SetMaxWallClock {
		if err := s.validateBudget(nil, nil, in.MaxWallClockSeconds); err != nil {
			return nil, err
		}
		cur.MaxWallClockSeconds = in.MaxWallClockSeconds
		changed["max_wall_clock_seconds"] = "<changed>"
	}
	if len(changed) == 0 {
		return cur, nil
	}
	out, err := s.repo.UpdateAgentKind(ctx, cur)
	if err != nil {
		return nil, err
	}
	s.recordAudit(ctx, c, "acp.agent_kind.update", out.ID.String(),
		map[string]any{"changed_fields": changed})
	return out, nil
}

func (s *agentKindService) Delete(ctx context.Context, c Caller, id uuid.UUID) error {
	if !c.IsAdmin {
		return errs.New(errs.CodeForbidden, "admin only")
	}
	cur, err := s.repo.GetAgentKindByID(ctx, id)
	if err != nil {
		return err
	}
	if err := s.repo.DeleteAgentKind(ctx, id); err != nil {
		return err
	}
	s.recordAudit(ctx, c, "acp.agent_kind.delete", id.String(),
		map[string]any{"name": cur.Name})
	return nil
}

// validateBudget 校验预算上限：非空值必须为正。
func (s *agentKindService) validateBudget(maxCost *float64, maxTokens *int64, maxWall *int32) error {
	if maxCost != nil && *maxCost <= 0 {
		return errs.New(errs.CodeInvalidInput, "max_cost_usd must be positive")
	}
	if maxTokens != nil && *maxTokens <= 0 {
		return errs.New(errs.CodeInvalidInput, "max_tokens must be positive")
	}
	if maxWall != nil && *maxWall <= 0 {
		return errs.New(errs.CodeInvalidInput, "max_wall_clock_seconds must be positive")
	}
	return nil
}

// validateModel 校验 model_id 引用一个启用的 llm_model（modelCheck 为 nil 时跳过）。
func (s *agentKindService) validateModel(ctx context.Context, modelID *uuid.UUID) error {
	if modelID == nil || s.modelCheck == nil {
		return nil
	}
	ok, err := s.modelCheck.IsEnabledModel(ctx, *modelID)
	if err != nil {
		return err
	}
	if !ok {
		return errs.New(errs.CodeInvalidInput, "model_id does not reference an enabled model")
	}
	return nil
}

func (s *agentKindService) recordAudit(ctx context.Context, c Caller, action, targetID string, meta map[string]any) {
	if s.audit == nil {
		return
	}
	uid := c.UserID
	_ = s.audit.Record(context.WithoutCancel(ctx), audit.Entry{
		UserID: &uid, Action: action, TargetType: "acp_agent_kind", TargetID: targetID, Metadata: meta,
	})
}

// encryptEnv 把 map[string]string 序列化后加密。nil → 返回 nil（清空）。
func encryptEnv(enc *crypto.Encryptor, env map[string]string) ([]byte, error) {
	if env == nil {
		return nil, nil
	}
	if len(env) == 0 {
		return enc.Encrypt([]byte(`{}`))
	}
	b, err := json.Marshal(env)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "marshal env")
	}
	return enc.Encrypt(b)
}

// encryptMCPServers 校验并加密 MCP server 列表。nil → nil（不存储）。
func encryptMCPServers(enc *crypto.Encryptor, servers []McpServerSpec) ([]byte, error) {
	if servers == nil {
		return nil, nil
	}
	if err := validateMCPServers(servers); err != nil {
		return nil, err
	}
	b, err := json.Marshal(servers)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "marshal mcp servers")
	}
	return enc.Encrypt(b)
}

// decryptMCPServers 解密 MCP server 列表。空密文 → 空列表。
func decryptMCPServers(enc *crypto.Encryptor, encrypted []byte) ([]McpServerSpec, error) {
	if len(encrypted) == 0 {
		return []McpServerSpec{}, nil
	}
	plain, err := enc.Decrypt(encrypted)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "decrypt mcp servers")
	}
	var out []McpServerSpec
	if err := json.Unmarshal(plain, &out); err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "unmarshal mcp servers")
	}
	if out == nil {
		out = []McpServerSpec{}
	}
	return out, nil
}

// validateMCPServers 校验条目：name 必填且不重复；stdio 需 command，http/sse 需 url。
func validateMCPServers(servers []McpServerSpec) error {
	seen := map[string]bool{}
	for _, sv := range servers {
		if sv.Name == "" {
			return errs.New(errs.CodeInvalidInput, "mcp server name required")
		}
		if sv.Name == AcwMcpServerName {
			return errs.New(errs.CodeInvalidInput, "mcp server name '"+AcwMcpServerName+"' is reserved")
		}
		if seen[sv.Name] {
			return errs.New(errs.CodeInvalidInput, "duplicate mcp server name: "+sv.Name)
		}
		seen[sv.Name] = true
		switch sv.Type {
		case McpStdio:
			if sv.Command == "" {
				return errs.New(errs.CodeInvalidInput, "mcp server "+sv.Name+": command required for stdio")
			}
		case McpHTTP, McpSSE:
			if sv.URL == "" {
				return errs.New(errs.CodeInvalidInput, "mcp server "+sv.Name+": url required for "+string(sv.Type))
			}
		default:
			return errs.New(errs.CodeInvalidInput, "mcp server "+sv.Name+": invalid type")
		}
	}
	return nil
}

// decryptEnv 解密并反序列化 env map。Part 2 的 supervisor.Spawn 用。
func decryptEnv(enc *crypto.Encryptor, encrypted []byte) (map[string]string, error) {
	if len(encrypted) == 0 {
		return map[string]string{}, nil
	}
	plain, err := enc.Decrypt(encrypted)
	if err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "decrypt env")
	}
	var m map[string]string
	if err := json.Unmarshal(plain, &m); err != nil {
		return nil, errs.Wrap(err, errs.CodeInternal, "unmarshal env")
	}
	if m == nil {
		m = map[string]string{}
	}
	return m, nil
}
