package services

import (
	"context"
	"fmt"

	"github.com/acp/interceptor/internal/approval"
	"github.com/acp/interceptor/internal/audit"
	"github.com/acp/interceptor/internal/auth"
	"github.com/acp/interceptor/internal/models"
	"github.com/acp/interceptor/internal/policy"
	"github.com/acp/interceptor/internal/registry"
	"github.com/acp/interceptor/internal/utils"
)

// InterceptorService orchestrates validation, policy evaluation, and audit emission.
type InterceptorService struct {
	policy           PolicyService
	audit            *AuditService
	approvals        approval.Repository
	agents           registry.Repository
	registryEnforce  bool
}

// NewInterceptorService constructs the core governance service.
func NewInterceptorService(
	p PolicyService,
	a *AuditService,
	approvals approval.Repository,
	agents registry.Repository,
	registryEnforce bool,
) *InterceptorService {
	return &InterceptorService{
		policy: p, audit: a, approvals: approvals,
		agents: agents, registryEnforce: registryEnforce,
	}
}

// HandleToolCall validates the request, evaluates policy, emits audit, and returns the API response model.
func (s *InterceptorService) HandleToolCall(ctx context.Context, cctx CallContext, req models.ToolCallRequest) (models.ToolCallResponse, error) {
	if err := utils.ValidateToolCallRequest(&req); err != nil {
		return models.ToolCallResponse{}, err
	}

	principal := auth.PrincipalFromContext(ctx)
	if principal != nil && principal.AgentID != req.AgentID {
		return models.ToolCallResponse{}, utils.ValidationError{
			Field:   "agent_id",
			Message: fmt.Sprintf("mismatch with jwt claim %q (body %q)", principal.AgentID, req.AgentID),
		}
	}

	regInfo, regErr := s.resolveAgent(req.AgentID)
	if regErr != nil {
		return s.emitDecision(cctx, req, "deny", regErr.Error(), "acp/agent_registry"), nil
	}

	if s.approvals != nil && cctx.TraceID != "" {
		if resp, ok := s.checkApproval(cctx, req, regInfo); ok {
			return resp, nil
		}
	}

	in := policy.ToPolicyInput(req, cctx.TraceID, principal, regInfo)
	dec, err := s.policy.Evaluate(ctx, in)
	if err != nil {
		return models.ToolCallResponse{}, err
	}

	if dec.Decision == "escalate" && s.approvals != nil && cctx.TraceID != "" {
		policyName := dec.Policy
		if policyName == "" {
			policyName = "acp/unknown"
		}
		s.approvals.UpsertPending(cctx.TraceID, req, dec.Reason, policyName)
	}

	policyName := dec.Policy
	if policyName == "" {
		policyName = "acp/unknown"
	}
	ev := audit.Event{
		Type:       audit.ToolCallEvaluated,
		TraceID:    cctx.TraceID,
		AgentID:    req.AgentID,
		Tool:       req.Tool,
		Action:     req.Action,
		Decision:   dec.Decision,
		Reason:     dec.Reason,
		Policy:     policyName,
		Amount:     req.Amount,
		RiskScore:  req.RiskScore,
		HTTPPath:   cctx.HTTPPath,
		HTTPMethod: cctx.HTTPMethod,
	}
	if principal != nil {
		ev.PrincipalSub = principal.Sub
		ev.PrincipalRoles = principal.Roles
	}
	s.audit.LogToolCallEvaluated(ev)

	return models.ToolCallResponse{
		Decision: dec.Decision,
		TraceID:  cctx.TraceID,
		Reason:   dec.Reason,
	}, nil
}

func (s *InterceptorService) resolveAgent(agentID string) (*models.AgentRegistryInfo, error) {
	if s.agents == nil {
		return nil, nil
	}
	a, err := s.agents.Get(agentID)
	if err != nil {
		if s.registryEnforce {
			return nil, fmt.Errorf("unregistered_agent")
		}
		return nil, nil
	}
	if s.registryEnforce && a.Status != registry.StatusActive {
		return nil, fmt.Errorf("agent_not_active")
	}
	return &models.AgentRegistryInfo{
		DisplayName: a.DisplayName,
		Tenant:      a.Tenant,
		Status:      string(a.Status),
		Roles:       a.Roles,
		Metadata:    a.Metadata,
	}, nil
}

// CallContext carries request-scoped metadata not present in the JSON body.
type CallContext struct {
	TraceID    string
	HTTPPath   string
	HTTPMethod string
}

func (s *InterceptorService) checkApproval(cctx CallContext, req models.ToolCallRequest, _ *models.AgentRegistryInfo) (models.ToolCallResponse, bool) {
	rec, ok := s.approvals.Get(cctx.TraceID)
	if !ok {
		return models.ToolCallResponse{}, false
	}
	if !rec.MatchesRequest(req) {
		return s.emitDecision(cctx, req, "deny", "approval_request_mismatch", rec.Policy), true
	}
	switch rec.Status {
	case approval.StatusDenied:
		return s.emitDecision(cctx, req, "deny", "approval_denied", rec.Policy), true
	case approval.StatusApproved:
		return s.emitDecision(cctx, req, "allow", "human_approval_granted", rec.Policy), true
	case approval.StatusPending:
		return s.emitDecision(cctx, req, "escalate", "approval_pending", rec.Policy), true
	default:
		return models.ToolCallResponse{}, false
	}
}

func (s *InterceptorService) emitDecision(cctx CallContext, req models.ToolCallRequest, decision, reason, policy string) models.ToolCallResponse {
	if policy == "" {
		policy = "acp/approval"
	}
	ev := audit.Event{
		Type:       audit.ToolCallEvaluated,
		TraceID:    cctx.TraceID,
		AgentID:    req.AgentID,
		Tool:       req.Tool,
		Action:     req.Action,
		Decision:   decision,
		Reason:     reason,
		Policy:     policy,
		Amount:     req.Amount,
		RiskScore:  req.RiskScore,
		HTTPPath:   cctx.HTTPPath,
		HTTPMethod: cctx.HTTPMethod,
	}
	s.audit.LogToolCallEvaluated(ev)
	return models.ToolCallResponse{
		Decision: decision,
		TraceID:  cctx.TraceID,
		Reason:   reason,
	}
}
