package payments

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"

	"github.com/niels/banking-app/backend/internal/domain"
)

const (
	StatusPending    = "pending"
	StatusProcessing = "processing"
	StatusReviewHeld = "review_held"
	StatusCompleted  = "completed"
	StatusFailed     = "failed"
	StatusRejected   = "rejected"
	StatusReversed   = "reversed"

	ProviderLocalSEPA = "local_sepa"
	ProviderRisk      = "risk"
	ProviderAdmin     = "admin"

	ProviderCodeAccepted             = "ACCEPTED"
	ProviderCodeTemporaryFailure     = "LOCAL_TEMPORARY_FAILURE"
	ProviderCodePermanentFailure     = "LOCAL_PERMANENT_FAILURE"
	ProviderCodeMaxAttemptsExceeded  = "MAX_ATTEMPTS_EXCEEDED"
	ProviderCodeRiskReview           = "RISK_REVIEW"
	ProviderCodeManualReviewRequired = "MANUAL_REVIEW_REQUIRED"
	ProviderCodeManualReviewReleased = "MANUAL_REVIEW_RELEASED"
	ProviderCodeManualReviewRejected = "MANUAL_REVIEW_REJECTED"

	ActorSystem   = "system"
	ActorAdmin    = "admin"
	ActorProvider = "provider"
	ActorRisk     = "risk"
)

type txer interface {
	Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}

type ReasonMapping struct {
	Provider     string
	ProviderCode string
	InternalCode string
	Category     string
	Severity     string
	Description  string
}

type StatusEventParams struct {
	TransferID   string
	FromStatus   string
	ToStatus     string
	Provider     string
	ProviderCode string
	Reason       ReasonMapping
	Note         string
	ActorType    string
	ActorUserID  string
}

var allowedTransitions = map[string]map[string]bool{
	StatusPending: {
		StatusProcessing: true,
		StatusReviewHeld: true,
	},
	StatusProcessing: {
		StatusCompleted: true,
		StatusPending:   true,
		StatusFailed:    true,
	},
	StatusReviewHeld: {
		StatusPending:  true,
		StatusRejected: true,
	},
	StatusCompleted: {
		StatusReversed: true,
	},
	StatusFailed: {
		StatusReversed: true,
	},
}

func CanTransition(fromStatus, toStatus string) bool {
	return allowedTransitions[strings.TrimSpace(fromStatus)][strings.TrimSpace(toStatus)]
}

func RequireTransition(fromStatus, toStatus string) error {
	fromStatus = strings.TrimSpace(fromStatus)
	toStatus = strings.TrimSpace(toStatus)
	if CanTransition(fromStatus, toStatus) {
		return nil
	}
	return fmt.Errorf("%w: invalid payment status transition from %s to %s", domain.ErrValidation, fromStatus, toStatus)
}

func MapReason(ctx context.Context, q txer, provider, providerCode string) (ReasonMapping, error) {
	provider = strings.TrimSpace(provider)
	providerCode = strings.ToUpper(strings.TrimSpace(providerCode))
	if provider == "" || providerCode == "" {
		return ReasonMapping{}, fmt.Errorf("%w: provider and provider_code are required", domain.ErrValidation)
	}

	var mapping ReasonMapping
	err := q.QueryRow(ctx, `
		SELECT provider, provider_code, internal_code, category, severity, description
		FROM payment_reason_code_mappings
		WHERE provider = $1 AND provider_code = $2 AND active = true
	`, provider, providerCode).Scan(
		&mapping.Provider,
		&mapping.ProviderCode,
		&mapping.InternalCode,
		&mapping.Category,
		&mapping.Severity,
		&mapping.Description,
	)
	if errors.Is(err, pgx.ErrNoRows) {
		return fallbackReason(provider, providerCode), nil
	}
	return mapping, err
}

func UpdateTransferReason(ctx context.Context, q txer, transferID string, reason ReasonMapping, manualReviewRequired bool) error {
	_, err := q.Exec(ctx, `
		UPDATE transfers
		SET payment_reason_code = NULLIF($2, ''),
			payment_reason_category = NULLIF($3, ''),
			payment_reason_description = NULLIF($4, ''),
			manual_review_required = $5
		WHERE id = $1
	`, transferID, reason.InternalCode, reason.Category, reason.Description, manualReviewRequired)
	return err
}

func RecordStatusEvent(ctx context.Context, q txer, params StatusEventParams) error {
	if params.TransferID == "" {
		return fmt.Errorf("%w: transfer_id is required", domain.ErrValidation)
	}
	params.Provider = strings.TrimSpace(params.Provider)
	if params.Provider == "" {
		params.Provider = "system"
	}
	params.ProviderCode = strings.ToUpper(strings.TrimSpace(params.ProviderCode))
	params.ActorType = strings.ToLower(strings.TrimSpace(params.ActorType))
	if params.ActorType == "" {
		params.ActorType = ActorSystem
	}
	if params.ActorType != ActorSystem && params.ActorType != ActorAdmin && params.ActorType != ActorProvider && params.ActorType != ActorRisk {
		return fmt.Errorf("%w: invalid payment status actor_type", domain.ErrValidation)
	}
	if params.Reason.InternalCode == "" {
		params.Reason = fallbackReason(params.Provider, params.ProviderCode)
	}
	if params.ToStatus == "" {
		return fmt.Errorf("%w: to_status is required", domain.ErrValidation)
	}

	_, err := q.Exec(ctx, `
		INSERT INTO payment_status_events (
			transfer_id, from_status, to_status, provider, provider_code, reason_code,
			reason_category, note, actor_type, actor_user_id
		)
		VALUES ($1, NULLIF($2, ''), $3, $4, NULLIF($5, ''), $6, $7, NULLIF($8, ''), $9, NULLIF($10, '')::uuid)
	`, params.TransferID, params.FromStatus, params.ToStatus, params.Provider, params.ProviderCode,
		params.Reason.InternalCode, params.Reason.Category, strings.TrimSpace(params.Note),
		params.ActorType, strings.TrimSpace(params.ActorUserID))
	return err
}

func fallbackReason(provider, providerCode string) ReasonMapping {
	code := strings.ToLower(strings.TrimSpace(providerCode))
	if code == "" {
		code = "unknown"
	}
	code = strings.ReplaceAll(code, " ", "_")
	return ReasonMapping{
		Provider:     strings.TrimSpace(provider),
		ProviderCode: strings.ToUpper(strings.TrimSpace(providerCode)),
		InternalCode: code,
		Category:     "system",
		Severity:     "medium",
		Description:  "Unmapped provider reason code",
	}
}
