package service

import (
	"fmt"
	"time"

	"metazone.cc/metalab/internal/common"
	"metazone.cc/metalab/internal/model"

	"gorm.io/gorm"
)

// expAdder EnergyService 对经验增加的最小依赖（ISP）
type expAdder interface {
	AddExp(userID uint, delta int) (newExp int, levelUp bool, err error)
}

// EnergyService 域能业务逻辑
type EnergyService struct {
	db       *gorm.DB
	repo     EnergyStore
	fundRepo FundStore
	expSvc   expAdder
	notifSvc energyNotifier
}

// NewEnergyService 构造函数
func NewEnergyService(db *gorm.DB, repo EnergyStore, expSvc expAdder, notifSvc energyNotifier) *EnergyService {
	return &EnergyService{db: db, repo: repo, expSvc: expSvc, notifSvc: notifSvc}
}

// SetExpAdder 链式注入经验服务（用于赋能获得经验）
func (s *EnergyService) SetExpAdder(svc expAdder) {
	s.expSvc = svc
}

// SetFundRepo 链式注入公户仓储
func (s *EnergyService) SetFundRepo(repo FundStore) {
	s.fundRepo = repo
}

// EnergyInfo 域能信息（含每日赋能经验状态）
type EnergyInfo struct {
	Energy             int  // 域能余额（乘10存储）
	DailyEnergizeExp   int  // 当日已通过赋能获得的经验值
	DailyExpCap        int  // 每日赋能经验上限
	DailyExpCapReached bool // 是否已达上限
}

// 域能相关常量（乘10存储，DB值）
const (
	EnergyCheckIn    = 10   // +1.0 域能
	EnergyRename     = -60  // -6.0 域能
	EnergyRenameRefund = 60 // +6.0 域能
	EnergyDeletePost = -20  // -2.0 域能
	EnergyEnergize1  = 10   // 轻赋 1 域能
	EnergyEnergize2  = 20   // 重赋 2 域能
	EnergizeExpPerEnergy = 10 // 每 1 域能 = 10 经验
	DailyEnergizeExpCap  = 50 // 每日赋能经验上限
	MaxEnergizePerPost   = 20 // 单用户单文章最多赋能 2 域能（×10）
)

// Energize 赋能文章
// 返回：获得的经验值、错误
func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int, error) {
	// 验证赋能量
	if amount != EnergyEnergize1 && amount != EnergyEnergize2 {
		return 0, common.ErrInvalidEnergyAmount
	}

	var expGained int
	var needExpUpdate bool
	var expToday time.Time
	var expTotal int

	err := s.db.Transaction(func(tx *gorm.DB) error {
		txRepo := s.repo.WithTx(tx)
		var txFundRepo FundStore
		if s.fundRepo != nil {
			txFundRepo = s.fundRepo.WithTx(tx)
		}

		// 1. 检查帖子存在，获取作者
		post, err := txRepo.FindPostByID(postID)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				return common.ErrPostNotFound
			}
			return fmt.Errorf("查询帖子失败: %w", err)
		}

		// 2. 不能给自己赋能
		if post.UserID == energizerID {
			return common.ErrCannotEnergizeSelf
		}

		// 3. 检查赋能者域能余额（owner 跳过）
		energizer, err := txRepo.FindUserByID(energizerID)
		if err != nil {
			return fmt.Errorf("查询用户失败: %w", err)
		}
		if energizer.Role != model.RoleOwner && energizer.Energy < amount {
			return common.ErrInsufficientEnergy
		}

		// 4. 检查单用户单文章赋能总量 ≤ MaxEnergizePerPost
		total, err := txRepo.SumUserPostEnergy(energizerID, postID)
		if err != nil {
			return fmt.Errorf("查询赋能记录失败: %w", err)
		}
		if total+amount > MaxEnergizePerPost {
			return common.ErrEnergizeLimitReached
		}

		// 5. 扣赋能者域能
		if err := txRepo.AddEnergy(energizerID, -amount); err != nil {
			return fmt.Errorf("扣减域能失败: %w", err)
		}

		// 6. 增加被赋能者（文章作者）域能（赋能量的十分之一）
		receivedAmount := amount / 10 // 1 域能→0.1，2 域能→0.2
		if err := txRepo.AddEnergy(post.UserID, receivedAmount); err != nil {
			return fmt.Errorf("增加被赋能者域能失败: %w", err)
		}

		// 7. 更新 Post.total_energy_received
		if err := txRepo.IncrPostEnergy(postID, amount); err != nil {
			return fmt.Errorf("更新文章赋能计数失败: %w", err)
		}

		// 8. 插入赋能记录
		eLog := &model.PostEnergizeLog{
			UserID: energizerID,
			PostID: postID,
			Amount: amount,
		}
		if err := txRepo.CreateEnergizeLog(eLog); err != nil {
			return fmt.Errorf("创建赋能记录失败: %w", err)
		}

		// 9. 写入域能流水 ×2
		amountDisplay := float64(amount) / 10
		receivedDisplay := float64(receivedAmount) / 10

		energizerLog := &model.EnergyLog{
			UserID:      energizerID,
			Amount:      -amount,
			Type:        model.EnergyTypeEnergize,
			RelatedType: "post",
			RelatedID:   postID,
			Description: fmt.Sprintf("给文章 %d 赋能 %.1f 域能", postID, amountDisplay),
		}
		if err := txRepo.CreateEnergyLog(energizerLog); err != nil {
			return fmt.Errorf("创建赋能者流水失败: %w", err)
		}

		energizedLog := &model.EnergyLog{
			UserID:      post.UserID,
			Amount:      receivedAmount,
			Type:        model.EnergyTypeEnergized,
			RelatedType: "post",
			RelatedID:   postID,
			Description: fmt.Sprintf("文章 %d 被赋能 +%.1f 域能", postID, receivedDisplay),
		}
		if err := txRepo.CreateEnergyLog(energizedLog); err != nil {
			return fmt.Errorf("创建被赋能者流水失败: %w", err)
		}

		// 10. 赋能税收入公户（差价 = amount - receivedAmount）
		if txFundRepo != nil {
			tax := amount - receivedAmount
			if tax > 0 {
				if err := txFundRepo.IncrBalance(tax); err != nil {
					return fmt.Errorf("公户入账失败: %w", err)
				}
				taxDisplay := float64(tax) / 10
				fundLog := &model.FundLog{
					Amount:      tax,
					Type:        model.FundTypeEnergizeTax,
					RelatedType: "post",
					RelatedID:   postID,
					Description: fmt.Sprintf("赋能文章 #%d 税收 %.1f 域能，赋能者 uid=%d", postID, taxDisplay, energizerID),
				}
				if err := txFundRepo.CreateLog(fundLog); err != nil {
					return fmt.Errorf("创建公户流水失败: %w", err)
				}
			}
		}

		// 11. 计算经验值（溢出截断）—— 在事务内读取以保证一致性
		loc, _ := time.LoadLocation("Asia/Shanghai")
		now := time.Now().In(loc)
		today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)

		summary, err := txRepo.GetDailyEnergizeExp(energizerID, today)
		currentDaily := 0
		if err == nil && summary != nil {
			currentDaily = summary.ExpEarned
		}

		expGain := amount // 1域能(DB10) → 10经验, 2域能(DB20) → 20经验
		expRoom := DailyEnergizeExpCap - currentDaily
		if expRoom < 0 {
			expRoom = 0
		}
		if expGain > expRoom {
			expGain = expRoom
		}

		expGained = expGain
		if expGain > 0 {
			needExpUpdate = true
			expToday = today
			expTotal = currentDaily + expGain
		}
		return nil
	})
	if err != nil {
		return 0, err
	}

	// 12. 事务提交后，在事务外增加经验 + 更新每日汇总
	if needExpUpdate {
		if s.expSvc != nil {
			if _, _, err := s.expSvc.AddExp(energizerID, expGained); err != nil {
				return expGained, fmt.Errorf("赋能成功，但增加经验失败: %w", err)
			}
		}
		newSummary := &model.DailyExpSummary{
			UserID:    energizerID,
			Date:      expToday,
			ExpEarned: expTotal,
		}
		if err := s.repo.UpsertDailyExp(newSummary); err != nil {
			return expGained, fmt.Errorf("赋能成功，但更新经验汇总失败: %w", err)
		}
	}

	return expGained, nil
}

// CheckInEnergy 签到获得域能（LV0 用户不调用此方法）
func (s *EnergyService) CheckInEnergy(userID uint) error {
	if err := s.repo.AddEnergy(userID, EnergyCheckIn); err != nil {
		return fmt.Errorf("增加签到域能失败: %w", err)
	}

	log := &model.EnergyLog{
		UserID:      userID,
		Amount:      EnergyCheckIn,
		Type:        model.EnergyTypeSignIn,
		Description: "每日签到 +1.0 域能",
	}
	return s.repo.CreateEnergyLog(log)
}

// DeductOnRename 改名扣域能（首次改名免费，不调用此方法）
// 事务包裹：扣用户域能 + 入公户 + 写流水
func (s *EnergyService) DeductOnRename(userID uint) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		txRepo := s.repo.WithTx(tx)

		// 检查域能余额（非 owner 需 ≥ 0）
		user, err := txRepo.FindUserByID(userID)
		if err != nil {
			return fmt.Errorf("查询用户失败: %w", err)
		}
		if user.Role != model.RoleOwner && user.Energy+EnergyRename < 0 {
			return common.ErrInsufficientEnergy
		}

		if err := txRepo.AddEnergy(userID, EnergyRename); err != nil {
			return fmt.Errorf("扣减改名域能失败: %w", err)
		}

		// 公户入账
		if s.fundRepo != nil {
			txFundRepo := s.fundRepo.WithTx(tx)
			if err := txFundRepo.IncrBalance(-EnergyRename); err != nil { // EnergyRename = -60，入公户 +60
				return fmt.Errorf("公户入账失败: %w", err)
			}
			fundLog := &model.FundLog{
				Amount:      -EnergyRename,
				Type:        model.FundTypeRenameDeduction,
				RelatedType: "user",
				RelatedID:   userID,
				Description: fmt.Sprintf("用户 uid=%d 改名消耗 6.0 域能", userID),
			}
			if err := txFundRepo.CreateLog(fundLog); err != nil {
				return fmt.Errorf("创建公户流水失败: %w", err)
			}
		}

		log := &model.EnergyLog{
			UserID:      userID,
			Amount:      EnergyRename,
			Type:        model.EnergyTypeRename,
			Description: "改名消耗 6.0 域能",
		}
		return txRepo.CreateEnergyLog(log)
	})
}

// RefundOnRenameReject 改名审核被拒，从公户返还域能
func (s *EnergyService) RefundOnRenameReject(userID uint) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		txRepo := s.repo.WithTx(tx)

		// 从公户支出（允许透支）
		if s.fundRepo != nil {
			txFundRepo := s.fundRepo.WithTx(tx)
			if err := txFundRepo.IncrBalance(-EnergyRenameRefund); err != nil {
				return fmt.Errorf("公户出账失败: %w", err)
			}
			fundLog := &model.FundLog{
				Amount:      -EnergyRenameRefund,
				Type:        model.FundTypeRenameRefund,
				RelatedType: "user",
				RelatedID:   userID,
				Description: fmt.Sprintf("用户 uid=%d 改名审核被拒，返还 6.0 域能", userID),
			}
			if err := txFundRepo.CreateLog(fundLog); err != nil {
				return fmt.Errorf("创建公户流水失败: %w", err)
			}
		}

		if err := txRepo.AddEnergy(userID, EnergyRenameRefund); err != nil {
			return fmt.Errorf("返还改名域能失败: %w", err)
		}

		log := &model.EnergyLog{
			UserID:      userID,
			Amount:      EnergyRenameRefund,
			Type:        model.EnergyTypeRenameRefund,
			Description: "改名审核被拒，返还 6.0 域能",
		}
		return txRepo.CreateEnergyLog(log)
	})
}

// DeductOnDeletePost 删稿扣域能（扣文章作者的域能，无视负数）
// 事务包裹：扣用户域能 + 入公户 + 写流水
func (s *EnergyService) DeductOnDeletePost(authorUserID uint, postID uint) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		txRepo := s.repo.WithTx(tx)

		if err := txRepo.AddEnergy(authorUserID, EnergyDeletePost); err != nil {
			return fmt.Errorf("扣减删稿域能失败: %w", err)
		}

		// 公户入账
		if s.fundRepo != nil {
			txFundRepo := s.fundRepo.WithTx(tx)
			if err := txFundRepo.IncrBalance(-EnergyDeletePost); err != nil { // EnergyDeletePost = -20，入公户 +20
				return fmt.Errorf("公户入账失败: %w", err)
			}
			fundLog := &model.FundLog{
				Amount:      -EnergyDeletePost,
				Type:        model.FundTypeDeletePost,
				RelatedType: "post",
				RelatedID:   postID,
				Description: fmt.Sprintf("用户 uid=%d 删除文章 #%d 消耗 2.0 域能", authorUserID, postID),
			}
			if err := txFundRepo.CreateLog(fundLog); err != nil {
				return fmt.Errorf("创建公户流水失败: %w", err)
			}
		}

		log := &model.EnergyLog{
			UserID:      authorUserID,
			Amount:      EnergyDeletePost,
			Type:        model.EnergyTypeDeletePost,
			RelatedType: "post",
			RelatedID:   postID,
			Description: "删除文章消耗 2.0 域能",
		}
		return txRepo.CreateEnergyLog(log)
	})
}

// AdminAdjust 后台调整用户域能
// mode: "admin_transfer"（受公户余额约束）| "system_operation"（不受约束，不碰公户）
func (s *EnergyService) AdminAdjust(operatorUID uint, userIDs []uint, amount int, description string, mode string) error {
	if mode == "" {
		mode = model.FundTypeAdminTransfer
	}

	return s.db.Transaction(func(tx *gorm.DB) error {
		txRepo := s.repo.WithTx(tx)

		// admin_transfer：公户出账需检查余额（行锁防 TOCTOU 竞态）
		isAdminTransfer := mode == model.FundTypeAdminTransfer
		if isAdminTransfer && amount > 0 && s.fundRepo != nil {
			txFundRepo := s.fundRepo.WithTx(tx)
			totalCost := amount * len(userIDs)
			balance, err := txFundRepo.LockAndGetBalance()
			if err != nil {
				return fmt.Errorf("查询公户余额失败: %w", err)
			}
			if balance < totalCost {
				return common.ErrInsufficientFund
			}
		}

		for _, uid := range userIDs {
			if err := txRepo.AddEnergy(uid, amount); err != nil {
				return fmt.Errorf("调整用户 %d 域能失败: %w", uid, err)
			}

			opUID := operatorUID
			energyLog := &model.EnergyLog{
				UserID:      uid,
				Amount:      amount,
				Type:        model.EnergyTypeAdminAdjust,
				Description: description,
				OperatorUID: &opUID,
			}
			if err := txRepo.CreateEnergyLog(energyLog); err != nil {
				return fmt.Errorf("创建调整流水失败: %w", err)
			}
		}

		// admin_transfer：公户扣款 + 公户流水（system_operation 不碰公户）
		if isAdminTransfer && s.fundRepo != nil {
			txFundRepo := s.fundRepo.WithTx(tx)
			totalCost := amount * len(userIDs)
			if err := txFundRepo.IncrBalance(-totalCost); err != nil {
				return fmt.Errorf("公户余额调整失败: %w", err)
			}

			totalDisplay := float64(totalCost) / 10
			fundLog := &model.FundLog{
				Amount:      -totalCost,
				Type:        mode,
				OperatorUID: &operatorUID,
				Description: fmt.Sprintf("%s：调整 %d 名用户域能 %.1f，说明：%s", getFundModeName(mode), len(userIDs), totalDisplay, description),
			}
			if len(userIDs) > 0 {
				fundLog.RelatedType = "user"
				fundLog.RelatedID = userIDs[0]
			}
			if err := txFundRepo.CreateLog(fundLog); err != nil {
				return fmt.Errorf("创建公户流水失败: %w", err)
			}
		}

		return nil
	})
}

// getFundModeName 返回操作模式中文名
func getFundModeName(mode string) string {
	switch mode {
	case model.FundTypeAdminTransfer:
		return "管理转出"
	case model.FundTypeSystemOperation:
		return "系统操作"
	default:
		return "后台调整"
	}
}

// GetEnergyInfo 获取用户域能余额和每日赋能经验状态
func (s *EnergyService) GetEnergyInfo(userID uint) (*EnergyInfo, error) {
	user, err := s.repo.FindUserByID(userID)
	if err != nil {
		return nil, err
	}

	loc, _ := time.LoadLocation("Asia/Shanghai")
	now := time.Now().In(loc)
	today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)

	dailyExp := 0
	summary, err := s.repo.GetDailyEnergizeExp(userID, today)
	if err == nil && summary != nil {
		dailyExp = summary.ExpEarned
	}

	return &EnergyInfo{
		Energy:             user.Energy,
		DailyEnergizeExp:   dailyExp,
		DailyExpCap:        DailyEnergizeExpCap,
		DailyExpCapReached: dailyExp >= DailyEnergizeExpCap,
	}, nil
}

// GetEnergyLogs 分页查询用户域能流水（近 N 天）
func (s *EnergyService) GetEnergyLogs(userID uint, days, page, pageSize int) ([]model.EnergyLog, int64, error) {
	p := common.Pagination{Page: page, PageSize: pageSize}
	p.DefaultPagination()

	total, err := s.repo.CountEnergyLogsByUser(userID, days)
	if err != nil {
		return nil, 0, err
	}

	logs, err := s.repo.ListEnergyLogsByUser(userID, days, p.Offset(), p.PageSize)
	if err != nil {
		return nil, 0, err
	}
	if logs == nil {
		logs = []model.EnergyLog{}
	}
	return logs, total, nil
}

// GetAdminEnergyLogs 后台查询全部域能流水
func (s *EnergyService) GetAdminEnergyLogs(energyType string, page, pageSize int) ([]model.EnergyLog, int64, error) {
	p := common.Pagination{Page: page, PageSize: pageSize}
	p.DefaultPagination()
	return s.repo.ListAllEnergyLogs(energyType, p.Offset(), p.PageSize)
}

// GetFundBalance 获取公户余额
func (s *EnergyService) GetFundBalance() (int, error) {
	if s.fundRepo == nil {
		return 0, nil
	}
	return s.fundRepo.GetBalance()
}

// GetFundLogs 分页查询公户流水
func (s *EnergyService) GetFundLogs(logType string, page, pageSize int) ([]model.FundLog, int64, error) {
	p := common.Pagination{Page: page, PageSize: pageSize}
	p.DefaultPagination()
	if s.fundRepo == nil {
		return []model.FundLog{}, 0, nil
	}
	logs, total, err := s.fundRepo.ListLogs(logType, p.Offset(), p.PageSize)
	if err != nil {
		return nil, 0, err
	}
	if logs == nil {
		logs = []model.FundLog{}
	}
	return logs, total, nil
}
