package compliance

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"go.uber.org/zap"

	"github.com/veylant/ia-gateway/internal/apierror"
	"github.com/veylant/ia-gateway/internal/auditlog"
	"github.com/veylant/ia-gateway/internal/middleware"
)

// Handler provides HTTP endpoints for the compliance module.
type Handler struct {
	store      ComplianceStore
	auditLog   auditlog.Logger // nil → 501 for GDPR and export endpoints
	db         *sql.DB         // nil → 501 for Art. 17 erasure log
	tenantName string
	logger     *zap.Logger
}

// New creates a compliance Handler.
func New(store ComplianceStore, logger *zap.Logger) *Handler {
	return &Handler{store: store, logger: logger, tenantName: "Organisation"}
}

// WithAudit attaches an audit logger (required for GDPR access/erase + CSV export).
func (h *Handler) WithAudit(al auditlog.Logger) *Handler {
	h.auditLog = al
	return h
}

// WithDB attaches a database connection (required for Art. 17 erasure log).
func (h *Handler) WithDB(db *sql.DB) *Handler {
	h.db = db
	return h
}

// WithTenantName sets the tenant display name used in PDF headers.
func (h *Handler) WithTenantName(name string) *Handler {
	if name != "" {
		h.tenantName = name
	}
	return h
}

// Routes registers all compliance endpoints on r.
// Callers must mount r under an authenticated prefix.
func (h *Handler) Routes(r chi.Router) {
	// Processing registry CRUD (E9-01)
	r.Get("/entries", h.listEntries)
	r.Post("/entries", h.createEntry)
	r.Get("/entries/{id}", h.getEntry)
	r.Put("/entries/{id}", h.updateEntry)
	r.Delete("/entries/{id}", h.deleteEntry)

	// AI Act classification (E9-02)
	r.Post("/entries/{id}/classify", h.classifyEntry)

	// PDF reports (E9-03, E9-04, E9-07)
	r.Get("/report/article30", h.reportArticle30)
	r.Get("/report/aiact", h.reportAiAct)
	r.Get("/dpia/{id}", h.reportDPIA)

	// GDPR rights (E9-05, E9-06)
	r.Get("/gdpr/access/{user_id}", h.gdprAccess)
	r.Delete("/gdpr/erase/{user_id}", h.gdprErase)

	// CSV export (E7-10)
	r.Get("/export/logs", h.exportLogsCSV)
}

// ─── helpers ──────────────────────────────────────────────────────────────────

func tenantFrom(w http.ResponseWriter, r *http.Request) (string, bool) {
	claims, ok := middleware.ClaimsFromContext(r.Context())
	if !ok || claims.TenantID == "" {
		apierror.WriteError(w, apierror.NewAuthError("missing authentication"))
		return "", false
	}
	return claims.TenantID, true
}

func userFrom(r *http.Request) string {
	if claims, ok := middleware.ClaimsFromContext(r.Context()); ok {
		return claims.UserID
	}
	return "unknown"
}

func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}

func writeStoreError(w http.ResponseWriter, err error) {
	if errors.Is(err, ErrNotFound) {
		apierror.WriteError(w, &apierror.APIError{
			Type: "not_found_error", Message: "entry not found", HTTPStatus: http.StatusNotFound,
		})
		return
	}
	apierror.WriteError(w, apierror.NewUpstreamError(err.Error()))
}

// ─── CRUD ────────────────────────────────────────────────────────────────────

func (h *Handler) listEntries(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	entries, err := h.store.List(r.Context(), tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to list entries: "+err.Error()))
		return
	}
	if entries == nil {
		entries = []ProcessingEntry{}
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"data": entries})
}

type entryRequest struct {
	UseCaseName      string   `json:"use_case_name"`
	LegalBasis       string   `json:"legal_basis"`
	Purpose          string   `json:"purpose"`
	DataCategories   []string `json:"data_categories"`
	Recipients       []string `json:"recipients"`
	Processors       []string `json:"processors"`
	RetentionPeriod  string   `json:"retention_period"`
	SecurityMeasures string   `json:"security_measures"`
	ControllerName   string   `json:"controller_name"`
}

func validateEntry(req entryRequest) error {
	if req.UseCaseName == "" {
		return fmt.Errorf("use_case_name is required")
	}
	if req.LegalBasis == "" {
		return fmt.Errorf("legal_basis is required")
	}
	if req.Purpose == "" {
		return fmt.Errorf("purpose is required")
	}
	if req.RetentionPeriod == "" {
		return fmt.Errorf("retention_period is required")
	}
	return nil
}

func (h *Handler) createEntry(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	var req entryRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}
	if err := validateEntry(req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError(err.Error()))
		return
	}
	if req.DataCategories == nil {
		req.DataCategories = []string{}
	}
	if req.Recipients == nil {
		req.Recipients = []string{}
	}
	if req.Processors == nil {
		req.Processors = []string{}
	}

	entry := ProcessingEntry{
		TenantID:         tenantID,
		UseCaseName:      req.UseCaseName,
		LegalBasis:       req.LegalBasis,
		Purpose:          req.Purpose,
		DataCategories:   req.DataCategories,
		Recipients:       req.Recipients,
		Processors:       req.Processors,
		RetentionPeriod:  req.RetentionPeriod,
		SecurityMeasures: req.SecurityMeasures,
		ControllerName:   req.ControllerName,
		IsActive:         true,
	}
	created, err := h.store.Create(r.Context(), entry)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to create entry: "+err.Error()))
		return
	}
	h.logger.Info("compliance entry created",
		zap.String("id", created.ID),
		zap.String("tenant_id", tenantID),
	)
	writeJSON(w, http.StatusCreated, created)
}

func (h *Handler) getEntry(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")
	entry, err := h.store.Get(r.Context(), id, tenantID)
	if err != nil {
		writeStoreError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, entry)
}

func (h *Handler) updateEntry(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")

	var req entryRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}
	if err := validateEntry(req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError(err.Error()))
		return
	}
	if req.DataCategories == nil {
		req.DataCategories = []string{}
	}
	if req.Recipients == nil {
		req.Recipients = []string{}
	}
	if req.Processors == nil {
		req.Processors = []string{}
	}

	entry := ProcessingEntry{
		ID:               id,
		TenantID:         tenantID,
		UseCaseName:      req.UseCaseName,
		LegalBasis:       req.LegalBasis,
		Purpose:          req.Purpose,
		DataCategories:   req.DataCategories,
		Recipients:       req.Recipients,
		Processors:       req.Processors,
		RetentionPeriod:  req.RetentionPeriod,
		SecurityMeasures: req.SecurityMeasures,
		ControllerName:   req.ControllerName,
		IsActive:         true,
	}
	updated, err := h.store.Update(r.Context(), entry)
	if err != nil {
		writeStoreError(w, err)
		return
	}
	h.logger.Info("compliance entry updated",
		zap.String("id", id),
		zap.String("tenant_id", tenantID),
	)
	writeJSON(w, http.StatusOK, updated)
}

func (h *Handler) deleteEntry(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")
	if err := h.store.Delete(r.Context(), id, tenantID); err != nil {
		writeStoreError(w, err)
		return
	}
	h.logger.Info("compliance entry deleted",
		zap.String("id", id),
		zap.String("tenant_id", tenantID),
	)
	w.WriteHeader(http.StatusNoContent)
}

// ─── AI Act classification (E9-02) ───────────────────────────────────────────

type classifyRequest struct {
	Answers map[string]bool `json:"answers"`
}

func (h *Handler) classifyEntry(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")

	var req classifyRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON: "+err.Error()))
		return
	}
	if len(req.Answers) == 0 {
		apierror.WriteError(w, apierror.NewBadRequestError("answers is required"))
		return
	}

	// Fetch current entry
	entry, err := h.store.Get(r.Context(), id, tenantID)
	if err != nil {
		writeStoreError(w, err)
		return
	}

	// Compute risk level
	entry.RiskLevel = ScoreRisk(req.Answers)
	entry.AiActAnswers = req.Answers

	updated, err := h.store.Update(r.Context(), entry)
	if err != nil {
		writeStoreError(w, err)
		return
	}
	h.logger.Info("AI Act classification updated",
		zap.String("id", id),
		zap.String("risk_level", updated.RiskLevel),
		zap.String("tenant_id", tenantID),
	)
	writeJSON(w, http.StatusOK, updated)
}

// ─── PDF reports ─────────────────────────────────────────────────────────────

func (h *Handler) reportArticle30(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	entries, err := h.store.List(r.Context(), tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to load entries: "+err.Error()))
		return
	}

	format := r.URL.Query().Get("format")
	if format == "json" {
		writeJSON(w, http.StatusOK, map[string]interface{}{"data": entries})
		return
	}

	filename := fmt.Sprintf("article30_rgpd_%s.pdf", time.Now().Format("2006-01-02"))
	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
	if err := GenerateArticle30(entries, h.tenantName, w); err != nil {
		h.logger.Error("Article 30 PDF generation failed", zap.Error(err))
	}
}

func (h *Handler) reportAiAct(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	entries, err := h.store.List(r.Context(), tenantID)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to load entries: "+err.Error()))
		return
	}

	format := r.URL.Query().Get("format")
	if format == "json" {
		writeJSON(w, http.StatusOK, map[string]interface{}{"data": entries})
		return
	}

	filename := fmt.Sprintf("aiact_report_%s.pdf", time.Now().Format("2006-01-02"))
	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
	if err := GenerateAiActReport(entries, h.tenantName, w); err != nil {
		h.logger.Error("AI Act PDF generation failed", zap.Error(err))
	}
}

func (h *Handler) reportDPIA(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	id := chi.URLParam(r, "id")
	entry, err := h.store.Get(r.Context(), id, tenantID)
	if err != nil {
		writeStoreError(w, err)
		return
	}

	filename := fmt.Sprintf("dpia_%s_%s.pdf", id[:8], time.Now().Format("2006-01-02"))
	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
	if err := GenerateDPIA(entry, h.tenantName, w); err != nil {
		h.logger.Error("DPIA PDF generation failed", zap.Error(err))
	}
}

// ─── GDPR Art. 15 — right of access ──────────────────────────────────────────

func (h *Handler) gdprAccess(w http.ResponseWriter, r *http.Request) {
	if h.auditLog == nil {
		apierror.WriteError(w, &apierror.APIError{
			Type: "not_implemented", Message: "audit logging not enabled", HTTPStatus: http.StatusNotImplemented,
		})
		return
	}
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	targetUser := chi.URLParam(r, "user_id")

	q := auditlog.AuditQuery{
		TenantID: tenantID,
		UserID:   targetUser,
		Limit:    1000,
	}
	result, err := h.auditLog.Query(r.Context(), q)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to query logs: "+err.Error()))
		return
	}

	h.logger.Info("GDPR Art. 15 access request",
		zap.String("target_user", targetUser),
		zap.String("requested_by", userFrom(r)),
		zap.Int("records", result.Total),
	)

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"user_id":      targetUser,
		"generated_at": time.Now().Format(time.RFC3339),
		"total":        result.Total,
		"records":      result.Data,
	})
}

// ─── GDPR Art. 17 — right to erasure ─────────────────────────────────────────

func (h *Handler) gdprErase(w http.ResponseWriter, r *http.Request) {
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}
	targetUser := chi.URLParam(r, "user_id")
	reason := r.URL.Query().Get("reason")
	requestedBy := userFrom(r)

	// Soft-delete user in users table
	recordsDeleted := 0
	if h.db != nil {
		res, err := h.db.ExecContext(r.Context(),
			`UPDATE users SET is_active=FALSE, updated_at=NOW() WHERE email=$1 AND tenant_id=$2`,
			targetUser, tenantID,
		)
		if err != nil {
			h.logger.Warn("GDPR erase: users table update failed", zap.Error(err))
		} else {
			n, _ := res.RowsAffected()
			recordsDeleted = int(n)
		}

		// Log erasure (immutable)
		_, logErr := h.db.ExecContext(r.Context(),
			`INSERT INTO gdpr_erasure_log (tenant_id, target_user, requested_by, reason, records_deleted)
			 VALUES ($1, $2, $3, $4, $5)`,
			tenantID, targetUser, requestedBy, reason, recordsDeleted,
		)
		if logErr != nil {
			h.logger.Error("GDPR erase: failed to write erasure log", zap.Error(logErr))
		}
	}

	h.logger.Info("GDPR Art. 17 erasure",
		zap.String("target_user", targetUser),
		zap.String("requested_by", requestedBy),
		zap.Int("records_deleted", recordsDeleted),
	)

	writeJSON(w, http.StatusOK, ErasureRecord{
		TenantID:       tenantID,
		TargetUser:     targetUser,
		RequestedBy:    requestedBy,
		Reason:         reason,
		RecordsDeleted: recordsDeleted,
		Status:         "completed",
		CreatedAt:      time.Now(),
	})
}

// ─── CSV export (E7-10) ───────────────────────────────────────────────────────

func (h *Handler) exportLogsCSV(w http.ResponseWriter, r *http.Request) {
	if h.auditLog == nil {
		apierror.WriteError(w, &apierror.APIError{
			Type: "not_implemented", Message: "audit logging not enabled", HTTPStatus: http.StatusNotImplemented,
		})
		return
	}
	tenantID, ok := tenantFrom(w, r)
	if !ok {
		return
	}

	q := auditlog.AuditQuery{
		TenantID: tenantID,
		Provider: r.URL.Query().Get("provider"),
		Limit:    10000,
	}
	if s := r.URL.Query().Get("start"); s != "" {
		if t, err := time.Parse(time.RFC3339, s); err == nil {
			q.StartTime = t
		}
	}
	if s := r.URL.Query().Get("end"); s != "" {
		if t, err := time.Parse(time.RFC3339, s); err == nil {
			q.EndTime = t
		}
	}

	result, err := h.auditLog.Query(r.Context(), q)
	if err != nil {
		apierror.WriteError(w, apierror.NewUpstreamError("failed to query logs: "+err.Error()))
		return
	}

	filename := fmt.Sprintf("audit_logs_%s_%s.csv", tenantID[:8], time.Now().Format("2006-01-02"))
	w.Header().Set("Content-Type", "text/csv; charset=utf-8")
	w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")

	// Write CSV header
	fmt.Fprintln(w, "request_id,timestamp,user_id,tenant_id,provider,model_requested,model_used,department,user_role,sensitivity_level,token_input,token_output,token_total,cost_usd,latency_ms,status,error_type,pii_entity_count,stream")

	for _, e := range result.Data {
		fmt.Fprintf(w, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%d,%d,%.6f,%d,%s,%s,%d,%t\n",
			e.RequestID,
			e.Timestamp.Format(time.RFC3339),
			e.UserID,
			e.TenantID,
			e.Provider,
			e.ModelRequested,
			e.ModelUsed,
			e.Department,
			e.UserRole,
			e.SensitivityLevel,
			e.TokenInput,
			e.TokenOutput,
			e.TokenTotal,
			e.CostUSD,
			e.LatencyMs,
			e.Status,
			e.ErrorType,
			e.PIIEntityCount,
			e.Stream,
		)
	}
}
