package financeorder

import (
	"errors"
	"fmt"
	"sort"
	"strings"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/model"
	"hyapp-admin-server/internal/modules/shared"

	"gorm.io/gorm"
)

const financeUSDTAddressGroup = "finance-usdt-address"

var financeUSDTChainOrder = map[string]int{
	"TRON": 0,
	"BSC":  1,
}

type financeUSDTAddressDTO struct {
	Chain       string `json:"chain"`
	Address     string `json:"address"`
	UpdatedAtMS int64  `json:"updatedAtMs"`
}

type financeUSDTAddressesDTO struct {
	AppCode string                  `json:"appCode"`
	Items   []financeUSDTAddressDTO `json:"items"`
}

type financeUSDTAddressUpsertRequest struct {
	Address string `json:"address"`
}

type financeUSDTAddressUpsertDTO struct {
	AppCode     string `json:"appCode"`
	Chain       string `json:"chain"`
	Address     string `json:"address"`
	UpdatedAtMS int64  `json:"updatedAtMs"`
}

// GetUSDTAddresses 只读取当前 App 自己持有的配置行，不继承空 app_code 的历史基线。
// 收款地址一旦跨 App 回退，运营会拿到另一账套的地址并让链上订单校验必然失败。
func (s *Service) GetUSDTAddresses(actor shared.Actor, appCode string) (*financeUSDTAddressesDTO, error) {
	if s == nil || s.store == nil {
		return nil, errors.New("admin store is not configured")
	}
	if actor.UserID == 0 || !hasPermission(actor.Permissions, permissionCoinSellerRechargeView) {
		return nil, errCoinSellerRechargeForbidden
	}
	if strings.TrimSpace(appCode) == "" {
		return nil, errInvalidCoinSellerRechargeOrderInput
	}
	appCode = appctx.Normalize(appCode)
	// App 范围是后台用户的数据边界；即使调用方绕过页面手写 app_code，也不能读取未授权账套地址。
	access, err := s.store.AppAccessForUser(actor.UserID)
	if err != nil {
		return nil, err
	}
	if !access.Allows(appCode) {
		return nil, errCoinSellerRechargeForbidden
	}
	configs, err := s.store.ListOwnedAppConfigs(appCode, financeUSDTAddressGroup)
	if err != nil {
		return nil, err
	}
	items, err := financeUSDTAddressesFromConfigs(configs)
	if err != nil {
		return nil, err
	}
	return &financeUSDTAddressesDTO{AppCode: appCode, Items: items}, nil
}

// UpsertUSDTAddress 把收款地址写入现有 app-scoped 配置表；唯一键确保更新是单行 upsert，
// 不新增地址历史副本，也不会覆盖其他 App。生产查单会在每次校验前重新读取该行。
func (s *Service) UpsertUSDTAddress(actor shared.Actor, appCode string, chain string, req financeUSDTAddressUpsertRequest) (*financeUSDTAddressUpsertDTO, error) {
	if s == nil || s.store == nil {
		return nil, errors.New("admin store is not configured")
	}
	if actor.UserID == 0 || !hasPermission(actor.Permissions, permissionUSDTAddressUpdate) {
		return nil, errCoinSellerRechargeForbidden
	}
	if strings.TrimSpace(appCode) == "" {
		return nil, errInvalidCoinSellerRechargeOrderInput
	}
	appCode = appctx.Normalize(appCode)
	chain = strings.ToUpper(strings.TrimSpace(chain))
	address := strings.TrimSpace(req.Address)
	if _, supported := financeUSDTChainOrder[chain]; !supported {
		return nil, errors.New("USDT 链不正确")
	}
	if !validFinanceUSDTAddress(chain, address) {
		return nil, errors.New("USDT 收款地址不正确")
	}
	access, err := s.store.AppAccessForUser(actor.UserID)
	if err != nil {
		return nil, err
	}
	if !access.Allows(appCode) {
		return nil, errCoinSellerRechargeForbidden
	}
	if err := s.store.UpsertScopedAppConfigs(appCode, []model.AppConfig{{
		Group:       financeUSDTAddressGroup,
		Key:         chain,
		Value:       address,
		Description: "财务 USDT 收款地址",
	}}); err != nil {
		return nil, err
	}
	item, err := s.store.GetOwnedAppConfig(appCode, financeUSDTAddressGroup, chain)
	if err != nil {
		return nil, err
	}
	return &financeUSDTAddressUpsertDTO{AppCode: appCode, Chain: chain, Address: item.Value, UpdatedAtMS: item.UpdatedAtMS}, nil
}

// financeUSDTReceiveAddress 把订单校验链映射到当前 SQL 地址。Off-chain 虽没有公链 hash，
// 但 Binance Deposit History 仍记录实际 TRX 收款地址，因此必须复用 TRON 配置；C2C Pay 则没有地址。
func (s *Service) financeUSDTReceiveAddress(appCode string, receiptChain string) (string, error) {
	addressChain := ""
	switch strings.ToUpper(strings.TrimSpace(receiptChain)) {
	case "TRON":
		addressChain = "TRON"
	case "BSC":
		addressChain = "BSC"
	case "OFFCHAIN":
		addressChain = "TRON"
	case "", "C2C":
		return "", nil
	default:
		return "", errors.New("USDT 链不正确")
	}
	item, err := s.store.GetOwnedAppConfig(appctx.Normalize(appCode), financeUSDTAddressGroup, addressChain)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return "", fmt.Errorf("当前 APP 未配置 %s USDT 收款地址", addressChain)
	}
	if err != nil {
		return "", err
	}
	address := strings.TrimSpace(item.Value)
	if !validFinanceUSDTAddress(addressChain, address) {
		return "", fmt.Errorf("%s USDT 收款地址配置不正确", addressChain)
	}
	return address, nil
}

func financeUSDTAddressesFromConfigs(configs []model.AppConfig) ([]financeUSDTAddressDTO, error) {
	items := make([]financeUSDTAddressDTO, 0, len(configs))
	for _, config := range configs {
		chain := strings.ToUpper(strings.TrimSpace(config.Key))
		if _, supported := financeUSDTChainOrder[chain]; !supported {
			// 同组未来若新增其他链，旧版本必须安全忽略，不能把未知地址误标成 TRON/BSC。
			continue
		}
		address := strings.TrimSpace(config.Value)
		if address == "" {
			continue
		}
		if !validFinanceUSDTAddress(chain, address) {
			return nil, fmt.Errorf("%s USDT 收款地址配置不正确", chain)
		}
		items = append(items, financeUSDTAddressDTO{Chain: chain, Address: address, UpdatedAtMS: config.UpdatedAtMS})
	}
	sort.Slice(items, func(i, j int) bool {
		return financeUSDTChainOrder[items[i].Chain] < financeUSDTChainOrder[items[j].Chain]
	})
	return items, nil
}

func validFinanceUSDTAddress(chain string, address string) bool {
	switch chain {
	case "TRON":
		if len(address) != 34 || address[0] != 'T' {
			return false
		}
		for _, char := range address {
			if !strings.ContainsRune("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", char) {
				return false
			}
		}
		return true
	case "BSC":
		if len(address) != 42 || !strings.HasPrefix(address, "0x") {
			return false
		}
		for _, char := range address[2:] {
			if !strings.ContainsRune("0123456789abcdefABCDEF", char) {
				return false
			}
		}
		return true
	default:
		return false
	}
}
