package privacy

import (
	"fmt"
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/niels/banking-app/backend/internal/audit"
	"github.com/niels/banking-app/backend/internal/domain"
	"github.com/niels/banking-app/backend/internal/httpapi/middleware"
	"github.com/niels/banking-app/backend/internal/platform/httputil"
	"github.com/niels/banking-app/backend/internal/respond"
)

type Handler struct {
	repo  *Repository
	audit *audit.Repository
	log   *slog.Logger
}

type retentionPolicyRequest struct {
	PolicyKey             string `json:"policy_key"`
	DataCategory          string `json:"data_category"`
	RetentionPeriodDays   int    `json:"retention_period_days"`
	AnonymizationStrategy string `json:"anonymization_strategy"`
	DeletionStrategy      string `json:"deletion_strategy"`
	LegalHoldAllowed      *bool  `json:"legal_hold_allowed"`
	Active                *bool  `json:"active"`
}

type dataSubjectRequestBody struct {
	UserID         string `json:"user_id"`
	RequesterEmail string `json:"requester_email"`
	RequestType    string `json:"request_type"`
	Details        string `json:"details"`
}

type updateDataSubjectRequestBody struct {
	Status                string `json:"status"`
	VerificationStatus    string `json:"verification_status"`
	AssignedToAdminUserID string `json:"assigned_to_admin_user_id"`
	Details               string `json:"details"`
}

type processDataSubjectRequestBody struct {
	DryRun bool `json:"dry_run"`
}

type privacyNoticeRequest struct {
	NoticeType  string `json:"notice_type"`
	Version     string `json:"version"`
	Status      string `json:"status"`
	Content     string `json:"content"`
	EffectiveAt string `json:"effective_at"`
}

type securityAttestationRequest struct {
	ControlType string `json:"control_type"`
	Status      string `json:"status"`
	Evidence    string `json:"evidence"`
}

type residencyPolicyRequest struct {
	RegionCode               string   `json:"region_code"`
	Status                   string   `json:"status"`
	AllowedStorageRegions    []string `json:"allowed_storage_regions"`
	RestrictedDataCategories []string `json:"restricted_data_categories"`
	Notes                    string   `json:"notes"`
}

func NewHandler(repo *Repository, auditRepo *audit.Repository, log *slog.Logger) *Handler {
	return &Handler{repo: repo, audit: auditRepo, log: log}
}

func (h *Handler) PublicNotices(w http.ResponseWriter, r *http.Request) {
	notices, err := h.repo.ListPrivacyNotices(r.Context(), strings.TrimSpace(r.URL.Query().Get("type")), true, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"privacy_notices": notices})
}

func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
	dashboard, err := h.repo.Dashboard(r.Context(), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, dashboard)
}

func (h *Handler) Inventory(w http.ResponseWriter, r *http.Request) {
	items, err := h.repo.ListInventory(
		r.Context(),
		strings.TrimSpace(r.URL.Query().Get("category")),
		strings.TrimSpace(r.URL.Query().Get("classification")),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"data_inventory": items})
}

func (h *Handler) RetentionPolicies(w http.ResponseWriter, r *http.Request) {
	activeOnly := r.URL.Query().Get("active") != "false"
	policies, err := h.repo.ListRetentionPolicies(r.Context(), activeOnly, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"retention_policies": policies})
}

func (h *Handler) UpsertRetentionPolicy(w http.ResponseWriter, r *http.Request) {
	var req retentionPolicyRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	category := strings.ToLower(strings.TrimSpace(req.DataCategory))
	if !validDataCategory(category) {
		respond.Error(w, fmt.Errorf("%w: invalid data_category", domain.ErrValidation))
		return
	}
	if strings.TrimSpace(req.PolicyKey) == "" {
		respond.Error(w, fmt.Errorf("%w: policy_key is required", domain.ErrValidation))
		return
	}
	if req.RetentionPeriodDays < 0 {
		respond.Error(w, fmt.Errorf("%w: retention_period_days cannot be negative", domain.ErrValidation))
		return
	}
	legalHoldAllowed := true
	if req.LegalHoldAllowed != nil {
		legalHoldAllowed = *req.LegalHoldAllowed
	}
	active := true
	if req.Active != nil {
		active = *req.Active
	}
	policy, err := h.repo.UpsertRetentionPolicy(r.Context(), RetentionPolicyParams{
		PolicyKey:             strings.TrimSpace(req.PolicyKey),
		DataCategory:          category,
		RetentionPeriodDays:   req.RetentionPeriodDays,
		AnonymizationStrategy: strings.TrimSpace(req.AnonymizationStrategy),
		DeletionStrategy:      strings.TrimSpace(req.DeletionStrategy),
		LegalHoldAllowed:      legalHoldAllowed,
		Active:                active,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	h.recordAudit(r, actorID, "privacy.retention_policy.upserted", "data_retention_policy", policy.ID, map[string]string{"policy_key": policy.PolicyKey})
	respond.Created(w, policy)
}

func (h *Handler) DataSubjectRequests(w http.ResponseWriter, r *http.Request) {
	requests, err := h.repo.ListDataSubjectRequests(r.Context(), strings.TrimSpace(r.URL.Query().Get("status")), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"data_subject_requests": requests})
}

func (h *Handler) CreateDataSubjectRequest(w http.ResponseWriter, r *http.Request) {
	var req dataSubjectRequestBody
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	requestType := strings.ToLower(strings.TrimSpace(req.RequestType))
	if !validDSRType(requestType) {
		respond.Error(w, fmt.Errorf("%w: invalid request_type", domain.ErrValidation))
		return
	}
	userID := strings.TrimSpace(req.UserID)
	if userID != "" {
		if err := domain.ValidateUUID("user_id", userID); err != nil {
			respond.Error(w, err)
			return
		}
	}
	email := strings.ToLower(strings.TrimSpace(req.RequesterEmail))
	if email == "" && userID == "" {
		respond.Error(w, fmt.Errorf("%w: requester_email or user_id is required", domain.ErrValidation))
		return
	}
	actorID := currentActorID(r)
	request, err := h.repo.CreateDataSubjectRequest(r.Context(), DataSubjectRequestParams{
		UserID:         userID,
		RequesterEmail: email,
		RequestType:    requestType,
		Details:        strings.TrimSpace(req.Details),
		AdminUserID:    actorID,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "privacy.data_subject_request.created", "data_subject_request", request.ID, map[string]string{"request_type": request.RequestType})
	respond.Created(w, request)
}

func (h *Handler) UpdateDataSubjectRequest(w http.ResponseWriter, r *http.Request) {
	requestID := r.PathValue("id")
	if err := domain.ValidateUUID("id", requestID); err != nil {
		respond.Error(w, err)
		return
	}
	var req updateDataSubjectRequestBody
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status != "" && !validDSRStatus(status) {
		respond.Error(w, fmt.Errorf("%w: invalid status", domain.ErrValidation))
		return
	}
	verification := strings.ToLower(strings.TrimSpace(req.VerificationStatus))
	if verification != "" && !validVerificationStatus(verification) {
		respond.Error(w, fmt.Errorf("%w: invalid verification_status", domain.ErrValidation))
		return
	}
	assignedTo := strings.TrimSpace(req.AssignedToAdminUserID)
	if assignedTo != "" {
		if err := domain.ValidateUUID("assigned_to_admin_user_id", assignedTo); err != nil {
			respond.Error(w, err)
			return
		}
	}
	var details *string
	if strings.TrimSpace(req.Details) != "" {
		trimmed := strings.TrimSpace(req.Details)
		details = &trimmed
	}
	request, err := h.repo.UpdateDataSubjectRequest(r.Context(), DataSubjectRequestUpdate{
		ID:                    requestID,
		Status:                status,
		VerificationStatus:    verification,
		AssignedToAdminUserID: assignedTo,
		Details:               details,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	h.recordAudit(r, actorID, "privacy.data_subject_request.updated", "data_subject_request", request.ID, map[string]string{"status": request.Status})
	respond.JSON(w, http.StatusOK, request)
}

func (h *Handler) ProcessDataSubjectRequest(w http.ResponseWriter, r *http.Request) {
	requestID := r.PathValue("id")
	if err := domain.ValidateUUID("id", requestID); err != nil {
		respond.Error(w, err)
		return
	}
	var req processDataSubjectRequestBody
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	request, job, err := h.repo.ProcessDataSubjectRequest(r.Context(), requestID, req.DryRun)
	if err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	h.recordAudit(r, actorID, "privacy.data_subject_request.processed", "data_subject_request", request.ID, map[string]any{
		"request_type": request.RequestType,
		"dry_run":      req.DryRun,
	})
	respond.JSON(w, http.StatusOK, map[string]any{"data_subject_request": request, "deletion_job": job})
}

func (h *Handler) Notices(w http.ResponseWriter, r *http.Request) {
	notices, err := h.repo.ListPrivacyNotices(r.Context(), strings.TrimSpace(r.URL.Query().Get("type")), false, queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"privacy_notices": notices})
}

func (h *Handler) UpsertNotice(w http.ResponseWriter, r *http.Request) {
	var req privacyNoticeRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	noticeType := strings.ToLower(strings.TrimSpace(req.NoticeType))
	if noticeType != "privacy" && noticeType != "cookie" {
		respond.Error(w, fmt.Errorf("%w: notice_type must be privacy or cookie", domain.ErrValidation))
		return
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status == "" {
		status = "draft"
	}
	if status != "draft" && status != "published" && status != "retired" {
		respond.Error(w, fmt.Errorf("%w: invalid notice status", domain.ErrValidation))
		return
	}
	if strings.TrimSpace(req.Version) == "" || strings.TrimSpace(req.Content) == "" {
		respond.Error(w, fmt.Errorf("%w: version and content are required", domain.ErrValidation))
		return
	}
	var effectiveAt *time.Time
	if strings.TrimSpace(req.EffectiveAt) != "" {
		parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(req.EffectiveAt))
		if err != nil {
			respond.Error(w, fmt.Errorf("%w: effective_at must be RFC3339", domain.ErrValidation))
			return
		}
		effectiveAt = &parsed
	}
	actorID := currentActorID(r)
	notice, err := h.repo.UpsertPrivacyNotice(r.Context(), PrivacyNoticeParams{
		NoticeType:  noticeType,
		Version:     strings.TrimSpace(req.Version),
		Status:      status,
		Content:     strings.TrimSpace(req.Content),
		EffectiveAt: effectiveAt,
		AdminUserID: actorID,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "privacy.notice.upserted", "privacy_notice", notice.ID, map[string]string{"notice_type": notice.NoticeType, "version": notice.Version})
	respond.Created(w, notice)
}

func (h *Handler) PublishNotice(w http.ResponseWriter, r *http.Request) {
	noticeID := r.PathValue("id")
	if err := domain.ValidateUUID("id", noticeID); err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	notice, err := h.repo.PublishPrivacyNotice(r.Context(), noticeID, actorID)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "privacy.notice.published", "privacy_notice", notice.ID, map[string]string{"notice_type": notice.NoticeType, "version": notice.Version})
	respond.JSON(w, http.StatusOK, notice)
}

func (h *Handler) SecurityAttestations(w http.ResponseWriter, r *http.Request) {
	attestations, err := h.repo.ListSecurityAttestations(r.Context(), strings.TrimSpace(r.URL.Query().Get("control_type")), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"security_attestations": attestations})
}

func (h *Handler) CreateSecurityAttestation(w http.ResponseWriter, r *http.Request) {
	var req securityAttestationRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	controlType := strings.ToLower(strings.TrimSpace(req.ControlType))
	if !validControlType(controlType) {
		respond.Error(w, fmt.Errorf("%w: invalid control_type", domain.ErrValidation))
		return
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status != "pass" && status != "fail" && status != "unknown" {
		respond.Error(w, fmt.Errorf("%w: status must be pass, fail or unknown", domain.ErrValidation))
		return
	}
	actorID := currentActorID(r)
	attestation, err := h.repo.CreateSecurityAttestation(r.Context(), SecurityAttestationParams{
		ControlType: controlType,
		Status:      status,
		Evidence:    strings.TrimSpace(req.Evidence),
		AdminUserID: actorID,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, actorID, "privacy.security_attestation.created", "data_protection_attestation", attestation.ID, map[string]string{"control_type": attestation.ControlType, "status": attestation.Status})
	respond.Created(w, attestation)
}

func (h *Handler) ResidencyPolicies(w http.ResponseWriter, r *http.Request) {
	policies, err := h.repo.ListResidencyPolicies(r.Context(), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"residency_policies": policies})
}

func (h *Handler) UpsertResidencyPolicy(w http.ResponseWriter, r *http.Request) {
	var req residencyPolicyRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	regionCode := strings.ToUpper(strings.TrimSpace(req.RegionCode))
	if regionCode == "" {
		respond.Error(w, fmt.Errorf("%w: region_code is required", domain.ErrValidation))
		return
	}
	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status == "" {
		status = "draft"
	}
	if status != "draft" && status != "active" && status != "retired" {
		respond.Error(w, fmt.Errorf("%w: status must be draft, active or retired", domain.ErrValidation))
		return
	}
	policy, err := h.repo.UpsertResidencyPolicy(r.Context(), ResidencyPolicyParams{
		RegionCode:               regionCode,
		Status:                   status,
		AllowedStorageRegions:    normalizeStringList(req.AllowedStorageRegions, true),
		RestrictedDataCategories: normalizeStringList(req.RestrictedDataCategories, false),
		Notes:                    strings.TrimSpace(req.Notes),
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	actorID := currentActorID(r)
	h.recordAudit(r, actorID, "privacy.residency_policy.upserted", "data_residency_policy", policy.ID, map[string]string{"region_code": policy.RegionCode})
	respond.Created(w, policy)
}

func (h *Handler) AuditIntegrity(w http.ResponseWriter, r *http.Request) {
	report, err := h.repo.VerifyAuditIntegrity(r.Context(), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, report)
}

func queryLimit(r *http.Request) int {
	limit := 50
	if raw := r.URL.Query().Get("limit"); raw != "" {
		if parsed, err := strconv.Atoi(raw); err == nil {
			limit = parsed
		}
	}
	return limit
}

func currentActorID(r *http.Request) string {
	if claims, ok := middleware.CurrentClaims(r); ok {
		return claims.Subject
	}
	return ""
}

func validDataCategory(category string) bool {
	switch category {
	case "pii", "financial", "card", "crypto", "secret", "audit", "operational":
		return true
	default:
		return false
	}
}

func validDSRType(requestType string) bool {
	switch requestType {
	case "access", "rectification", "erasure", "restriction", "portability", "objection":
		return true
	default:
		return false
	}
}

func validDSRStatus(status string) bool {
	switch status {
	case "received", "verifying_identity", "in_progress", "completed", "rejected", "canceled":
		return true
	default:
		return false
	}
}

func validVerificationStatus(status string) bool {
	switch status {
	case "pending", "verified", "failed":
		return true
	default:
		return false
	}
}

func validControlType(controlType string) bool {
	switch controlType {
	case "encryption_at_rest", "tls_in_transit", "backup_encryption", "restore_access", "audit_retention", "audit_tamper_evidence":
		return true
	default:
		return false
	}
}

func normalizeStringList(values []string, upper bool) []string {
	result := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		if upper {
			trimmed = strings.ToUpper(trimmed)
		} else {
			trimmed = strings.ToLower(trimmed)
		}
		result = append(result, trimmed)
	}
	return result
}

func (h *Handler) recordAudit(r *http.Request, actorID, eventType, targetType, targetID string, metadata any) {
	var actor *string
	if actorID != "" {
		actor = &actorID
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: actor,
		EventType:   eventType,
		TargetType:  targetType,
		TargetID:    targetID,
		Metadata:    metadata,
		RemoteIP:    httputil.RemoteIP(r),
		UserAgent:   r.UserAgent(),
	}); err != nil && h.log != nil {
		h.log.Error("audit record failed", "error", err)
	}
}
