package backoffice

import (
	"encoding/json"
	"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 caseRequest struct {
	Category       string `json:"category"`
	SourceType     string `json:"source_type"`
	SourceID       string `json:"source_id"`
	CustomerUserID string `json:"customer_user_id"`
	Title          string `json:"title"`
	Description    string `json:"description"`
	Priority       string `json:"priority"`
	Status         string `json:"status"`
	OwnerTeam      string `json:"owner_team"`
	AssignedTo     string `json:"assigned_to_admin_user_id"`
	SLADueAt       string `json:"sla_due_at"`
}

type caseUpdateRequest struct {
	Status      string `json:"status"`
	Priority    string `json:"priority"`
	OwnerTeam   string `json:"owner_team"`
	AssignedTo  string `json:"assigned_to_admin_user_id"`
	SLADueAt    string `json:"sla_due_at"`
	Description string `json:"description"`
}

type noteRequest struct {
	NoteType string `json:"note_type"`
	Body     string `json:"body"`
}

type attachmentRequest struct {
	FileName         string `json:"file_name"`
	ContentType      string `json:"content_type"`
	StorageReference string `json:"storage_reference"`
	ChecksumSHA256   string `json:"checksum_sha256"`
	Classification   string `json:"classification"`
}

type configChangeRequest struct {
	TargetArea      string          `json:"target_area"`
	ChangeType      string          `json:"change_type"`
	TargetType      string          `json:"target_type"`
	TargetID        string          `json:"target_id"`
	ProposedPayload json.RawMessage `json:"proposed_payload"`
	Reason          string          `json:"reason"`
}

type decisionRequest struct {
	DecisionNote      string `json:"decision_note"`
	EvidenceReference string `json:"evidence_reference"`
}

type impersonationRequest struct {
	CustomerUserID  string   `json:"customer_user_id"`
	Reason          string   `json:"reason"`
	TicketReference string   `json:"ticket_reference"`
	AllowedActions  []string `json:"allowed_actions"`
	ExpiresAt       string   `json:"expires_at"`
}

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

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

func (h *Handler) SyncQueues(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	result, err := h.repo.SyncQueues(r.Context())
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.backoffice_queues.synced", "backoffice_queue", "sync", result)
	respond.JSON(w, http.StatusOK, map[string]any{"synced": result})
}

func (h *Handler) Cases(w http.ResponseWriter, r *http.Request) {
	cases, err := h.repo.ListCases(
		r.Context(),
		r.URL.Query().Get("category"),
		r.URL.Query().Get("status"),
		r.URL.Query().Get("owner_team"),
		r.URL.Query().Get("assigned_to_admin_user_id"),
		queryLimit(r),
	)
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"cases": cases})
}

func (h *Handler) CreateCase(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req caseRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	slaDueAt, err := parseOptionalTime(req.SLADueAt, "sla_due_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	item, err := h.repo.CreateCase(r.Context(), CaseParams{
		Category:       req.Category,
		SourceType:     req.SourceType,
		SourceID:       req.SourceID,
		CustomerUserID: req.CustomerUserID,
		Title:          req.Title,
		Description:    req.Description,
		Priority:       req.Priority,
		Status:         req.Status,
		OwnerTeam:      req.OwnerTeam,
		AssignedTo:     req.AssignedTo,
		SLADueAt:       slaDueAt,
		AdminUserID:    claims.Subject,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.backoffice_case.created", "backoffice_case", item.ID, auditCaseMetadata(item))
	respond.Created(w, item)
}

func (h *Handler) UpdateCase(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req caseUpdateRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	slaDueAt, err := parseOptionalTime(req.SLADueAt, "sla_due_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	item, err := h.repo.UpdateCase(r.Context(), CaseUpdateParams{
		ID:          r.PathValue("id"),
		Status:      req.Status,
		Priority:    req.Priority,
		OwnerTeam:   req.OwnerTeam,
		AssignedTo:  req.AssignedTo,
		SLADueAt:    slaDueAt,
		Description: req.Description,
		AdminUserID: claims.Subject,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.backoffice_case.updated", "backoffice_case", item.ID, auditCaseMetadata(item))
	respond.JSON(w, http.StatusOK, item)
}

func (h *Handler) CaseNotes(w http.ResponseWriter, r *http.Request) {
	notes, err := h.repo.ListNotes(r.Context(), r.PathValue("id"), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"notes": notes})
}

func (h *Handler) AddCaseNote(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req noteRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	note, err := h.repo.AddNote(r.Context(), NoteParams{CaseID: r.PathValue("id"), AdminUserID: claims.Subject, NoteType: req.NoteType, Body: req.Body})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.backoffice_case.note_added", "backoffice_case", note.CaseID, map[string]any{"note_id": note.ID, "note_type": note.NoteType})
	respond.Created(w, note)
}

func (h *Handler) CaseAttachments(w http.ResponseWriter, r *http.Request) {
	attachments, err := h.repo.ListAttachments(r.Context(), r.PathValue("id"), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"attachments": attachments})
}

func (h *Handler) AddCaseAttachment(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req attachmentRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	attachment, err := h.repo.AddAttachment(r.Context(), AttachmentParams{
		CaseID:           r.PathValue("id"),
		AdminUserID:      claims.Subject,
		FileName:         req.FileName,
		ContentType:      req.ContentType,
		StorageReference: req.StorageReference,
		ChecksumSHA256:   req.ChecksumSHA256,
		Classification:   req.Classification,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.backoffice_case.attachment_added", "backoffice_case", attachment.CaseID, map[string]any{"attachment_id": attachment.ID, "classification": attachment.Classification})
	respond.Created(w, attachment)
}

func (h *Handler) CaseHistory(w http.ResponseWriter, r *http.Request) {
	history, err := h.repo.ListHistory(r.Context(), r.PathValue("id"), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"history": history})
}

func (h *Handler) ConfigChanges(w http.ResponseWriter, r *http.Request) {
	changes, err := h.repo.ListConfigChanges(r.Context(), r.URL.Query().Get("status"), r.URL.Query().Get("target_area"), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"config_change_requests": changes})
}

func (h *Handler) CreateConfigChange(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req configChangeRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	change, err := h.repo.CreateConfigChange(r.Context(), ConfigChangeParams{
		TargetArea:      req.TargetArea,
		ChangeType:      req.ChangeType,
		TargetType:      req.TargetType,
		TargetID:        req.TargetID,
		ProposedPayload: req.ProposedPayload,
		Reason:          req.Reason,
		AdminUserID:     claims.Subject,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.high_risk_config_change.created", "config_change", change.ID, auditConfigMetadata(change))
	respond.Created(w, change)
}

func (h *Handler) ApproveConfigChange(w http.ResponseWriter, r *http.Request) {
	h.decideConfigChange(w, r, "approve")
}

func (h *Handler) RejectConfigChange(w http.ResponseWriter, r *http.Request) {
	h.decideConfigChange(w, r, "reject")
}

func (h *Handler) CancelConfigChange(w http.ResponseWriter, r *http.Request) {
	h.decideConfigChange(w, r, "cancel")
}

func (h *Handler) ImplementConfigChange(w http.ResponseWriter, r *http.Request) {
	h.decideConfigChange(w, r, "implement")
}

func (h *Handler) decideConfigChange(w http.ResponseWriter, r *http.Request, action string) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req decisionRequest
	if r.Body != nil {
		if err := respond.DecodeJSON(r, &req); err != nil {
			respond.Error(w, err)
			return
		}
	}
	change, err := h.repo.DecideConfigChange(r.Context(), ConfigDecisionParams{
		ID:                r.PathValue("id"),
		AdminUserID:       claims.Subject,
		Action:            action,
		DecisionNote:      req.DecisionNote,
		EvidenceReference: req.EvidenceReference,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.high_risk_config_change."+action, "config_change", change.ID, auditConfigMetadata(change))
	respond.JSON(w, http.StatusOK, change)
}

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

func (h *Handler) EvidencePackage(w http.ResponseWriter, r *http.Request) {
	pkg, err := h.repo.EvidencePackage(r.Context(), r.URL.Query().Get("target_type"), r.URL.Query().Get("target_id"), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, pkg)
}

func (h *Handler) ImpersonationSessions(w http.ResponseWriter, r *http.Request) {
	sessions, err := h.repo.ListImpersonationSessions(r.Context(), r.URL.Query().Get("status"), r.URL.Query().Get("customer_user_id"), queryLimit(r))
	if err != nil {
		respond.Error(w, err)
		return
	}
	respond.JSON(w, http.StatusOK, map[string]any{"impersonation_sessions": sessions})
}

func (h *Handler) StartImpersonation(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	var req impersonationRequest
	if err := respond.DecodeJSON(r, &req); err != nil {
		respond.Error(w, err)
		return
	}
	expiresAt, err := parseOptionalTime(req.ExpiresAt, "expires_at")
	if err != nil {
		respond.Error(w, err)
		return
	}
	expiry := time.Time{}
	if expiresAt != nil {
		expiry = *expiresAt
	}
	session, err := h.repo.StartImpersonation(r.Context(), ImpersonationParams{
		AdminUserID:     claims.Subject,
		CustomerUserID:  req.CustomerUserID,
		Reason:          req.Reason,
		TicketReference: req.TicketReference,
		AllowedActions:  req.AllowedActions,
		ExpiresAt:       expiry,
		RemoteIP:        httputil.RemoteIP(r),
		UserAgent:       r.UserAgent(),
	})
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.support_impersonation.started", "support_impersonation", session.ID, map[string]any{
		"customer_user_id": session.CustomerUserID,
		"ticket_reference": session.TicketReference,
		"allowed_actions":  session.AllowedActions,
		"expires_at":       session.ExpiresAt,
	})
	respond.Created(w, session)
}

func (h *Handler) EndImpersonation(w http.ResponseWriter, r *http.Request) {
	claims, ok := middleware.CurrentClaims(r)
	if !ok {
		respond.Error(w, domain.ErrUnauthorized)
		return
	}
	session, err := h.repo.EndImpersonation(r.Context(), r.PathValue("id"), claims.Subject)
	if err != nil {
		respond.Error(w, err)
		return
	}
	h.recordAudit(r, claims.Subject, "admin.support_impersonation.ended", "support_impersonation", session.ID, map[string]any{"customer_user_id": session.CustomerUserID})
	respond.JSON(w, http.StatusOK, session)
}

func exportFiltersFromRequest(r *http.Request) (ExportFilters, error) {
	from, err := parseOptionalTime(r.URL.Query().Get("from"), "from")
	if err != nil {
		return ExportFilters{}, err
	}
	to, err := parseOptionalTime(r.URL.Query().Get("to"), "to")
	if err != nil {
		return ExportFilters{}, err
	}
	filters := ExportFilters{
		ActorUserID: strings.TrimSpace(r.URL.Query().Get("actor_user_id")),
		TargetType:  strings.TrimSpace(r.URL.Query().Get("target_type")),
		TargetID:    strings.TrimSpace(r.URL.Query().Get("target_id")),
		From:        from,
		To:          to,
	}
	if filters.ActorUserID != "" {
		if err := domain.ValidateUUID("actor_user_id", filters.ActorUserID); err != nil {
			return ExportFilters{}, err
		}
	}
	return filters, nil
}

func parseOptionalTime(raw, field string) (*time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339, raw)
	if err != nil {
		return nil, fmt.Errorf("%w: %s must be RFC3339", domain.ErrValidation, field)
	}
	utc := parsed.UTC()
	return &utc, nil
}

func auditCaseMetadata(item Case) map[string]any {
	return map[string]any{
		"category":    item.Category,
		"status":      item.Status,
		"priority":    item.Priority,
		"owner_team":  item.OwnerTeam,
		"source_type": item.SourceType,
		"source_id":   item.SourceID,
	}
}

func auditConfigMetadata(change ConfigChangeRequest) map[string]any {
	return map[string]any{
		"target_area":        change.TargetArea,
		"change_type":        change.ChangeType,
		"target_type":        change.TargetType,
		"target_id":          change.TargetID,
		"status":             change.Status,
		"evidence_reference": change.EvidenceReference,
	}
}

func (h *Handler) recordAudit(r *http.Request, adminUserID, eventType, targetType, targetID string, metadata any) {
	if h.audit == nil {
		return
	}
	if err := h.audit.Record(r.Context(), audit.Event{
		ActorUserID: &adminUserID,
		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)
	}
}

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
}
