package batchmeta

import (
	"encoding/json"
	"strconv"
	"strings"

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

const (
	// AttrPayoutTargets stores serialized payout target descriptors in intent attributes.
	AttrPayoutTargets = "orchestrator.v2.batch_payout_targets"

	MetaPayoutTargetRef   = "orchestrator.v2.payout_target_ref"
	MetaPayoutTargetIndex = "orchestrator.v2.payout_target_index"
	MetaAmount            = "orchestrator.v2.amount"
	MetaCurrency          = "orchestrator.v2.currency"

	MetaCardPan            = "orchestrator.v2.card_pan"
	MetaCardToken          = "orchestrator.v2.card_token"
	MetaCardholder         = "orchestrator.v2.cardholder"
	MetaCardholderSurname  = "orchestrator.v2.cardholder_surname"
	MetaCardExpMonth       = "orchestrator.v2.card_exp_month"
	MetaCardExpYear        = "orchestrator.v2.card_exp_year"
	MetaCardCountry        = "orchestrator.v2.card_country"
	MetaCardMaskedPan      = "orchestrator.v2.card_masked_pan"
	MetaCustomerID         = "orchestrator.v2.customer_id"
	MetaCustomerFirstName  = "orchestrator.v2.customer_first_name"
	MetaCustomerMiddleName = "orchestrator.v2.customer_middle_name"
	MetaCustomerLastName   = "orchestrator.v2.customer_last_name"
	MetaCustomerIP         = "orchestrator.v2.customer_ip"
	MetaCustomerZip        = "orchestrator.v2.customer_zip"
	MetaCustomerCountry    = "orchestrator.v2.customer_country"
	MetaCustomerState      = "orchestrator.v2.customer_state"
	MetaCustomerCity       = "orchestrator.v2.customer_city"
	MetaCustomerAddress    = "orchestrator.v2.customer_address"
)

// PayoutTarget carries one destination-level payout branch payload for batch execution.
type PayoutTarget struct {
	TargetRef string              `json:"target_ref,omitempty"`
	IntentRef []string            `json:"intent_refs,omitempty"`
	Comment   string              `json:"comment,omitempty"`
	Amount    *paymenttypes.Money `json:"amount,omitempty"`
	Card      *model.CardEndpoint `json:"card,omitempty"`
	Customer  *model.Customer     `json:"customer,omitempty"`
}

func EncodePayoutTargets(targets []PayoutTarget) (string, error) {
	norm := normalizeTargets(targets)
	if len(norm) == 0 {
		return "", nil
	}
	data, err := json.Marshal(norm)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func DecodePayoutTargets(raw string) ([]PayoutTarget, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	var targets []PayoutTarget
	if err := json.Unmarshal([]byte(raw), &targets); err != nil {
		return nil, err
	}
	return normalizeTargets(targets), nil
}

func StepMetadataForTarget(target PayoutTarget, index int) map[string]string {
	target = normalizeTarget(target, index)
	out := map[string]string{}

	if ref := strings.TrimSpace(target.TargetRef); ref != "" {
		out[MetaPayoutTargetRef] = ref
	}
	if index >= 0 {
		out[MetaPayoutTargetIndex] = strconv.Itoa(index + 1)
	}
	if target.Amount != nil {
		if amount := strings.TrimSpace(target.Amount.Amount); amount != "" {
			out[MetaAmount] = amount
		}
		if currency := strings.ToUpper(strings.TrimSpace(target.Amount.Currency)); currency != "" {
			out[MetaCurrency] = currency
		}
	}
	if target.Card != nil {
		appendIfNotEmpty(out, MetaCardPan, target.Card.Pan)
		appendIfNotEmpty(out, MetaCardToken, target.Card.Token)
		appendIfNotEmpty(out, MetaCardholder, target.Card.Cardholder)
		appendIfNotEmpty(out, MetaCardholderSurname, target.Card.CardholderSurname)
		appendIfNotEmpty(out, MetaCardCountry, target.Card.Country)
		appendIfNotEmpty(out, MetaCardMaskedPan, target.Card.MaskedPan)
		if target.Card.ExpMonth != 0 {
			out[MetaCardExpMonth] = strconv.FormatUint(uint64(target.Card.ExpMonth), 10)
		}
		if target.Card.ExpYear != 0 {
			out[MetaCardExpYear] = strconv.FormatUint(uint64(target.Card.ExpYear), 10)
		}
	}
	if target.Customer != nil {
		appendIfNotEmpty(out, MetaCustomerID, target.Customer.ID)
		appendIfNotEmpty(out, MetaCustomerFirstName, target.Customer.FirstName)
		appendIfNotEmpty(out, MetaCustomerMiddleName, target.Customer.MiddleName)
		appendIfNotEmpty(out, MetaCustomerLastName, target.Customer.LastName)
		appendIfNotEmpty(out, MetaCustomerIP, target.Customer.IP)
		appendIfNotEmpty(out, MetaCustomerZip, target.Customer.Zip)
		appendIfNotEmpty(out, MetaCustomerCountry, target.Customer.Country)
		appendIfNotEmpty(out, MetaCustomerState, target.Customer.State)
		appendIfNotEmpty(out, MetaCustomerCity, target.Customer.City)
		appendIfNotEmpty(out, MetaCustomerAddress, target.Customer.Address)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func AmountFromMetadata(metadata map[string]string) (*paymenttypes.Money, bool) {
	if len(metadata) == 0 {
		return nil, false
	}
	amount := strings.TrimSpace(metadata[MetaAmount])
	currency := strings.ToUpper(strings.TrimSpace(metadata[MetaCurrency]))
	if amount == "" || currency == "" {
		return nil, false
	}
	return &paymenttypes.Money{Amount: amount, Currency: currency}, true
}

func CardFromMetadata(metadata map[string]string) (*model.CardEndpoint, bool) {
	if len(metadata) == 0 {
		return nil, false
	}
	card := &model.CardEndpoint{
		Pan:               strings.TrimSpace(metadata[MetaCardPan]),
		Token:             strings.TrimSpace(metadata[MetaCardToken]),
		Cardholder:        strings.TrimSpace(metadata[MetaCardholder]),
		CardholderSurname: strings.TrimSpace(metadata[MetaCardholderSurname]),
		Country:           strings.ToUpper(strings.TrimSpace(metadata[MetaCardCountry])),
		MaskedPan:         strings.TrimSpace(metadata[MetaCardMaskedPan]),
	}
	if parsed, ok := parseUint32(metadata[MetaCardExpMonth]); ok {
		card.ExpMonth = parsed
	}
	if parsed, ok := parseUint32(metadata[MetaCardExpYear]); ok {
		card.ExpYear = parsed
	}
	if card.Pan == "" && card.Token == "" && card.Cardholder == "" &&
		card.CardholderSurname == "" && card.ExpMonth == 0 && card.ExpYear == 0 &&
		card.Country == "" && card.MaskedPan == "" {
		return nil, false
	}
	return card, true
}

func CustomerFromMetadata(metadata map[string]string) (*model.Customer, bool) {
	if len(metadata) == 0 {
		return nil, false
	}
	customer := &model.Customer{
		ID:         strings.TrimSpace(metadata[MetaCustomerID]),
		FirstName:  strings.TrimSpace(metadata[MetaCustomerFirstName]),
		MiddleName: strings.TrimSpace(metadata[MetaCustomerMiddleName]),
		LastName:   strings.TrimSpace(metadata[MetaCustomerLastName]),
		IP:         strings.TrimSpace(metadata[MetaCustomerIP]),
		Zip:        strings.TrimSpace(metadata[MetaCustomerZip]),
		Country:    strings.ToUpper(strings.TrimSpace(metadata[MetaCustomerCountry])),
		State:      strings.TrimSpace(metadata[MetaCustomerState]),
		City:       strings.TrimSpace(metadata[MetaCustomerCity]),
		Address:    strings.TrimSpace(metadata[MetaCustomerAddress]),
	}
	if customer.ID == "" && customer.FirstName == "" && customer.MiddleName == "" &&
		customer.LastName == "" && customer.IP == "" && customer.Zip == "" &&
		customer.Country == "" && customer.State == "" && customer.City == "" &&
		customer.Address == "" {
		return nil, false
	}
	return customer, true
}

func normalizeTargets(targets []PayoutTarget) []PayoutTarget {
	if len(targets) == 0 {
		return nil
	}
	out := make([]PayoutTarget, 0, len(targets))
	for i := range targets {
		target := normalizeTarget(targets[i], i)
		if target.TargetRef == "" && target.Amount == nil && target.Card == nil && target.Customer == nil {
			continue
		}
		out = append(out, target)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func normalizeTarget(target PayoutTarget, index int) PayoutTarget {
	target.TargetRef = strings.TrimSpace(target.TargetRef)
	if target.TargetRef == "" {
		target.TargetRef = "target-" + strconv.Itoa(index+1)
	}
	target.IntentRef = normalizeStringSlice(target.IntentRef)
	target.Comment = strings.TrimSpace(target.Comment)
	if target.Amount != nil {
		amount := strings.TrimSpace(target.Amount.Amount)
		currency := strings.ToUpper(strings.TrimSpace(target.Amount.Currency))
		if amount == "" || currency == "" {
			target.Amount = nil
		} else {
			target.Amount = &paymenttypes.Money{
				Amount:   amount,
				Currency: currency,
			}
		}
	}
	if target.Card != nil {
		target.Card = &model.CardEndpoint{
			Pan:               strings.TrimSpace(target.Card.Pan),
			Token:             strings.TrimSpace(target.Card.Token),
			Cardholder:        strings.TrimSpace(target.Card.Cardholder),
			CardholderSurname: strings.TrimSpace(target.Card.CardholderSurname),
			ExpMonth:          target.Card.ExpMonth,
			ExpYear:           target.Card.ExpYear,
			Country:           strings.ToUpper(strings.TrimSpace(target.Card.Country)),
			MaskedPan:         strings.TrimSpace(target.Card.MaskedPan),
		}
	}
	if target.Customer != nil {
		target.Customer = &model.Customer{
			ID:         strings.TrimSpace(target.Customer.ID),
			FirstName:  strings.TrimSpace(target.Customer.FirstName),
			MiddleName: strings.TrimSpace(target.Customer.MiddleName),
			LastName:   strings.TrimSpace(target.Customer.LastName),
			IP:         strings.TrimSpace(target.Customer.IP),
			Zip:        strings.TrimSpace(target.Customer.Zip),
			Country:    strings.ToUpper(strings.TrimSpace(target.Customer.Country)),
			State:      strings.TrimSpace(target.Customer.State),
			City:       strings.TrimSpace(target.Customer.City),
			Address:    strings.TrimSpace(target.Customer.Address),
		}
	}
	return target
}

func normalizeStringSlice(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	seen := make(map[string]struct{}, len(values))
	out := make([]string, 0, len(values))
	for i := range values {
		token := strings.TrimSpace(values[i])
		if token == "" {
			continue
		}
		if _, exists := seen[token]; exists {
			continue
		}
		seen[token] = struct{}{}
		out = append(out, token)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func appendIfNotEmpty(dst map[string]string, key string, value string) {
	if dst == nil {
		return
	}
	trimmed := strings.TrimSpace(value)
	if trimmed == "" {
		return
	}
	dst[key] = trimmed
}

func parseUint32(raw string) (uint32, bool) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return 0, false
	}
	parsed, err := strconv.ParseUint(trimmed, 10, 32)
	if err != nil {
		return 0, false
	}
	return uint32(parsed), true
}
