package cards

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

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

type webhookEnvelope struct {
	ID        string          `json:"id"`
	Type      string          `json:"type"`
	CreatedAt string          `json:"created_at"`
	Data      json.RawMessage `json:"data"`
}

type authorizationRequestedData struct {
	ExternalAuthorizationID string `json:"external_authorization_id"`
	ExternalCardID          string `json:"external_card_id"`
	AmountCents             int64  `json:"amount_cents"`
	Currency                string `json:"currency"`
	MerchantName            string `json:"merchant_name"`
	MerchantCategoryCode    string `json:"merchant_category_code"`
	MerchantCountry         string `json:"merchant_country"`
}

type authorizationReversedData struct {
	ExternalAuthorizationID string `json:"external_authorization_id"`
}

type authorizationClearedData struct {
	ExternalAuthorizationID string `json:"external_authorization_id"`
	AmountCents             int64  `json:"amount_cents"`
}

type limitSyncedData struct {
	ExternalCardID     string `json:"external_card_id"`
	SpendingLimitCents int64  `json:"spending_limit_cents"`
}

type statusUpdatedData struct {
	ExternalCardID string `json:"external_card_id"`
	Status         string `json:"status"`
}

func (h *Handler) Webhook(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid webhook body", domain.ErrValidation))
		return
	}

	if !h.validWebhookSignature(r.Header.Get("X-Card-Issuer-Signature"), r.Header.Get("X-Card-Issuer-Timestamp"), body) {
		respond.Problem(w, http.StatusUnauthorized, "invalid_signature", "invalid card issuer signature")
		return
	}

	var event webhookEnvelope
	if err := json.Unmarshal(body, &event); err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid webhook JSON", domain.ErrValidation))
		return
	}

	event.ID = strings.TrimSpace(event.ID)
	event.Type = strings.TrimSpace(event.Type)
	if event.ID == "" || event.Type == "" {
		respond.Error(w, fmt.Errorf("%w: webhook id and type are required", domain.ErrValidation))
		return
	}
	if h.providerWebhooks != nil {
		decision, err := h.providerWebhooks.RecordWebhookEvent(r.Context(), providers.WebhookEventRecord{
			ProviderType:     "card_issuer",
			ProviderName:     h.providerName,
			ExternalEventID:  event.ID,
			EventType:        event.Type,
			SequenceNumber:   providers.WebhookSequence(r.Header.Get("X-Card-Issuer-Sequence")),
			Payload:          body,
			SignatureStatus:  providers.SignatureVerified,
			ProcessingStatus: "processing",
		})
		if err != nil {
			respond.Error(w, err)
			return
		}
		if !decision.Inserted {
			respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "duplicate_event"})
			return
		}
		if decision.OrderingStatus == providers.OrderingGap || decision.OrderingStatus == providers.OrderingRegression {
			respond.JSON(w, http.StatusOK, map[string]string{"status": "quarantined", "reason": decision.OrderingStatus})
			return
		}
	}

	inserted, err := h.repo.RecordWebhookEvent(r.Context(), event.ID, event.Type, body)
	if err != nil {
		respond.Error(w, err)
		return
	}
	if !inserted {
		respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "duplicate_event"})
		return
	}

	switch event.Type {
	case "authorization.requested":
		h.handleAuthorizationRequested(w, r, event, body)
	case "authorization.cleared":
		h.handleAuthorizationCleared(w, r, event, body)
	case "authorization.reversed":
		h.handleAuthorizationReversed(w, r, event, body)
	case "virtual_card.spending_limit.synced":
		h.handleLimitSynced(w, r, event)
	case "virtual_card.status.updated":
		h.handleStatusUpdated(w, r, event)
	default:
		respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "unsupported_event_type"})
	}
}

func (h *Handler) handleAuthorizationRequested(w http.ResponseWriter, r *http.Request, event webhookEnvelope, raw []byte) {
	var data authorizationRequestedData
	if err := json.Unmarshal(event.Data, &data); err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid authorization data", domain.ErrValidation))
		return
	}

	data.Currency = domain.NormalizeCurrency(data.Currency)
	if strings.TrimSpace(data.ExternalAuthorizationID) == "" || strings.TrimSpace(data.ExternalCardID) == "" {
		respond.Error(w, fmt.Errorf("%w: external authorization and card ids are required", domain.ErrValidation))
		return
	}
	if err := domain.ValidateCurrency(data.Currency); err != nil {
		respond.Error(w, err)
		return
	}

	decision, err := h.repo.Authorize(r.Context(), AuthorizationParams{
		ExternalAuthorizationID: strings.TrimSpace(data.ExternalAuthorizationID),
		ExternalCardID:          strings.TrimSpace(data.ExternalCardID),
		AmountCents:             data.AmountCents,
		Currency:                data.Currency,
		MerchantName:            strings.TrimSpace(data.MerchantName),
		MerchantCategoryCode:    strings.TrimSpace(data.MerchantCategoryCode),
		MerchantCountry:         strings.ToUpper(strings.TrimSpace(data.MerchantCountry)),
		RawEvent:                raw,
	})
	if err != nil {
		respond.Error(w, err)
		return
	}

	targetID := decision.Authorization.ID
	if targetID == "" {
		targetID = data.ExternalAuthorizationID
	}

	h.recordSystemAudit(r, "card_authorization."+decision.Decision, "card_authorization", targetID, map[string]any{
		"external_authorization_id": data.ExternalAuthorizationID,
		"external_card_id":          data.ExternalCardID,
		"amount_cents":              data.AmountCents,
		"currency":                  data.Currency,
		"reason":                    decision.Reason,
	})

	respond.JSON(w, http.StatusOK, decision)
}

func (h *Handler) handleAuthorizationCleared(w http.ResponseWriter, r *http.Request, event webhookEnvelope, raw []byte) {
	var data authorizationClearedData
	if err := json.Unmarshal(event.Data, &data); err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid clearing data", domain.ErrValidation))
		return
	}

	auth, err := h.repo.ClearAuthorization(r.Context(), strings.TrimSpace(data.ExternalAuthorizationID), data.AmountCents, raw)
	if errors.Is(err, domain.ErrNotFound) {
		respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "unknown_authorization"})
		return
	}
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordSystemAudit(r, "card_authorization.cleared", "card_authorization", auth.ID, map[string]any{
		"external_authorization_id": auth.ExternalAuthorizationID,
		"amount_cents":              auth.AmountCents,
	})

	respond.JSON(w, http.StatusOK, map[string]any{"status": "processed", "authorization": auth})
}

func (h *Handler) handleAuthorizationReversed(w http.ResponseWriter, r *http.Request, event webhookEnvelope, raw []byte) {
	var data authorizationReversedData
	if err := json.Unmarshal(event.Data, &data); err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid reversal data", domain.ErrValidation))
		return
	}

	auth, err := h.repo.ReverseAuthorization(r.Context(), strings.TrimSpace(data.ExternalAuthorizationID), raw)
	if errors.Is(err, domain.ErrNotFound) {
		respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "unknown_authorization"})
		return
	}
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordSystemAudit(r, "card_authorization.reversed", "card_authorization", auth.ID, map[string]any{
		"external_authorization_id": auth.ExternalAuthorizationID,
	})

	respond.JSON(w, http.StatusOK, map[string]any{"status": "processed", "authorization": auth})
}

func (h *Handler) handleLimitSynced(w http.ResponseWriter, r *http.Request, event webhookEnvelope) {
	var data limitSyncedData
	if err := json.Unmarshal(event.Data, &data); err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid limit sync data", domain.ErrValidation))
		return
	}
	if data.SpendingLimitCents < 0 {
		respond.Error(w, fmt.Errorf("%w: spending_limit_cents cannot be negative", domain.ErrValidation))
		return
	}

	card, err := h.repo.UpdateSpendingLimitByExternalID(r.Context(), strings.TrimSpace(data.ExternalCardID), data.SpendingLimitCents)
	if errors.Is(err, domain.ErrNotFound) {
		respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "unknown_card"})
		return
	}
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordSystemAudit(r, "virtual_card.limit_synced_from_issuer", "virtual_card", card.ID, map[string]any{
		"spending_limit_cents": card.SpendingLimitCents,
		"last4":                card.Last4,
	})

	respond.JSON(w, http.StatusOK, map[string]any{"status": "processed", "card": card})
}

func (h *Handler) handleStatusUpdated(w http.ResponseWriter, r *http.Request, event webhookEnvelope) {
	var data statusUpdatedData
	if err := json.Unmarshal(event.Data, &data); err != nil {
		respond.Error(w, fmt.Errorf("%w: invalid status data", domain.ErrValidation))
		return
	}

	status := strings.ToLower(strings.TrimSpace(data.Status))
	if status != "active" && status != "frozen" && status != "canceled" {
		respond.Error(w, fmt.Errorf("%w: unsupported card status", domain.ErrValidation))
		return
	}

	card, err := h.repo.UpdateStatusByExternalID(r.Context(), strings.TrimSpace(data.ExternalCardID), status)
	if errors.Is(err, domain.ErrNotFound) {
		respond.JSON(w, http.StatusOK, map[string]string{"status": "ignored", "reason": "unknown_card"})
		return
	}
	if err != nil {
		respond.Error(w, err)
		return
	}

	h.recordSystemAudit(r, "virtual_card.status_synced_from_issuer", "virtual_card", card.ID, map[string]any{
		"status": card.Status,
		"last4":  card.Last4,
	})

	respond.JSON(w, http.StatusOK, map[string]any{"status": "processed", "card": card})
}

func (h *Handler) validWebhookSignature(header, timestamp string, body []byte) bool {
	return providers.VerifyWebhookHMAC(providers.WebhookVerification{
		ProviderType: "card_issuer",
		ProviderName: h.providerName,
		Secret:       h.webhookSecret,
		Signature:    header,
		Timestamp:    timestamp,
		Body:         body,
		Tolerance:    h.webhookTolerance,
	}) == nil
}

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