package gateway

import (
	"hash/fnv"
	"strconv"
	"strings"
	"time"

	storagemodel "github.com/tech/sendico/gateway/chsettle/storage/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

const (
	scenarioMetadataKey      = "chsettle_scenario"
	scenarioMetadataAliasKey = "scenario"
)

type settlementScenario struct {
	Name          string
	InitialStatus storagemodel.PaymentStatus
	FinalStatus   storagemodel.PaymentStatus
	FinalDelay    time.Duration
	FailureReason string
}

type settlementScenarioTrace struct {
	Source             string
	OverrideRaw        string
	OverrideNormalized string
	AmountRaw          string
	AmountCurrency     string
	BucketSlot         int
}

var scenarioFastSuccess = settlementScenario{
	Name:          "fast_success",
	InitialStatus: storagemodel.PaymentStatusSuccess,
}

var scenarioSlowSuccess = settlementScenario{
	Name:          "slow_success",
	InitialStatus: storagemodel.PaymentStatusWaiting,
	FinalStatus:   storagemodel.PaymentStatusSuccess,
	FinalDelay:    30 * time.Second,
}

var scenarioFailImmediate = settlementScenario{
	Name:          "fail_immediate",
	InitialStatus: storagemodel.PaymentStatusFailed,
	FailureReason: "simulated_fail_immediate",
}

var scenarioFailTimeout = settlementScenario{
	Name:          "fail_timeout",
	InitialStatus: storagemodel.PaymentStatusWaiting,
	FinalStatus:   storagemodel.PaymentStatusFailed,
	FinalDelay:    45 * time.Second,
	FailureReason: "simulated_fail_timeout",
}

var scenarioStuckPending = settlementScenario{
	Name:          "stuck_pending",
	InitialStatus: storagemodel.PaymentStatusWaiting,
}

var scenarioRetryThenSuccess = settlementScenario{
	Name:          "retry_then_success",
	InitialStatus: storagemodel.PaymentStatusProcessing,
	FinalStatus:   storagemodel.PaymentStatusSuccess,
	FinalDelay:    25 * time.Second,
}

var scenarioWebhookDelayedSuccess = settlementScenario{
	Name:          "webhook_delayed_success",
	InitialStatus: storagemodel.PaymentStatusWaiting,
	FinalStatus:   storagemodel.PaymentStatusSuccess,
	FinalDelay:    60 * time.Second,
}

var scenarioSlowThenFail = settlementScenario{
	Name:          "slow_then_fail",
	InitialStatus: storagemodel.PaymentStatusProcessing,
	FinalStatus:   storagemodel.PaymentStatusFailed,
	FinalDelay:    75 * time.Second,
	FailureReason: "simulated_slow_then_fail",
}

var scenarioPartialProgressStuck = settlementScenario{
	Name:          "partial_progress_stuck",
	InitialStatus: storagemodel.PaymentStatusProcessing,
}

func resolveSettlementScenario(idempotencyKey string, amount *paymenttypes.Money, metadata map[string]string) settlementScenario {
	scenario, _ := resolveSettlementScenarioWithTrace(idempotencyKey, amount, metadata)
	return scenario
}

func resolveSettlementScenarioWithTrace(idempotencyKey string, amount *paymenttypes.Money, metadata map[string]string) (settlementScenario, settlementScenarioTrace) {
	trace := settlementScenarioTrace{
		BucketSlot: -1,
	}
	if amount != nil {
		trace.AmountRaw = strings.TrimSpace(amount.Amount)
		trace.AmountCurrency = strings.TrimSpace(amount.Currency)
	}
	overrideScenario, overrideRaw, overrideNormalized, overrideApplied := parseScenarioOverride(metadata)
	if overrideRaw != "" {
		trace.OverrideRaw = overrideRaw
		trace.OverrideNormalized = overrideNormalized
	}
	if overrideApplied {
		trace.Source = "explicit_override"
		return overrideScenario, trace
	}
	slot, ok := amountModuloSlot(amount)
	if ok {
		if trace.OverrideRaw != "" {
			trace.Source = "invalid_override_amount_bucket"
		} else {
			trace.Source = "amount_bucket"
		}
		trace.BucketSlot = slot
		return scenarioBySlot(slot, idempotencyKey), trace
	}
	slot = hashModulo(idempotencyKey, 1000)
	if trace.OverrideRaw != "" {
		trace.Source = "invalid_override_idempotency_hash_bucket"
	} else {
		trace.Source = "idempotency_hash_bucket"
	}
	trace.BucketSlot = slot
	return scenarioBySlot(slot, idempotencyKey), trace
}

func parseScenarioOverride(metadata map[string]string) (settlementScenario, string, string, bool) {
	if len(metadata) == 0 {
		return settlementScenario{}, "", "", false
	}
	overrideRaw := strings.TrimSpace(metadata[scenarioMetadataKey])
	if overrideRaw == "" {
		overrideRaw = strings.TrimSpace(metadata[scenarioMetadataAliasKey])
	}
	if overrideRaw == "" {
		return settlementScenario{}, "", "", false
	}
	scenario, normalized, ok := scenarioByName(overrideRaw)
	return scenario, overrideRaw, normalized, ok
}

func scenarioByName(value string) (settlementScenario, string, bool) {
	key := normalizeScenarioName(value)
	switch key {
	case "fast_success", "success_fast", "instant_success":
		return scenarioFastSuccess, key, true
	case "slow_success", "success_slow":
		return scenarioSlowSuccess, key, true
	case "fail_immediate", "immediate_fail", "failed":
		return scenarioFailImmediate, key, true
	case "fail_timeout", "timeout_fail":
		return scenarioFailTimeout, key, true
	case "stuck", "stuck_pending", "pending_stuck":
		return scenarioStuckPending, key, true
	case "retry_then_success":
		return scenarioRetryThenSuccess, key, true
	case "webhook_delayed_success":
		return scenarioWebhookDelayedSuccess, key, true
	case "slow_then_fail":
		return scenarioSlowThenFail, key, true
	case "partial_progress_stuck":
		return scenarioPartialProgressStuck, key, true
	case "chaos", "chaos_random_seeded":
		return scenarioBySlot(950, ""), key, true
	default:
		return settlementScenario{}, key, false
	}
}

func normalizeScenarioName(value string) string {
	key := strings.ToLower(strings.TrimSpace(value))
	key = strings.ReplaceAll(key, "-", "_")
	return key
}

func scenarioBySlot(slot int, seed string) settlementScenario {
	switch {
	case slot < 100:
		return scenarioFastSuccess
	case slot < 200:
		return scenarioSlowSuccess
	case slot < 300:
		return scenarioFailImmediate
	case slot < 400:
		return scenarioFailTimeout
	case slot < 500:
		return scenarioStuckPending
	case slot < 600:
		return scenarioRetryThenSuccess
	case slot < 700:
		return scenarioWebhookDelayedSuccess
	case slot < 800:
		return scenarioSlowThenFail
	case slot < 900:
		return scenarioPartialProgressStuck
	default:
		return chaosScenario(seed)
	}
}

func chaosScenario(seed string) settlementScenario {
	choices := []settlementScenario{
		scenarioFastSuccess,
		scenarioSlowSuccess,
		scenarioFailImmediate,
		scenarioFailTimeout,
		scenarioStuckPending,
		scenarioSlowThenFail,
	}
	idx := hashModulo(seed, len(choices))
	return choices[idx]
}

func amountModuloSlot(amount *paymenttypes.Money) (int, bool) {
	if amount == nil {
		return 0, false
	}
	raw := strings.TrimSpace(amount.Amount)
	if raw == "" {
		return 0, false
	}
	sign := 1
	raw = strings.TrimPrefix(raw, "+")
	if strings.HasPrefix(raw, "-") {
		sign = -1
		raw = strings.TrimPrefix(raw, "-")
	}
	parts := strings.SplitN(raw, ".", 3)
	if len(parts) == 0 || len(parts) > 2 {
		return 0, false
	}
	whole := parts[0]
	if whole == "" || !digitsOnly(whole) {
		return 0, false
	}
	frac := "00"
	if len(parts) == 2 {
		f := parts[1]
		if f == "" || !digitsOnly(f) {
			return 0, false
		}
		if len(f) >= 2 {
			frac = f[:2]
		} else {
			frac = f + "0"
		}
	}
	wholeMod := digitsMod(whole, 10)
	fracVal, _ := strconv.Atoi(frac)
	slot := (wholeMod*100 + fracVal) % 1000
	if sign < 0 {
		slot = (-slot + 1000) % 1000
	}
	return slot, true
}

func digitsOnly(value string) bool {
	if value == "" {
		return false
	}
	for i := 0; i < len(value); i++ {
		if value[i] < '0' || value[i] > '9' {
			return false
		}
	}
	return true
}

func digitsMod(value string, mod int) int {
	if mod <= 0 {
		return 0
	}
	result := 0
	for i := 0; i < len(value); i++ {
		digit := int(value[i] - '0')
		result = (result*10 + digit) % mod
	}
	return result
}

func hashModulo(input string, mod int) int {
	if mod <= 0 {
		return 0
	}
	h := fnv.New32a()
	_, _ = h.Write([]byte(strings.TrimSpace(input)))
	return int(h.Sum32()) % mod
}

func (s settlementScenario) delayedTransitionEnabled() bool {
	return s.FinalStatus != "" && s.FinalDelay > 0
}
