package mysql

import (
	"context"
	"database/sql"
	"errors"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	"strconv"
	"strings"
	"time"
)

func (r *Repository) ListThirdPartyPaymentChannels(ctx context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, query.AppCode)
	app := appcode.FromContext(ctx)
	providerCode := strings.ToLower(strings.TrimSpace(query.ProviderCode))
	status := ledger.NormalizeThirdPartyPaymentStatus(query.Status)
	where := `WHERE app_code = ?`
	args := []any{app}
	if providerCode != "" {
		where += ` AND provider_code = ?`
		args = append(args, providerCode)
	}
	if status != "" {
		where += ` AND status = ?`
		args = append(args, status)
	}
	rows, err := r.db.QueryContext(ctx, `
		SELECT app_code, provider_code, provider_name, status, sort_order
		FROM third_party_payment_channels
		`+where+`
		ORDER BY sort_order DESC, provider_code ASC`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	channels := make([]ledger.ThirdPartyPaymentChannel, 0)
	for rows.Next() {
		var channel ledger.ThirdPartyPaymentChannel
		if err := rows.Scan(&channel.AppCode, &channel.ProviderCode, &channel.ProviderName, &channel.Status, &channel.SortOrder); err != nil {
			return nil, err
		}
		channels = append(channels, channel)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	for index := range channels {
		methods, err := r.listThirdPartyPaymentMethods(ctx, app, channels[index].ProviderCode, "", query.IncludeDisabledMethods, false)
		if err != nil {
			return nil, err
		}
		channels[index].Methods = methods
	}
	return channels, nil
}

func (r *Repository) SetThirdPartyPaymentMethodStatus(ctx context.Context, command ledger.ThirdPartyPaymentMethodStatusCommand) (ledger.ThirdPartyPaymentMethod, error) {
	if r == nil || r.db == nil {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	status := ledger.ThirdPartyPaymentStatusDisabled
	if command.Enabled {
		status = ledger.ThirdPartyPaymentStatusActive
	}
	result, err := r.db.ExecContext(ctx, `
		UPDATE third_party_payment_methods
		SET status = ?, updated_at_ms = ?
		WHERE app_code = ? AND method_id = ?`,
		status, time.Now().UnixMilli(), appcode.FromContext(ctx), command.MethodID,
	)
	if err != nil {
		return ledger.ThirdPartyPaymentMethod{}, err
	}
	if affected, err := result.RowsAffected(); err != nil {
		return ledger.ThirdPartyPaymentMethod{}, err
	} else if affected == 0 {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.NotFound, "payment method not found")
	}
	return r.GetThirdPartyPaymentMethod(ctx, appcode.FromContext(ctx), command.MethodID)
}

func (r *Repository) UpdateThirdPartyPaymentRate(ctx context.Context, command ledger.ThirdPartyPaymentRateCommand) (ledger.ThirdPartyPaymentMethod, error) {
	if r == nil || r.db == nil {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, command.AppCode)
	result, err := r.db.ExecContext(ctx, `
		UPDATE third_party_payment_methods
		SET usd_to_currency_rate = ?, updated_at_ms = ?
		WHERE app_code = ? AND method_id = ?`,
		command.USDToCurrencyRate, time.Now().UnixMilli(), appcode.FromContext(ctx), command.MethodID,
	)
	if err != nil {
		return ledger.ThirdPartyPaymentMethod{}, err
	}
	if affected, err := result.RowsAffected(); err != nil {
		return ledger.ThirdPartyPaymentMethod{}, err
	} else if affected == 0 {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.NotFound, "payment method not found")
	}
	return r.GetThirdPartyPaymentMethod(ctx, appcode.FromContext(ctx), command.MethodID)
}

func (r *Repository) UpsertThirdPartyPaymentMethods(ctx context.Context, appCode string, providerCode string, methods []ledger.ThirdPartyPaymentMethod) (int, int, error) {
	if r == nil || r.db == nil {
		return 0, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, appCode)
	app := appcode.FromContext(ctx)
	provider := ledger.NormalizePaymentProvider(providerCode)
	if provider == "" {
		return 0, 0, xerr.New(xerr.InvalidArgument, "payment provider is invalid")
	}
	if len(methods) == 0 {
		return 0, 0, nil
	}
	existing, err := r.loadThirdPartyPaymentMethodKeys(ctx, app, provider)
	if err != nil {
		return 0, 0, err
	}
	nowMS := time.Now().UnixMilli()
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return 0, 0, err
	}
	defer tx.Rollback()

	// 同步按钮只保证渠道行存在；如果运营曾手动关闭渠道，不在同步过程中重新打开。
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO third_party_payment_channels (
		  app_code, provider_code, provider_name, status, sort_order, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, 'active', ?, ?, ?)
		ON DUPLICATE KEY UPDATE provider_name = VALUES(provider_name), updated_at_ms = VALUES(updated_at_ms)`,
		app, provider, thirdPartyPaymentProviderName(provider), thirdPartyPaymentProviderSortOrder(provider), nowMS, nowMS,
	); err != nil {
		return 0, 0, err
	}

	created := 0
	updated := 0
	seen := map[string]struct{}{}
	for _, method := range methods {
		method = normalizeThirdPartyPaymentMethodForUpsert(app, provider, method, nowMS)
		if method.CountryCode == "" || method.CurrencyCode == "" || method.PayWay == "" || method.PayType == "" || method.MethodName == "" {
			continue
		}
		key := thirdPartyPaymentMethodKey(method.CountryCode, method.PayWay, method.PayType)
		if _, duplicate := seen[key]; duplicate {
			continue
		}
		seen[key] = struct{}{}
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO third_party_payment_methods (
			  app_code, provider_code, country_code, country_name, currency_code, pay_way, pay_type,
			  method_name, logo_url, status, usd_to_currency_rate, sort_order, created_at_ms, updated_at_ms
			) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, ?)
			ON DUPLICATE KEY UPDATE
			  country_name = VALUES(country_name),
			  currency_code = VALUES(currency_code),
			  method_name = VALUES(method_name),
			  logo_url = CASE WHEN VALUES(logo_url) <> '' THEN VALUES(logo_url) ELSE logo_url END,
			  sort_order = VALUES(sort_order),
			  updated_at_ms = VALUES(updated_at_ms)`,
			method.AppCode, method.ProviderCode, method.CountryCode, method.CountryName, method.CurrencyCode, method.PayWay, method.PayType,
			method.MethodName, method.LogoURL, method.USDToCurrencyRate, method.SortOrder, method.CreatedAtMS, method.UpdatedAtMS,
		); err != nil {
			return 0, 0, err
		}
		if _, exists := existing[key]; exists {
			updated++
		} else {
			created++
		}
	}
	if err := tx.Commit(); err != nil {
		return 0, 0, err
	}
	return created, updated, nil
}

func (r *Repository) loadThirdPartyPaymentMethodKeys(ctx context.Context, appCode string, providerCode string) (map[string]struct{}, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT country_code, pay_way, pay_type
		FROM third_party_payment_methods
		WHERE app_code = ? AND provider_code = ?`,
		appCode, providerCode,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	keys := map[string]struct{}{}
	for rows.Next() {
		var countryCode string
		var payWay string
		var payType string
		if err := rows.Scan(&countryCode, &payWay, &payType); err != nil {
			return nil, err
		}
		keys[thirdPartyPaymentMethodKey(countryCode, payWay, payType)] = struct{}{}
	}
	return keys, rows.Err()
}

func (r *Repository) GetThirdPartyPaymentMethod(ctx context.Context, appCode string, methodID int64) (ledger.ThirdPartyPaymentMethod, error) {
	ctx = contextWithCommandApp(ctx, appCode)
	row := r.db.QueryRowContext(ctx, thirdPartyPaymentMethodSelectSQL()+`
		WHERE m.app_code = ? AND m.method_id = ?`,
		appcode.FromContext(ctx), methodID,
	)
	method, err := scanThirdPartyPaymentMethod(row)
	if errors.Is(err, sql.ErrNoRows) {
		return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.NotFound, "payment method not found")
	}
	return method, err
}

func (r *Repository) ListH5RechargeOptions(ctx context.Context, query ledger.H5RechargeOptionsQuery) (ledger.H5RechargeOptions, error) {
	if r == nil || r.db == nil {
		return ledger.H5RechargeOptions{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, query.AppCode)
	products, _, err := r.ListRechargeProducts(ctx, query.TargetUserID, query.TargetRegionID, ledger.RechargeProductPlatformWeb, query.AudienceType)
	if err != nil {
		return ledger.H5RechargeOptions{}, err
	}
	methods, err := r.listH5ThirdPartyPaymentMethods(ctx, appcode.FromContext(ctx), strings.ToUpper(strings.TrimSpace(query.TargetCountryCode)))
	if err != nil {
		return ledger.H5RechargeOptions{}, err
	}
	return ledger.H5RechargeOptions{Products: products, PaymentMethods: methods}, nil
}

func (r *Repository) listH5ThirdPartyPaymentMethods(ctx context.Context, appCode string, countryCode string) ([]ledger.ThirdPartyPaymentMethod, error) {
	where := `WHERE m.app_code = ? AND m.status = 'active' AND m.usd_to_currency_rate > 0`
	args := []any{appCode}
	if countryCode != "" {
		where += ` AND m.country_code IN (?, 'GLOBAL')`
		args = append(args, countryCode)
	}
	rows, err := r.db.QueryContext(ctx, thirdPartyPaymentMethodSelectSQL()+`
		`+where+`
		ORDER BY c.sort_order DESC, CASE WHEN m.country_code = 'GLOBAL' THEN 1 ELSE 0 END ASC, m.sort_order DESC, m.method_id ASC`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	methods := make([]ledger.ThirdPartyPaymentMethod, 0)
	for rows.Next() {
		method, err := scanThirdPartyPaymentMethod(rows)
		if err != nil {
			return nil, err
		}
		methods = append(methods, method)
	}
	return methods, rows.Err()
}

func (r *Repository) listThirdPartyPaymentMethods(ctx context.Context, appCode string, providerCode string, countryCode string, includeDisabled bool, requireRate bool) ([]ledger.ThirdPartyPaymentMethod, error) {
	where := `WHERE m.app_code = ? AND m.provider_code = ?`
	args := []any{appCode, providerCode}
	if countryCode != "" {
		where += ` AND m.country_code IN (?, 'GLOBAL')`
		args = append(args, countryCode)
	}
	if !includeDisabled {
		where += ` AND m.status = 'active'`
	}
	if requireRate {
		where += ` AND m.usd_to_currency_rate > 0`
	}
	rows, err := r.db.QueryContext(ctx, thirdPartyPaymentMethodSelectSQL()+`
		`+where+`
		ORDER BY CASE WHEN m.country_code = 'GLOBAL' THEN 1 ELSE 0 END ASC, m.sort_order DESC, m.method_id ASC`, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	methods := make([]ledger.ThirdPartyPaymentMethod, 0)
	for rows.Next() {
		method, err := scanThirdPartyPaymentMethod(rows)
		if err != nil {
			return nil, err
		}
		methods = append(methods, method)
	}
	return methods, rows.Err()
}

func thirdPartyPaymentMethodSelectSQL() string {
	return `
		SELECT m.method_id, m.app_code, m.provider_code, c.provider_name, m.country_code, m.country_name,
		       m.currency_code, m.pay_way, m.pay_type, m.method_name, m.logo_url, m.status,
		       CAST(m.usd_to_currency_rate AS CHAR), m.sort_order, m.created_at_ms, m.updated_at_ms
		FROM third_party_payment_methods m
		JOIN third_party_payment_channels c ON c.app_code = m.app_code AND c.provider_code = m.provider_code
	`
}

func scanThirdPartyPaymentMethod(scanner scanTarget) (ledger.ThirdPartyPaymentMethod, error) {
	var method ledger.ThirdPartyPaymentMethod
	if err := scanner.Scan(
		&method.MethodID,
		&method.AppCode,
		&method.ProviderCode,
		&method.ProviderName,
		&method.CountryCode,
		&method.CountryName,
		&method.CurrencyCode,
		&method.PayWay,
		&method.PayType,
		&method.MethodName,
		&method.LogoURL,
		&method.Status,
		&method.USDToCurrencyRate,
		&method.SortOrder,
		&method.CreatedAtMS,
		&method.UpdatedAtMS,
	); err != nil {
		return ledger.ThirdPartyPaymentMethod{}, err
	}
	return method, nil
}

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

func normalizeThirdPartyPaymentMethodForUpsert(appCode string, providerCode string, method ledger.ThirdPartyPaymentMethod, nowMS int64) ledger.ThirdPartyPaymentMethod {
	method.AppCode = appcode.Normalize(appCode)
	method.ProviderCode = providerCode
	method.CountryCode = strings.ToUpper(strings.TrimSpace(method.CountryCode))
	method.CountryName = strings.TrimSpace(method.CountryName)
	method.CurrencyCode = strings.ToUpper(strings.TrimSpace(method.CurrencyCode))
	method.PayWay = strings.TrimSpace(method.PayWay)
	method.PayType = strings.ToUpper(strings.TrimSpace(method.PayType))
	method.MethodName = strings.TrimSpace(method.MethodName)
	method.LogoURL = strings.TrimSpace(method.LogoURL)
	method.USDToCurrencyRate = strings.TrimSpace(method.USDToCurrencyRate)
	if method.USDToCurrencyRate == "" || !validNonNegativeDecimalForRepository(method.USDToCurrencyRate) {
		method.USDToCurrencyRate = "0"
	}
	if method.Status == "" {
		method.Status = ledger.ThirdPartyPaymentStatusActive
	}
	method.CreatedAtMS = nowMS
	method.UpdatedAtMS = nowMS
	return method
}

func thirdPartyPaymentMethodKey(countryCode string, payWay string, payType string) string {
	return strings.ToUpper(strings.TrimSpace(countryCode)) + "|" +
		strings.ToLower(strings.TrimSpace(payWay)) + "|" +
		strings.ToUpper(strings.TrimSpace(payType))
}

func thirdPartyPaymentProviderName(providerCode string) string {
	switch providerCode {
	case ledger.PaymentProviderV5Pay:
		return "V5Pay"
	case ledger.PaymentProviderMifaPay:
		return "MiFaPay"
	default:
		return providerCode
	}
}

func thirdPartyPaymentProviderSortOrder(providerCode string) int {
	switch providerCode {
	case ledger.PaymentProviderMifaPay:
		return 10
	case ledger.PaymentProviderV5Pay:
		return 20
	default:
		return 100
	}
}

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