package handlers

import (
	"net/http"
	"time"

	"github.com/breakpilot/edu-search-service/internal/policy"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// PolicyHandler contains all policy-related HTTP handlers.
type PolicyHandler struct {
	store    *policy.Store
	enforcer *policy.Enforcer
}

// policyHandler is the singleton instance
var policyHandler *PolicyHandler

// InitPolicyHandler initializes the policy handler with a database pool.
func InitPolicyHandler(store *policy.Store) {
	policyHandler = &PolicyHandler{
		store:    store,
		enforcer: policy.NewEnforcer(store),
	}
}

// GetPolicyHandler returns the policy handler instance.
func GetPolicyHandler() *PolicyHandler {
	return policyHandler
}

// =============================================================================
// POLICIES
// =============================================================================

// ListPolicies returns all source policies.
func (h *PolicyHandler) ListPolicies(c *gin.Context) {
	var filter policy.PolicyListFilter
	if err := c.ShouldBindQuery(&filter); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters", "details": err.Error()})
		return
	}

	// Set defaults
	if filter.Limit <= 0 || filter.Limit > 100 {
		filter.Limit = 50
	}

	policies, total, err := h.store.ListPolicies(c.Request.Context(), &filter)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list policies", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"policies": policies,
		"total":    total,
		"limit":    filter.Limit,
		"offset":   filter.Offset,
	})
}

// GetPolicy returns a single policy by ID.
func (h *PolicyHandler) GetPolicy(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid policy ID"})
		return
	}

	p, err := h.store.GetPolicy(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get policy", "details": err.Error()})
		return
	}
	if p == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Policy not found"})
		return
	}

	c.JSON(http.StatusOK, p)
}

// CreatePolicy creates a new source policy.
func (h *PolicyHandler) CreatePolicy(c *gin.Context) {
	var req policy.CreateSourcePolicyRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	p, err := h.store.CreatePolicy(c.Request.Context(), &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create policy", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionCreate, policy.AuditEntitySourcePolicy, &p.ID, nil, p, userEmail)

	c.JSON(http.StatusCreated, p)
}

// UpdatePolicy updates an existing policy.
func (h *PolicyHandler) UpdatePolicy(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid policy ID"})
		return
	}

	// Get old value for audit
	oldPolicy, err := h.store.GetPolicy(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get policy", "details": err.Error()})
		return
	}
	if oldPolicy == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Policy not found"})
		return
	}

	var req policy.UpdateSourcePolicyRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	p, err := h.store.UpdatePolicy(c.Request.Context(), id, &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update policy", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionUpdate, policy.AuditEntitySourcePolicy, &p.ID, oldPolicy, p, userEmail)

	c.JSON(http.StatusOK, p)
}

// =============================================================================
// SOURCES (WHITELIST)
// =============================================================================

// ListSources returns all allowed sources.
func (h *PolicyHandler) ListSources(c *gin.Context) {
	var filter policy.SourceListFilter
	if err := c.ShouldBindQuery(&filter); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters", "details": err.Error()})
		return
	}

	// Set defaults
	if filter.Limit <= 0 || filter.Limit > 100 {
		filter.Limit = 50
	}

	sources, total, err := h.store.ListSources(c.Request.Context(), &filter)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list sources", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"sources": sources,
		"total":   total,
		"limit":   filter.Limit,
		"offset":  filter.Offset,
	})
}

// GetSource returns a single source by ID.
func (h *PolicyHandler) GetSource(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid source ID"})
		return
	}

	source, err := h.store.GetSource(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get source", "details": err.Error()})
		return
	}
	if source == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
		return
	}

	c.JSON(http.StatusOK, source)
}

// CreateSource creates a new allowed source.
func (h *PolicyHandler) CreateSource(c *gin.Context) {
	var req policy.CreateAllowedSourceRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	source, err := h.store.CreateSource(c.Request.Context(), &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create source", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionCreate, policy.AuditEntityAllowedSource, &source.ID, nil, source, userEmail)

	c.JSON(http.StatusCreated, source)
}

// UpdateSource updates an existing source.
func (h *PolicyHandler) UpdateSource(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid source ID"})
		return
	}

	// Get old value for audit
	oldSource, err := h.store.GetSource(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get source", "details": err.Error()})
		return
	}
	if oldSource == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
		return
	}

	var req policy.UpdateAllowedSourceRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	source, err := h.store.UpdateSource(c.Request.Context(), id, &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update source", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionUpdate, policy.AuditEntityAllowedSource, &source.ID, oldSource, source, userEmail)

	c.JSON(http.StatusOK, source)
}

// DeleteSource deletes a source.
func (h *PolicyHandler) DeleteSource(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid source ID"})
		return
	}

	// Get source for audit before deletion
	source, err := h.store.GetSource(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get source", "details": err.Error()})
		return
	}
	if source == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Source not found"})
		return
	}

	if err := h.store.DeleteSource(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete source", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionDelete, policy.AuditEntityAllowedSource, &id, source, nil, userEmail)

	c.JSON(http.StatusOK, gin.H{"deleted": true, "id": id})
}

// =============================================================================
// OPERATIONS MATRIX
// =============================================================================

// GetOperationsMatrix returns all sources with their operation permissions.
func (h *PolicyHandler) GetOperationsMatrix(c *gin.Context) {
	sources, err := h.store.GetOperationsMatrix(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get operations matrix", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"sources": sources,
		"operations": []string{
			string(policy.OperationLookup),
			string(policy.OperationRAG),
			string(policy.OperationTraining),
			string(policy.OperationExport),
		},
	})
}

// UpdateOperationPermission updates a single operation permission.
func (h *PolicyHandler) UpdateOperationPermission(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid operation permission ID"})
		return
	}

	var req policy.UpdateOperationPermissionRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	// SECURITY: Prevent enabling training
	if req.IsAllowed != nil && *req.IsAllowed {
		// Check if this is a training operation by querying
		ops, _ := h.store.GetOperationsBySourceID(c.Request.Context(), id)
		for _, op := range ops {
			if op.ID == id && op.Operation == policy.OperationTraining {
				c.JSON(http.StatusForbidden, gin.H{
					"error":   "Training operations cannot be enabled",
					"message": "Training with external data is FORBIDDEN by policy",
				})
				return
			}
		}
	}

	op, err := h.store.UpdateOperationPermission(c.Request.Context(), id, &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update operation permission", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionUpdate, policy.AuditEntityOperationPermission, &op.ID, nil, op, userEmail)

	c.JSON(http.StatusOK, op)
}

// =============================================================================
// PII RULES
// =============================================================================

// ListPIIRules returns all PII detection rules.
func (h *PolicyHandler) ListPIIRules(c *gin.Context) {
	activeOnly := c.Query("active_only") == "true"

	rules, err := h.store.ListPIIRules(c.Request.Context(), activeOnly)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list PII rules", "details": err.Error()})
		return
	}

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

// GetPIIRule returns a single PII rule by ID.
func (h *PolicyHandler) GetPIIRule(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid PII rule ID"})
		return
	}

	rule, err := h.store.GetPIIRule(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get PII rule", "details": err.Error()})
		return
	}
	if rule == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "PII rule not found"})
		return
	}

	c.JSON(http.StatusOK, rule)
}

// CreatePIIRule creates a new PII detection rule.
func (h *PolicyHandler) CreatePIIRule(c *gin.Context) {
	var req policy.CreatePIIRuleRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	rule, err := h.store.CreatePIIRule(c.Request.Context(), &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create PII rule", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionCreate, policy.AuditEntityPIIRule, &rule.ID, nil, rule, userEmail)

	c.JSON(http.StatusCreated, rule)
}

// UpdatePIIRule updates an existing PII rule.
func (h *PolicyHandler) UpdatePIIRule(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid PII rule ID"})
		return
	}

	// Get old value for audit
	oldRule, err := h.store.GetPIIRule(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get PII rule", "details": err.Error()})
		return
	}
	if oldRule == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "PII rule not found"})
		return
	}

	var req policy.UpdatePIIRuleRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	rule, err := h.store.UpdatePIIRule(c.Request.Context(), id, &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update PII rule", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionUpdate, policy.AuditEntityPIIRule, &rule.ID, oldRule, rule, userEmail)

	c.JSON(http.StatusOK, rule)
}

// DeletePIIRule deletes a PII rule.
func (h *PolicyHandler) DeletePIIRule(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid PII rule ID"})
		return
	}

	// Get rule for audit before deletion
	rule, err := h.store.GetPIIRule(c.Request.Context(), id)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get PII rule", "details": err.Error()})
		return
	}
	if rule == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "PII rule not found"})
		return
	}

	if err := h.store.DeletePIIRule(c.Request.Context(), id); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete PII rule", "details": err.Error()})
		return
	}

	// Log audit
	userEmail := getUserEmail(c)
	h.enforcer.LogChange(c.Request.Context(), policy.AuditActionDelete, policy.AuditEntityPIIRule, &id, rule, nil, userEmail)

	c.JSON(http.StatusOK, gin.H{"deleted": true, "id": id})
}

// TestPIIRules tests PII detection against sample text.
func (h *PolicyHandler) TestPIIRules(c *gin.Context) {
	var req policy.PIITestRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	response, err := h.enforcer.DetectPII(c.Request.Context(), req.Text)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to test PII detection", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, response)
}

// =============================================================================
// AUDIT & COMPLIANCE
// =============================================================================

// ListAuditLogs returns audit log entries.
func (h *PolicyHandler) ListAuditLogs(c *gin.Context) {
	var filter policy.AuditLogFilter
	if err := c.ShouldBindQuery(&filter); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters", "details": err.Error()})
		return
	}

	// Set defaults
	if filter.Limit <= 0 || filter.Limit > 500 {
		filter.Limit = 100
	}

	logs, total, err := h.store.ListAuditLogs(c.Request.Context(), &filter)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list audit logs", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"logs":   logs,
		"total":  total,
		"limit":  filter.Limit,
		"offset": filter.Offset,
	})
}

// ListBlockedContent returns blocked content log entries.
func (h *PolicyHandler) ListBlockedContent(c *gin.Context) {
	var filter policy.BlockedContentFilter
	if err := c.ShouldBindQuery(&filter); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters", "details": err.Error()})
		return
	}

	// Set defaults
	if filter.Limit <= 0 || filter.Limit > 500 {
		filter.Limit = 100
	}

	logs, total, err := h.store.ListBlockedContent(c.Request.Context(), &filter)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list blocked content", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"blocked": logs,
		"total":   total,
		"limit":   filter.Limit,
		"offset":  filter.Offset,
	})
}

// CheckCompliance performs a compliance check for a URL.
func (h *PolicyHandler) CheckCompliance(c *gin.Context) {
	var req policy.CheckComplianceRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
		return
	}

	response, err := h.enforcer.CheckCompliance(c.Request.Context(), &req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check compliance", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, response)
}

// GetPolicyStats returns aggregated statistics.
func (h *PolicyHandler) GetPolicyStats(c *gin.Context) {
	stats, err := h.store.GetStats(c.Request.Context())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get stats", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, stats)
}

// GenerateComplianceReport generates an audit report.
func (h *PolicyHandler) GenerateComplianceReport(c *gin.Context) {
	var auditFilter policy.AuditLogFilter
	var blockedFilter policy.BlockedContentFilter

	// Parse date filters
	fromStr := c.Query("from")
	toStr := c.Query("to")

	if fromStr != "" {
		from, err := time.Parse("2006-01-02", fromStr)
		if err == nil {
			auditFilter.FromDate = &from
			blockedFilter.FromDate = &from
		}
	}

	if toStr != "" {
		to, err := time.Parse("2006-01-02", toStr)
		if err == nil {
			// Add 1 day to include the end date
			to = to.Add(24 * time.Hour)
			auditFilter.ToDate = &to
			blockedFilter.ToDate = &to
		}
	}

	// No limit for report
	auditFilter.Limit = 10000
	blockedFilter.Limit = 10000

	auditor := policy.NewAuditor(h.store)
	report, err := auditor.GenerateAuditReport(c.Request.Context(), &auditFilter, &blockedFilter)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate report", "details": err.Error()})
		return
	}

	// Set filename for download
	format := c.Query("format")
	if format == "download" {
		filename := "compliance-report-" + time.Now().Format("2006-01-02") + ".json"
		c.Header("Content-Disposition", "attachment; filename="+filename)
		c.Header("Content-Type", "application/json")
	}

	c.JSON(http.StatusOK, report)
}

// =============================================================================
// HELPERS
// =============================================================================

// getUserEmail extracts user email from context or headers.
func getUserEmail(c *gin.Context) *string {
	// Try to get from header (set by auth proxy)
	email := c.GetHeader("X-User-Email")
	if email != "" {
		return &email
	}

	// Try to get from context (set by auth middleware)
	if e, exists := c.Get("user_email"); exists {
		if emailStr, ok := e.(string); ok {
			return &emailStr
		}
	}

	return nil
}

// =============================================================================
// ROUTE SETUP
// =============================================================================

// SetupPolicyRoutes configures all policy-related routes.
func SetupPolicyRoutes(r *gin.RouterGroup) {
	if policyHandler == nil {
		return
	}

	h := policyHandler

	// Policies
	r.GET("/policies", h.ListPolicies)
	r.GET("/policies/:id", h.GetPolicy)
	r.POST("/policies", h.CreatePolicy)
	r.PUT("/policies/:id", h.UpdatePolicy)

	// Sources (Whitelist)
	r.GET("/sources", h.ListSources)
	r.GET("/sources/:id", h.GetSource)
	r.POST("/sources", h.CreateSource)
	r.PUT("/sources/:id", h.UpdateSource)
	r.DELETE("/sources/:id", h.DeleteSource)

	// Operations Matrix
	r.GET("/operations-matrix", h.GetOperationsMatrix)
	r.PUT("/operations/:id", h.UpdateOperationPermission)

	// PII Rules
	r.GET("/pii-rules", h.ListPIIRules)
	r.GET("/pii-rules/:id", h.GetPIIRule)
	r.POST("/pii-rules", h.CreatePIIRule)
	r.PUT("/pii-rules/:id", h.UpdatePIIRule)
	r.DELETE("/pii-rules/:id", h.DeletePIIRule)
	r.POST("/pii-rules/test", h.TestPIIRules)

	// Audit & Compliance
	r.GET("/policy-audit", h.ListAuditLogs)
	r.GET("/blocked-content", h.ListBlockedContent)
	r.POST("/check-compliance", h.CheckCompliance)
	r.GET("/policy-stats", h.GetPolicyStats)
	r.GET("/compliance-report", h.GenerateComplianceReport)
}
