package repository

import (
	"errors"
	"sort"
	"strconv"
	"strings"
	"time"

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

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type MoneyAccess struct {
	UserID uint
	All    bool
	Scopes []model.UserMoneyScope
}

func (access MoneyAccess) Allows(appCode string, regionID int64) bool {
	if access.All {
		return true
	}
	appCode = appctx.Normalize(appCode)
	for _, scope := range access.Scopes {
		if appctx.Normalize(scope.AppCode) != appCode {
			continue
		}
		if scope.RegionID == 0 || RegionIDLooseEqual(scope.RegionID, regionID) {
			return true
		}
	}
	return false
}

// RegionIDLooseEqual 对齐 legacy 大区 ID 的浮点圆整误差：雪花/合成 ID 超出 JS 安全整数，
// 浏览器写回的值是 JS Number 的"最短可往返十进制"（如 …690882 → …691000），与
// int64(float64(x)) 的回转值（…690880）都不相等，唯一可靠的等价层是 float64 本身：
// 只要两个十进制落在同一个 float64 桶里就视为同一区域（相邻 ID 间距远大于桶宽，不会误碰）。
func RegionIDLooseEqual(left int64, right int64) bool {
	if left == right {
		return true
	}
	if left == 0 || right == 0 {
		return false
	}
	return float64(left) == float64(right)
}

func (access MoneyAccess) AppCodes() []string {
	if access.All {
		return nil
	}
	set := map[string]struct{}{}
	for _, scope := range access.Scopes {
		appCode := appctx.Normalize(scope.AppCode)
		if appCode != "" {
			set[appCode] = struct{}{}
		}
	}
	out := make([]string, 0, len(set))
	for appCode := range set {
		out = append(out, appCode)
	}
	sort.Strings(out)
	return out
}

// AllowsApp 用于没有 region 维度的资金事实（例如用户提现）：任一归属该 App 的财务范围都可查看和审核该 App 申请。
func (access MoneyAccess) AllowsApp(appCode string) bool {
	if access.All {
		return true
	}
	appCode = appctx.Normalize(appCode)
	for _, allowed := range access.AppCodes() {
		if allowed == appCode {
			return true
		}
	}
	return false
}

func (s *Store) MoneyAccessForUser(userID uint) (MoneyAccess, error) {
	if userID == 0 {
		return MoneyAccess{All: true}, nil
	}
	var user model.User
	if err := s.db.Preload("Roles").First(&user, userID).Error; err != nil {
		return MoneyAccess{}, err
	}
	scopes, err := s.ListUserMoneyScopes(userID)
	if err != nil {
		return MoneyAccess{}, err
	}
	if len(scopes) == 0 && userHasRole(user, "platform-admin") {
		return MoneyAccess{UserID: userID, All: true}, nil
	}
	return MoneyAccess{UserID: userID, Scopes: scopes}, nil
}

func (s *Store) ListUserMoneyScopes(userID uint) ([]model.UserMoneyScope, error) {
	var scopes []model.UserMoneyScope
	err := s.db.Where("user_id = ?", userID).Order("app_code ASC, region_id ASC").Find(&scopes).Error
	return scopes, err
}

func (s *Store) ListAllUserMoneyScopes() ([]model.UserMoneyScope, error) {
	var scopes []model.UserMoneyScope
	err := s.db.Order("app_code ASC, region_id ASC, user_id ASC").Find(&scopes).Error
	return scopes, err
}

func (s *Store) ReplaceUserMoneyScopes(userID uint, scopes []model.UserMoneyScope) error {
	if userID == 0 {
		return errors.New("user id is required")
	}
	normalized, err := normalizeMoneyScopes(userID, scopes)
	if err != nil {
		return err
	}
	return s.db.Transaction(func(tx *gorm.DB) error {
		var user model.User
		if err := tx.First(&user, userID).Error; err != nil {
			return err
		}
		if err := tx.Where("user_id = ?", userID).Delete(&model.UserMoneyScope{}).Error; err != nil {
			return err
		}
		if len(normalized) == 0 {
			return nil
		}
		return tx.Create(&normalized).Error
	})
}

func (s *Store) RecordTemporaryPaymentLinkOwner(owner model.TemporaryPaymentLinkOwner) error {
	owner.AppCode = appctx.Normalize(owner.AppCode)
	owner.OrderID = strings.TrimSpace(owner.OrderID)
	if owner.AppCode == "" || owner.OrderID == "" || owner.AdminUserID == 0 || owner.RegionID <= 0 {
		return errors.New("temporary payment link owner is incomplete")
	}
	nowMS := time.Now().UTC().UnixMilli()
	if owner.CreatedAtMS == 0 {
		owner.CreatedAtMS = nowMS
	}
	owner.UpdatedAtMS = nowMS
	return s.db.Clauses(clause.OnConflict{
		Columns: []clause.Column{{Name: "app_code"}, {Name: "order_id"}},
		DoUpdates: clause.AssignmentColumns([]string{
			"admin_user_id",
			"region_id",
			"updated_at_ms",
		}),
	}).Create(&owner).Error
}

func (s *Store) TemporaryPaymentLinkOwners(appCode string, orderIDs []string) (map[string]model.TemporaryPaymentLinkOwner, error) {
	appCode = appctx.Normalize(appCode)
	ids := uniqueStrings(orderIDs)
	out := make(map[string]model.TemporaryPaymentLinkOwner, len(ids))
	if appCode == "" || len(ids) == 0 {
		return out, nil
	}
	var owners []model.TemporaryPaymentLinkOwner
	if err := s.db.Where("app_code = ? AND order_id IN ?", appCode, ids).Find(&owners).Error; err != nil {
		return nil, err
	}
	for _, owner := range owners {
		out[owner.OrderID] = owner
	}
	return out, nil
}

func (s *Store) FindUsersByIDs(ids []uint) (map[uint]model.User, error) {
	uniqueIDs := uniqueUints(ids)
	out := make(map[uint]model.User, len(uniqueIDs))
	if len(uniqueIDs) == 0 {
		return out, nil
	}
	var users []model.User
	if err := s.db.Where("id IN ?", uniqueIDs).Find(&users).Error; err != nil {
		return nil, err
	}
	for _, user := range users {
		out[user.ID] = user
	}
	return out, nil
}

func normalizeMoneyScopes(userID uint, scopes []model.UserMoneyScope) ([]model.UserMoneyScope, error) {
	seen := map[string]struct{}{}
	out := make([]model.UserMoneyScope, 0, len(scopes))
	for _, scope := range scopes {
		appCode := appctx.Normalize(scope.AppCode)
		if appCode == "" || scope.RegionID < 0 {
			return nil, errors.New("money scope app code and region id are required")
		}
		key := appCode + ":" + strconv.FormatInt(scope.RegionID, 10)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, model.UserMoneyScope{UserID: userID, AppCode: appCode, RegionID: scope.RegionID})
	}
	return out, nil
}

func userHasRole(user model.User, code string) bool {
	for _, role := range user.Roles {
		if role.Code == code {
			return true
		}
	}
	return false
}

func uniqueStrings(values []string) []string {
	set := map[string]struct{}{}
	out := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, ok := set[value]; ok {
			continue
		}
		set[value] = struct{}{}
		out = append(out, value)
	}
	return out
}

func uniqueUints(values []uint) []uint {
	set := map[uint]struct{}{}
	out := make([]uint, 0, len(values))
	for _, value := range values {
		if value == 0 {
			continue
		}
		if _, ok := set[value]; ok {
			continue
		}
		set[value] = struct{}{}
		out = append(out, value)
	}
	return out
}
