package handlers

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
)

// ComplianceGraphHandlers serves the read-only Compliance Execution Graph
// (Regulation -> Obligation -> Control -> Evidence) over the file-backed bridge artifacts.
// It is intentionally SEPARATE from the DB-backed ObligationsHandlers: this is the curated
// cross-session graph (Registry join keys + accepted control mappings + evidence requirements),
// loaded once at startup. Fail-closed: if the graph could not load, every request answers 503.
type ComplianceGraphHandlers struct {
	joins    *ucca.ObligationJoinKeys
	mappings *ucca.ControlMappingSet
	evidence *ucca.EvidenceRequirementSet
	loadErr  error
}

// NewComplianceGraphHandlers loads the graph once. Construction never fails; a load error is
// retained and surfaced as 503 per request (matches the codebase's load-warn-continue startup).
func NewComplianceGraphHandlers() *ComplianceGraphHandlers {
	joins, mappings, evidence, err := ucca.LoadComplianceGraph()
	return &ComplianceGraphHandlers{joins: joins, mappings: mappings, evidence: evidence, loadErr: err}
}

// LoadError exposes a startup load failure so the wiring can log a warning.
func (h *ComplianceGraphHandlers) LoadError() error { return h.loadErr }

// RegisterRoutes mounts the compliance-graph routes under /compliance.
func (h *ComplianceGraphHandlers) RegisterRoutes(r *gin.RouterGroup) {
	g := r.Group("/compliance")
	g.GET("/obligation-status", h.ObligationStatus)
}

type cgControlDTO struct {
	Framework        string   `json:"framework"`
	Control          string   `json:"control"`
	MappingType      string   `json:"mapping_type"`
	EvidenceRequired []string `json:"evidence_required"`
	EvidenceStatus   string   `json:"evidence_status"` // missing | partial | present | none_required
}

type cgStatusResponse struct {
	ObligationID  string         `json:"obligation_id"`
	OverallStatus string         `json:"overall_status"` // unknown_obligation | unmapped | not_assessed | open | met
	LegalBasis    []string       `json:"legal_basis,omitempty"`
	CitationSpans string         `json:"citation_spans"` // "pending" until the Legal-KG attaches spans
	Controls      []cgControlDTO `json:"controls"`
	Note          string         `json:"note,omitempty"`
}

// ObligationStatus answers GET /sdk/v1/compliance/obligation-status?obligation_id=...
//
// It NEVER asserts fulfillment automatically. With no evidence collection wired (MVP), a mapped
// obligation is "not_assessed" and every required evidence is "missing" — the honest picture is
// "required vs present evidence", not "a document exists". Fail-closed otherwise:
//   - no obligation_id          -> 400
//   - graph not loaded          -> 503
//   - id not in the Registry    -> 200 overall_status=unknown_obligation
//   - mapped but no control yet  -> 200 overall_status=unmapped
func (h *ComplianceGraphHandlers) ObligationStatus(c *gin.Context) {
	if h.loadErr != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "compliance graph unavailable", "detail": h.loadErr.Error()})
		return
	}
	obID := strings.TrimSpace(c.Query("obligation_id"))
	if obID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "obligation_id query parameter required"})
		return
	}
	resp := cgStatusResponse{ObligationID: obID, CitationSpans: "pending", Controls: []cgControlDTO{}}

	if h.joins.FindObligation(obID) == nil {
		resp.OverallStatus = "unknown_obligation"
		resp.Note = "obligation_id not in the Registry join-key contract"
		c.JSON(http.StatusOK, resp)
		return
	}

	// MVP: hasEvidence=nil -> no collection wired -> all required evidence counts as missing.
	st := ucca.AssessObligationStatus(h.joins, h.mappings, h.evidence, obID, nil)
	resp.LegalBasis = st.LegalBasis

	if len(st.Controls) == 0 {
		resp.OverallStatus = "unmapped"
		resp.Note = "no accepted control maps to this obligation yet"
		c.JSON(http.StatusOK, resp)
		return
	}

	for _, cs := range st.Controls {
		types := make([]string, 0, len(cs.RequiredEvidence))
		for _, e := range cs.RequiredEvidence {
			types = append(types, e.EvidenceType)
		}
		resp.Controls = append(resp.Controls, cgControlDTO{
			Framework:        cs.Framework,
			Control:          cs.Control,
			MappingType:      cs.MappingType,
			EvidenceRequired: types,
			EvidenceStatus:   cgEvidenceStatus(len(cs.RequiredEvidence), len(cs.MissingEvidence)),
		})
	}
	// No fulfillment claim without real evidence collection.
	resp.OverallStatus = "not_assessed"
	resp.Note = "evidence collection not wired (MVP) — fulfillment not asserted"
	c.JSON(http.StatusOK, resp)
}

func cgEvidenceStatus(required, missing int) string {
	switch {
	case required == 0:
		return "none_required"
	case missing == 0:
		return "present"
	case missing == required:
		return "missing"
	default:
		return "partial"
	}
}
