package wallet

import (
	"encoding/json"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	"math"
	"net/url"
	"strconv"
	"strings"
)

func normalizeCreateExternalRechargeOrderCommand(command ledger.CreateExternalRechargeOrderCommand) ledger.CreateExternalRechargeOrderCommand {
	command.AppCode = appcode.Normalize(command.AppCode)
	command.CommandID = strings.TrimSpace(command.CommandID)
	command.TargetCountryCode = strings.ToUpper(strings.TrimSpace(command.TargetCountryCode))
	command.AudienceType = ledger.NormalizeRechargeAudienceType(command.AudienceType)
	command.ProviderCode = ledger.NormalizePaymentProvider(command.ProviderCode)
	command.ReturnURL = strings.TrimSpace(command.ReturnURL)
	command.NotifyURL = strings.TrimSpace(command.NotifyURL)
	command.ClientIP = strings.TrimSpace(command.ClientIP)
	command.Language = normalizeMifaPayLanguage(command.Language)
	command.PayerName = strings.TrimSpace(command.PayerName)
	command.PayerAccount = strings.TrimSpace(command.PayerAccount)
	command.PayerEmail = strings.TrimSpace(command.PayerEmail)
	return command
}

func normalizeMifaPayLanguage(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return "en"
	}
	if comma := strings.IndexByte(value, ','); comma >= 0 {
		value = value[:comma]
	}
	if semicolon := strings.IndexByte(value, ';'); semicolon >= 0 {
		value = value[:semicolon]
	}
	value = strings.TrimSpace(value)
	if !validMifaPayLanguageTag(value) {
		return "en"
	}

	return value
}

func validMifaPayLanguageTag(value string) bool {
	if value == "" {
		return false
	}
	parts := strings.Split(value, "-")
	if len(parts) == 0 || len(parts) > 4 || len(parts[0]) < 2 || len(parts[0]) > 3 || !allASCIILetters(parts[0]) {
		return false
	}
	for _, part := range parts[1:] {
		if len(part) < 2 || len(part) > 8 || !allASCIILettersOrDigits(part) {
			return false
		}
	}
	return true
}

func allASCIILetters(value string) bool {
	for _, char := range value {
		if (char < 'A' || char > 'Z') && (char < 'a' || char > 'z') {
			return false
		}
	}
	return value != ""
}

func allASCIILettersOrDigits(value string) bool {
	for _, char := range value {
		if (char < 'A' || char > 'Z') && (char < 'a' || char > 'z') && (char < '0' || char > '9') {
			return false
		}
	}
	return value != ""
}

func validateCreateExternalRechargeOrderCommand(command ledger.CreateExternalRechargeOrderCommand) error {
	if command.CommandID == "" || command.TargetUserID <= 0 || command.TargetRegionID <= 0 || command.ProductID <= 0 {
		return xerr.New(xerr.InvalidArgument, "external recharge command is incomplete")
	}
	if command.AudienceType == "" || command.ProviderCode == "" {
		return xerr.New(xerr.InvalidArgument, "external recharge command type is invalid")
	}
	if len(command.CommandID) > 128 {
		return xerr.New(xerr.InvalidArgument, "command_id is too long")
	}
	return nil
}

func validateExternalRechargeProduct(product ledger.RechargeProduct, command ledger.CreateExternalRechargeOrderCommand) error {
	if product.Status != ledger.RechargeProductStatusActive || !product.Enabled {
		return xerr.New(xerr.Conflict, "recharge product is not active")
	}
	if product.Platform != ledger.RechargeProductPlatformWeb {
		return xerr.New(xerr.InvalidArgument, "recharge product is not h5 product")
	}
	if product.AudienceType != command.AudienceType {
		return xerr.New(xerr.Conflict, "recharge product audience does not match")
	}
	if !rechargeProductSupportsRegion(product, command.TargetRegionID) {
		return xerr.New(xerr.Conflict, "recharge product is not available in user region")
	}
	return nil
}

func validateMifaPayMethodForOrder(method ledger.ThirdPartyPaymentMethod, command ledger.CreateExternalRechargeOrderCommand) error {
	if method.ProviderCode != ledger.PaymentProviderMifaPay || method.Status != ledger.ThirdPartyPaymentStatusActive {
		return xerr.New(xerr.Conflict, "mifapay method is not active")
	}
	if method.CountryCode != "GLOBAL" && !strings.EqualFold(method.CountryCode, command.TargetCountryCode) {
		return xerr.New(xerr.Conflict, "mifapay method country does not match")
	}
	if _, err := calculateMifaPayProviderAmountMinor(100, method.USDToCurrencyRate, method.CurrencyCode); err != nil {
		return err
	}
	return nil
}

func validateV5PayMethodForOrder(method ledger.ThirdPartyPaymentMethod, command ledger.CreateExternalRechargeOrderCommand) error {
	if method.ProviderCode != ledger.PaymentProviderV5Pay || method.Status != ledger.ThirdPartyPaymentStatusActive {
		return xerr.New(xerr.Conflict, "v5pay method is not active")
	}
	if method.CountryCode != "GLOBAL" && !strings.EqualFold(method.CountryCode, command.TargetCountryCode) {
		return xerr.New(xerr.Conflict, "v5pay method country does not match")
	}
	if strings.TrimSpace(method.PayType) == "" {
		return xerr.New(xerr.InvalidArgument, "v5pay product type is required")
	}
	if _, err := calculateProviderAmountMinor(100, method.USDToCurrencyRate, method.CurrencyCode); err != nil {
		return err
	}
	return nil
}

func calculateProviderAmountMinor(usdMinor int64, rate string, currencyCode string) (int64, error) {
	if usdMinor <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "usd amount is invalid")
	}
	value, err := strconv.ParseFloat(strings.TrimSpace(rate), 64)
	if err != nil || value <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "payment exchange rate is invalid")
	}
	if providerCurrencyScale(currencyCode) == 0 {
		// MiFaPay still receives a fixed two-decimal integer amount, but zero-decimal currencies
		// such as IDR must represent a whole major-unit amount after the provider divides by 100.
		return int64(math.Round(float64(usdMinor)/100*value)) * 100, nil
	}
	return int64(math.Round(float64(usdMinor) * value)), nil
}

const mifaPayINRMinimumMajorAmount = int64(100)

func calculateMifaPayProviderAmountMinor(usdMinor int64, rate string, currencyCode string) (int64, error) {
	if !mifaPayUsesWholeMajorAmount(currencyCode) {
		return calculateProviderAmountMinor(usdMinor, rate, currencyCode)
	}
	if usdMinor <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "usd amount is invalid")
	}
	value, err := strconv.ParseFloat(strings.TrimSpace(rate), 64)
	if err != nil || value <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "payment exchange rate is invalid")
	}
	majorAmount := int64(math.Round(float64(usdMinor) / 100 * value))
	if strings.EqualFold(currencyCode, "INR") && majorAmount < mifaPayINRMinimumMajorAmount {
		// MiFaPay 印度通道要求 amount 是整数卢比，且当前网关最小金额为 100 INR；
		// 本地仍按 minor 保存 10000，回调和账单都能表达实际向三方提交的收款金额。
		majorAmount = mifaPayINRMinimumMajorAmount
	}
	return majorAmount * 100, nil
}

func mifaPayProviderAmountValue(providerAmountMinor int64, currencyCode string) string {
	if mifaPayUsesWholeMajorAmount(currencyCode) {
		return strconv.FormatInt(providerAmountMinor/100, 10)
	}
	return strconv.FormatInt(providerAmountMinor, 10)
}

func mifaPayUsesWholeMajorAmount(currencyCode string) bool {
	return strings.EqualFold(strings.TrimSpace(currencyCode), "INR")
}

func providerCurrencyScale(currencyCode string) int {
	switch strings.ToUpper(strings.TrimSpace(currencyCode)) {
	case "BIF", "CLP", "DJF", "GNF", "IDR", "JPY", "KMF", "KRW", "MGA", "PYG", "RWF", "UGX", "VND", "VUV", "XAF", "XOF", "XPF":
		return 0
	default:
		return 2
	}
}

func amountMicroToUSDMinor(amountMicro int64) int64 {
	if amountMicro <= 0 {
		return 0
	}
	return amountMicro / 10_000
}

func validNonNegativeDecimal(value string) bool {
	parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
	return err == nil && parsed >= 0
}

func mifaPayNotifyMatchesOrder(data MifaPayNotifyData, order ledger.ExternalRechargeOrder) bool {
	if !strings.EqualFold(data.Currency, order.CurrencyCode) {
		return false
	}
	if data.PayType != "" && !strings.EqualFold(data.PayType, order.PayType) {
		return false
	}
	return strings.TrimSpace(data.Amount) == mifaPayProviderAmountValue(order.ProviderAmountMinor, order.CurrencyCode)
}

func mifaPayQueryMatchesOrder(data MifaPayNotifyData, order ledger.ExternalRechargeOrder) bool {
	if data.OrderID != "" && !strings.EqualFold(data.OrderID, order.OrderID) {
		return false
	}
	if data.Currency != "" && !strings.EqualFold(data.Currency, order.CurrencyCode) {
		return false
	}
	if data.PayType != "" && !strings.EqualFold(data.PayType, order.PayType) {
		return false
	}
	return strings.TrimSpace(data.Amount) == mifaPayProviderAmountValue(order.ProviderAmountMinor, order.CurrencyCode)
}

func v5PayOrderDataMatchesOrder(data V5PayNotifyData, order ledger.ExternalRechargeOrder) bool {
	if data.OrderID != "" && !strings.EqualFold(data.OrderID, order.OrderID) {
		return false
	}
	if data.Currency != "" && !strings.EqualFold(data.Currency, order.CurrencyCode) {
		return false
	}
	if data.ProductType != "" && !strings.EqualFold(data.ProductType, order.PayType) {
		return false
	}
	amount, err := parseProviderAmountMinor(data.Amount)
	return err == nil && amount == order.ProviderAmountMinor
}

func parseProviderAmountMinor(value string) (int64, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0, strconv.ErrSyntax
	}
	if !strings.Contains(value, ".") {
		parsed, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return 0, err
		}
		return parsed * 100, nil
	}
	parts := strings.SplitN(value, ".", 2)
	whole, err := strconv.ParseInt(firstNonEmpty(parts[0], "0"), 10, 64)
	if err != nil {
		return 0, err
	}
	fraction := parts[1]
	if len(fraction) > 2 {
		fraction = fraction[:2]
	}
	for len(fraction) < 2 {
		fraction += "0"
	}
	minor, err := strconv.ParseInt(fraction, 10, 64)
	if err != nil {
		return 0, err
	}
	return whole*100 + minor, nil
}

func mifaPayProviderReturnURL(returnURL string) string {
	returnURL = strings.TrimSpace(returnURL)
	if returnURL == "" {
		return returnURL
	}
	parsed, err := url.Parse(returnURL)
	if err != nil || parsed.Scheme == "" || parsed.Host == "" {
		return returnURL
	}

	parsed.RawQuery = ""
	parsed.Fragment = ""
	return parsed.String()
}

func v5PayProviderReturnURL(returnURL string) string {
	return mifaPayProviderReturnURL(returnURL)
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}
	return ""
}

func mustJSONText(value any) string {
	body, err := json.Marshal(value)
	if err != nil {
		return "{}"
	}
	return string(body)
}
