package handlers

import (
	"encoding/json"
	"net/http"

	"github.com/breakpilot/ai-compliance-sdk/internal/iace"
	"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// ============================================================================
// Hazard Library & Controls Library
// ============================================================================

// ListHazardLibrary handles GET /hazard-library
// Returns built-in hazard library entries merged with any custom DB entries,
// optionally filtered by ?category and ?componentType.
func (h *IACEHandler) ListHazardLibrary(c *gin.Context) {
	category := c.Query("category")
	componentType := c.Query("componentType")

	// Start with built-in templates from Go code
	builtinEntries := iace.GetBuiltinHazardLibrary()

	// Apply filters to built-in entries
	var entries []iace.HazardLibraryEntry
	for _, entry := range builtinEntries {
		if category != "" && entry.Category != category {
			continue
		}
		if componentType != "" && !containsString(entry.ApplicableComponentTypes, componentType) {
			continue
		}
		entries = append(entries, entry)
	}

	// Merge with custom DB entries (tenant-specific)
	dbEntries, err := h.store.ListHazardLibrary(c.Request.Context(), category, componentType)
	if err == nil && len(dbEntries) > 0 {
		// Add DB entries that are not built-in (avoid duplicates)
		builtinIDs := make(map[string]bool)
		for _, e := range entries {
			builtinIDs[e.ID.String()] = true
		}
		for _, dbEntry := range dbEntries {
			if !builtinIDs[dbEntry.ID.String()] {
				entries = append(entries, dbEntry)
			}
		}
	}

	if entries == nil {
		entries = []iace.HazardLibraryEntry{}
	}

	c.JSON(http.StatusOK, gin.H{
		"hazard_library": entries,
		"total":          len(entries),
	})
}

// ListControlsLibrary handles GET /controls-library
// Returns the built-in controls library, optionally filtered by ?domain and ?category.
func (h *IACEHandler) ListControlsLibrary(c *gin.Context) {
	domain := c.Query("domain")
	category := c.Query("category")

	all := iace.GetControlsLibrary()

	var filtered []iace.ControlLibraryEntry
	for _, entry := range all {
		if domain != "" && entry.Domain != domain {
			continue
		}
		if category != "" && !containsString(entry.MapsToHazardCategories, category) {
			continue
		}
		filtered = append(filtered, entry)
	}

	if filtered == nil {
		filtered = []iace.ControlLibraryEntry{}
	}

	c.JSON(http.StatusOK, gin.H{
		"controls": filtered,
		"total":    len(filtered),
	})
}

// ============================================================================
// Hazard CRUD
// ============================================================================

// CreateHazard handles POST /projects/:id/hazards
// Creates a new hazard within a project.
func (h *IACEHandler) CreateHazard(c *gin.Context) {
	projectID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	var req iace.CreateHazardRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Override project ID from URL path
	req.ProjectID = projectID

	hazard, err := h.store.CreateHazard(c.Request.Context(), req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Audit trail
	userID := rbac.GetUserID(c)
	newVals, _ := json.Marshal(hazard)
	h.store.AddAuditEntry(
		c.Request.Context(), projectID, "hazard", hazard.ID,
		iace.AuditActionCreate, userID.String(), nil, newVals,
	)

	c.JSON(http.StatusCreated, gin.H{"hazard": hazard})
}

// ListHazards handles GET /projects/:id/hazards
// Lists all hazards for a project.
func (h *IACEHandler) ListHazards(c *gin.Context) {
	projectID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	hazards, err := h.store.ListHazards(c.Request.Context(), projectID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if hazards == nil {
		hazards = []iace.Hazard{}
	}

	// Enrich hazards with latest risk assessment (single batch query instead of N+1)
	type enrichedHazard struct {
		iace.Hazard
		RiskAssessment interface{} `json:"risk_assessment"`
	}

	assessmentMap, _ := h.store.GetLatestAssessmentsByProject(c.Request.Context(), projectID)

	enriched := make([]enrichedHazard, len(hazards))
	for i, hz := range hazards {
		enriched[i] = enrichedHazard{Hazard: hz}
		if ra, ok := assessmentMap[hz.ID]; ok {
			enriched[i].RiskAssessment = ra
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"hazards": enriched,
		"total":   len(enriched),
	})
}

// UpdateHazard handles PUT /projects/:id/hazards/:hid
// Updates a hazard with the provided fields.
func (h *IACEHandler) UpdateHazard(c *gin.Context) {
	_, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	hazardID, err := uuid.Parse(c.Param("hid"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid hazard ID"})
		return
	}

	var updates map[string]interface{}
	if err := c.ShouldBindJSON(&updates); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	hazard, err := h.store.UpdateHazard(c.Request.Context(), hazardID, updates)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if hazard == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "hazard not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"hazard": hazard})
}

// SuggestHazards handles POST /projects/:id/hazards/suggest
// Returns hazard library matches based on the project's components.
// TODO: Enhance with LLM-based suggestions for more intelligent matching.
func (h *IACEHandler) SuggestHazards(c *gin.Context) {
	projectID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	components, err := h.store.ListComponents(c.Request.Context(), projectID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Collect unique component types from the project
	componentTypes := make(map[string]bool)
	for _, comp := range components {
		componentTypes[string(comp.ComponentType)] = true
	}

	// Match built-in hazard templates against project component types
	var suggestions []iace.HazardLibraryEntry
	seen := make(map[uuid.UUID]bool)

	builtinEntries := iace.GetBuiltinHazardLibrary()
	for _, entry := range builtinEntries {
		for _, applicableType := range entry.ApplicableComponentTypes {
			if componentTypes[applicableType] && !seen[entry.ID] {
				seen[entry.ID] = true
				suggestions = append(suggestions, entry)
				break
			}
		}
	}

	// Also check DB for custom tenant-specific hazard templates
	for compType := range componentTypes {
		dbEntries, err := h.store.ListHazardLibrary(c.Request.Context(), "", compType)
		if err != nil {
			continue
		}
		for _, entry := range dbEntries {
			if !seen[entry.ID] {
				seen[entry.ID] = true
				suggestions = append(suggestions, entry)
			}
		}
	}

	if suggestions == nil {
		suggestions = []iace.HazardLibraryEntry{}
	}

	c.JSON(http.StatusOK, gin.H{
		"suggestions":     suggestions,
		"total":           len(suggestions),
		"component_types": componentTypeKeys(componentTypes),
		"_note":           "TODO: LLM-based suggestion ranking not yet implemented",
	})
}

// ============================================================================
// Risk Assessment
// ============================================================================

// AssessRisk handles POST /projects/:id/hazards/:hid/assess
// Performs a quantitative risk assessment for a hazard using the IACE risk engine.
func (h *IACEHandler) AssessRisk(c *gin.Context) {
	projectID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	hazardID, err := uuid.Parse(c.Param("hid"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid hazard ID"})
		return
	}

	// Verify hazard exists
	hazard, err := h.store.GetHazard(c.Request.Context(), hazardID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if hazard == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "hazard not found"})
		return
	}

	var req iace.AssessRiskRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Override hazard ID from URL path
	req.HazardID = hazardID

	userID := rbac.GetUserID(c)

	// Calculate risk using the engine
	inherentRisk := h.engine.CalculateInherentRisk(req.Severity, req.Exposure, req.Probability, req.Avoidance)
	controlEff := h.engine.CalculateControlEffectiveness(req.ControlMaturity, req.ControlCoverage, req.TestEvidenceStrength)
	residualRisk := h.engine.CalculateResidualRisk(req.Severity, req.Exposure, req.Probability, controlEff)

	// ISO 12100 mode: use ISO thresholds when avoidance is set
	var riskLevel iace.RiskLevel
	if req.Avoidance >= 1 {
		riskLevel = h.engine.DetermineRiskLevelISO(inherentRisk)
	} else {
		riskLevel = h.engine.DetermineRiskLevel(residualRisk)
	}
	acceptable, acceptanceReason := h.engine.IsAcceptable(residualRisk, false, req.AcceptanceJustification != "")

	// Determine version by checking existing assessments
	existingAssessments, _ := h.store.ListAssessments(c.Request.Context(), hazardID)
	version := len(existingAssessments) + 1

	assessment := &iace.RiskAssessment{
		HazardID:                hazardID,
		Version:                 version,
		AssessmentType:          iace.AssessmentTypeInitial,
		Severity:                req.Severity,
		Exposure:                req.Exposure,
		Probability:             req.Probability,
		Avoidance:               req.Avoidance,
		InherentRisk:            inherentRisk,
		ControlMaturity:         req.ControlMaturity,
		ControlCoverage:         req.ControlCoverage,
		TestEvidenceStrength:    req.TestEvidenceStrength,
		CEff:                    controlEff,
		ResidualRisk:            residualRisk,
		RiskLevel:               riskLevel,
		IsAcceptable:            acceptable,
		AcceptanceJustification: req.AcceptanceJustification,
		AssessedBy:              userID,
	}

	if err := h.store.CreateRiskAssessment(c.Request.Context(), assessment); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Update hazard status
	h.store.UpdateHazard(c.Request.Context(), hazardID, map[string]interface{}{
		"status": string(iace.HazardStatusAssessed),
	})

	// Audit trail
	newVals, _ := json.Marshal(assessment)
	h.store.AddAuditEntry(
		c.Request.Context(), projectID, "risk_assessment", assessment.ID,
		iace.AuditActionCreate, userID.String(), nil, newVals,
	)

	c.JSON(http.StatusCreated, gin.H{
		"assessment":        assessment,
		"acceptable":        acceptable,
		"acceptance_reason": acceptanceReason,
	})
}

// GetRiskSummary handles GET /projects/:id/risk-summary
// Returns an aggregated risk overview for a project.
func (h *IACEHandler) GetRiskSummary(c *gin.Context) {
	projectID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	summary, err := h.store.GetRiskSummary(c.Request.Context(), projectID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"risk_summary": summary})
}

// ReassessRisk handles POST /projects/:id/hazards/:hid/reassess
// Creates a post-mitigation risk reassessment for a hazard.
func (h *IACEHandler) ReassessRisk(c *gin.Context) {
	projectID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid project ID"})
		return
	}

	hazardID, err := uuid.Parse(c.Param("hid"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid hazard ID"})
		return
	}

	// Verify hazard exists
	hazard, err := h.store.GetHazard(c.Request.Context(), hazardID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if hazard == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "hazard not found"})
		return
	}

	var req iace.AssessRiskRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	userID := rbac.GetUserID(c)

	// Calculate risk using the engine
	inherentRisk := h.engine.CalculateInherentRisk(req.Severity, req.Exposure, req.Probability, req.Avoidance)
	controlEff := h.engine.CalculateControlEffectiveness(req.ControlMaturity, req.ControlCoverage, req.TestEvidenceStrength)
	residualRisk := h.engine.CalculateResidualRisk(req.Severity, req.Exposure, req.Probability, controlEff)
	riskLevel := h.engine.DetermineRiskLevel(residualRisk)

	// For reassessment, check if all reduction steps have been applied
	mitigations, _ := h.store.ListMitigations(c.Request.Context(), hazardID)
	allReductionStepsApplied := len(mitigations) > 0
	for _, m := range mitigations {
		if m.Status != iace.MitigationStatusVerified {
			allReductionStepsApplied = false
			break
		}
	}

	acceptable, acceptanceReason := h.engine.IsAcceptable(residualRisk, allReductionStepsApplied, req.AcceptanceJustification != "")

	// Determine version
	existingAssessments, _ := h.store.ListAssessments(c.Request.Context(), hazardID)
	version := len(existingAssessments) + 1

	assessment := &iace.RiskAssessment{
		HazardID:                hazardID,
		Version:                 version,
		AssessmentType:          iace.AssessmentTypePostMitigation,
		Severity:                req.Severity,
		Exposure:                req.Exposure,
		Probability:             req.Probability,
		Avoidance:               req.Avoidance,
		InherentRisk:            inherentRisk,
		ControlMaturity:         req.ControlMaturity,
		ControlCoverage:         req.ControlCoverage,
		TestEvidenceStrength:    req.TestEvidenceStrength,
		CEff:                    controlEff,
		ResidualRisk:            residualRisk,
		RiskLevel:               riskLevel,
		IsAcceptable:            acceptable,
		AcceptanceJustification: req.AcceptanceJustification,
		AssessedBy:              userID,
	}

	if err := h.store.CreateRiskAssessment(c.Request.Context(), assessment); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Audit trail
	newVals, _ := json.Marshal(assessment)
	h.store.AddAuditEntry(
		c.Request.Context(), projectID, "risk_assessment", assessment.ID,
		iace.AuditActionCreate, userID.String(), nil, newVals,
	)

	c.JSON(http.StatusCreated, gin.H{
		"assessment":                  assessment,
		"acceptable":                  acceptable,
		"acceptance_reason":           acceptanceReason,
		"all_reduction_steps_applied": allReductionStepsApplied,
	})
}
