package taskcenter

import (
	"bindbox-game/internal/pkg/logger"
	"bindbox-game/internal/repository/mysql"
	"bindbox-game/internal/repository/mysql/dao"
	tcmodel "bindbox-game/internal/repository/mysql/task_center"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	titlesvc "bindbox-game/internal/service/title"
	usersvc "bindbox-game/internal/service/user"

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

type Service interface {
	ListTasks(ctx context.Context, in ListTasksInput) (items []TaskItem, total int64, err error)
	CreateTask(ctx context.Context, in CreateTaskInput) (int64, error)
	ModifyTask(ctx context.Context, id int64, in ModifyTaskInput) error
	DeleteTask(ctx context.Context, id int64) error
	ListTaskTiers(ctx context.Context, taskID int64) ([]TaskTierItem, error)
	UpsertTaskTiers(ctx context.Context, taskID int64, tiers []TaskTierInput) error
	ListTaskRewards(ctx context.Context, taskID int64) ([]TaskRewardItem, error)
	UpsertTaskRewards(ctx context.Context, taskID int64, rewards []TaskRewardInput) error
	GetUserProgress(ctx context.Context, userID int64, taskID int64) (*UserProgress, error)
	ClaimTier(ctx context.Context, userID int64, taskID int64, tierID int64) error
	OnOrderPaid(ctx context.Context, userID int64, orderID int64) error
	OnInviteSuccess(ctx context.Context, inviterID int64, inviteeID int64) error
}

type service struct {
	logger   logger.CustomLogger
	readDB   *dao.Query
	writeDB  *dao.Query
	repo     mysql.Repo
	userSvc  usersvc.Service
	titleSvc titlesvc.Service
}

func New(l logger.CustomLogger, db mysql.Repo) Service {
	return &service{
		logger:   l,
		readDB:   dao.Use(db.GetDbR()),
		writeDB:  dao.Use(db.GetDbW()),
		repo:     db,
		userSvc:  usersvc.New(l, db),
		titleSvc: titlesvc.New(l, db),
	}
}

type ListTasksInput struct {
	Page     int
	PageSize int
}

type TaskItem struct {
	ID          int64
	Name        string
	Description string
	Status      int32
	StartTime   int64
	EndTime     int64
	Visibility  int32
	Tiers       []TaskTierItem
	Rewards     []TaskRewardItem
}

type UserProgress struct {
	TaskID       int64
	UserID       int64
	OrderCount   int64
	InviteCount  int64
	FirstOrder   bool
	ClaimedTiers []int64
}

type CreateTaskInput struct {
	Name        string
	Description string
	Status      int32
	StartTime   *time.Time
	EndTime     *time.Time
	Visibility  int32
}

type ModifyTaskInput struct {
	Name        string
	Description string
	Status      int32
	StartTime   *time.Time
	EndTime     *time.Time
	Visibility  int32
}

type TaskTierInput struct {
	Metric     string
	Operator   string
	Threshold  int64
	Window     string
	Repeatable int32
	Priority   int32
}

type TaskTierItem struct {
	ID         int64  `json:"id"`
	Metric     string `json:"metric"`
	Operator   string `json:"operator"`
	Threshold  int64  `json:"threshold"`
	Window     string `json:"window"`
	Repeatable int32  `json:"repeatable"`
	Priority   int32  `json:"priority"`
}

type TaskRewardInput struct {
	TierID        int64          `json:"tier_id"`
	RewardType    string         `json:"reward_type"`
	RewardPayload datatypes.JSON `json:"reward_payload"`
	Quantity      int64          `json:"quantity"`
}

type TaskRewardItem struct {
	ID            int64          `json:"id"`
	TierID        int64          `json:"tier_id"`
	RewardType    string         `json:"reward_type"`
	RewardPayload datatypes.JSON `json:"reward_payload"`
	Quantity      int64          `json:"quantity"`
}

func (s *service) ListTasks(ctx context.Context, in ListTasksInput) (items []TaskItem, total int64, err error) {
	db := s.repo.GetDbR()
	var rows []tcmodel.Task
	q := db.Model(&tcmodel.Task{})
	if in.PageSize <= 0 {
		in.PageSize = 20
	}
	if in.Page <= 0 {
		in.Page = 1
	}
	if err = q.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if err = q.Offset((in.Page - 1) * in.PageSize).Limit(in.PageSize).Order("id desc").Find(&rows).Error; err != nil {
		return nil, 0, err
	}
	items = make([]TaskItem, len(rows))
	for i, v := range rows {
		var st, et int64
		if v.StartTime != nil {
			st = v.StartTime.Unix()
		}
		if v.EndTime != nil {
			et = v.EndTime.Unix()
		}
		items[i] = TaskItem{ID: v.ID, Name: v.Name, Description: v.Description, Status: v.Status, StartTime: st, EndTime: et, Visibility: v.Visibility}
		// 填充 Tiers
		if tiers, err := s.ListTaskTiers(ctx, v.ID); err == nil {
			items[i].Tiers = tiers
		}
		// 填充 Rewards
		if rewards, err := s.ListTaskRewards(ctx, v.ID); err == nil {
			items[i].Rewards = rewards
		}
	}
	return items, total, nil
}

func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int64) (*UserProgress, error) {
	db := s.repo.GetDbR()
	var row tcmodel.UserTaskProgress
	if err := db.Where("user_id=? AND task_id=?", userID, taskID).First(&row).Error; err != nil {
		return &UserProgress{TaskID: taskID, UserID: userID, ClaimedTiers: []int64{}}, nil
	}
	var claimed []int64
	if len(row.ClaimedTiers) > 0 {
		_ = json.Unmarshal([]byte(row.ClaimedTiers), &claimed)
	}
	return &UserProgress{TaskID: taskID, UserID: userID, OrderCount: row.OrderCount, InviteCount: row.InviteCount, FirstOrder: row.FirstOrder == 1, ClaimedTiers: claimed}, nil
}

func (s *service) ClaimTier(ctx context.Context, userID int64, taskID int64, tierID int64) error {
	return s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
		var p tcmodel.UserTaskProgress
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id=? AND task_id=?", userID, taskID).First(&p).Error; err != nil {
			return errors.New("progress_not_found")
		}
		var claimed []int64
		if len(p.ClaimedTiers) > 0 {
			_ = json.Unmarshal([]byte(p.ClaimedTiers), &claimed)
		}
		for _, id := range claimed {
			if id == tierID {
				return nil
			}
		}
		claimed = append(claimed, tierID)
		b, _ := json.Marshal(claimed)
		p.ClaimedTiers = datatypes.JSON(b)
		return tx.Model(&tcmodel.UserTaskProgress{}).Where("id=?", p.ID).Update("claimed_tiers", p.ClaimedTiers).Error
	})
}

func (s *service) CreateTask(ctx context.Context, in CreateTaskInput) (int64, error) {
	db := s.repo.GetDbW()
	row := &tcmodel.Task{Name: in.Name, Description: in.Description, Status: in.Status, StartTime: in.StartTime, EndTime: in.EndTime, Visibility: in.Visibility}
	if err := db.Create(row).Error; err != nil {
		return 0, err
	}
	return row.ID, nil
}

func (s *service) ModifyTask(ctx context.Context, id int64, in ModifyTaskInput) error {
	db := s.repo.GetDbW()
	return db.Model(&tcmodel.Task{}).Where("id=?", id).Updates(map[string]any{"name": in.Name, "description": in.Description, "status": in.Status, "start_time": in.StartTime, "end_time": in.EndTime, "visibility": in.Visibility}).Error
}

func (s *service) DeleteTask(ctx context.Context, id int64) error {
	db := s.repo.GetDbW()
	return db.Where("id=?", id).Delete(&tcmodel.Task{}).Error
}

func (s *service) ListTaskTiers(ctx context.Context, taskID int64) ([]TaskTierItem, error) {
	db := s.repo.GetDbR()
	var rows []tcmodel.TaskTier
	if err := db.Where("task_id=?", taskID).Order("priority asc, id asc").Find(&rows).Error; err != nil {
		return nil, err
	}
	out := make([]TaskTierItem, len(rows))
	for i, v := range rows {
		out[i] = TaskTierItem{ID: v.ID, Metric: v.Metric, Operator: v.Operator, Threshold: v.Threshold, Window: v.Window, Repeatable: v.Repeatable, Priority: v.Priority}
	}
	return out, nil
}

func (s *service) UpsertTaskTiers(ctx context.Context, taskID int64, tiers []TaskTierInput) error {
	db := s.repo.GetDbW()
	if err := db.Where("task_id=?", taskID).Delete(&tcmodel.TaskTier{}).Error; err != nil {
		return err
	}
	for _, t := range tiers {
		row := &tcmodel.TaskTier{TaskID: taskID, Metric: t.Metric, Operator: t.Operator, Threshold: t.Threshold, Window: t.Window, Repeatable: t.Repeatable, Priority: t.Priority}
		if err := db.Create(row).Error; err != nil {
			return err
		}
	}
	return nil
}

func (s *service) ListTaskRewards(ctx context.Context, taskID int64) ([]TaskRewardItem, error) {
	db := s.repo.GetDbR()
	var rows []tcmodel.TaskReward
	if err := db.Where("task_id=?", taskID).Order("id asc").Find(&rows).Error; err != nil {
		return nil, err
	}
	out := make([]TaskRewardItem, len(rows))
	for i, v := range rows {
		out[i] = TaskRewardItem{ID: v.ID, TierID: v.TierID, RewardType: v.RewardType, RewardPayload: v.RewardPayload, Quantity: v.Quantity}
	}
	return out, nil
}

func (s *service) UpsertTaskRewards(ctx context.Context, taskID int64, rewards []TaskRewardInput) error {
	db := s.repo.GetDbW()
	if err := db.Where("task_id=?", taskID).Delete(&tcmodel.TaskReward{}).Error; err != nil {
		return err
	}
	for _, r := range rewards {
		row := &tcmodel.TaskReward{TaskID: taskID, TierID: r.TierID, RewardType: r.RewardType, RewardPayload: r.RewardPayload, Quantity: r.Quantity}
		if err := db.Create(row).Error; err != nil {
			return err
		}
	}
	return nil
}

func (s *service) OnOrderPaid(ctx context.Context, userID int64, orderID int64) error {
	var tasks []tcmodel.Task
	if err := s.repo.GetDbR().Where("status=1").Find(&tasks).Error; err != nil {
		return err
	}
	for _, t := range tasks {
		var p tcmodel.UserTaskProgress
		err := s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
			if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id=? AND task_id=?", userID, t.ID).First(&p).Error; err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					p = tcmodel.UserTaskProgress{UserID: userID, TaskID: t.ID, OrderCount: 1, FirstOrder: 1}
					return tx.Create(&p).Error
				}
				return err
			}
			if err := s.checkAndResetDailyProgress(ctx, tx, t.ID, &p); err != nil {
				return err
			}
			p.OrderCount++
			if p.OrderCount == 1 {
				p.FirstOrder = 1
			}
			return tx.Save(&p).Error
		})
		if err != nil {
			return err
		}
		if err := s.matchAndGrant(ctx, &t, &p, "order", orderID, fmt.Sprintf("ord:%d", orderID)); err != nil {
			return err
		}
	}
	return nil
}

func (s *service) OnInviteSuccess(ctx context.Context, inviterID int64, inviteeID int64) error {
	var tasks []tcmodel.Task
	if err := s.repo.GetDbR().Where("status=1").Find(&tasks).Error; err != nil {
		return err
	}
	for _, t := range tasks {
		var p tcmodel.UserTaskProgress
		err := s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
			if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("user_id=? AND task_id=?", inviterID, t.ID).First(&p).Error; err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					p = tcmodel.UserTaskProgress{UserID: inviterID, TaskID: t.ID, InviteCount: 1}
					return tx.Create(&p).Error
				}
				return err
			}
			if err := s.checkAndResetDailyProgress(ctx, tx, t.ID, &p); err != nil {
				return err
			}
			p.InviteCount++
			return tx.Save(&p).Error
		})
		if err != nil {
			return err
		}
		if err := s.matchAndGrant(ctx, &t, &p, "invite", inviteeID, fmt.Sprintf("inv:%d", inviteeID)); err != nil {
			return err
		}
	}
	return nil
}

func (s *service) checkAndResetDailyProgress(ctx context.Context, tx *gorm.DB, taskID int64, p *tcmodel.UserTaskProgress) error {
	var count int64
	if err := tx.Model(&tcmodel.TaskTier{}).Where("task_id = ? AND window = ?", taskID, "daily").Count(&count).Error; err != nil {
		return err
	}
	if count == 0 {
		return nil
	}
	now := time.Now()
	if p.UpdatedAt.IsZero() {
		return nil
	}
	y1, m1, d1 := p.UpdatedAt.Date()
	y2, m2, d2 := now.Date()
	if y1 == y2 && m1 == m2 && d1 == d2 {
		return nil
	}
	p.OrderCount = 0
	p.InviteCount = 0
	p.FirstOrder = 0
	p.ClaimedTiers = datatypes.JSON("[]")
	return nil
}

func (s *service) matchAndGrant(ctx context.Context, t *tcmodel.Task, p *tcmodel.UserTaskProgress, sourceType string, sourceID int64, eventID string) error {
	var tiers []tcmodel.TaskTier
	if err := s.repo.GetDbR().Where("task_id=?", t.ID).Order("priority asc").Find(&tiers).Error; err != nil {
		return err
	}
	var claimed []int64
	if len(p.ClaimedTiers) > 0 {
		_ = json.Unmarshal([]byte(p.ClaimedTiers), &claimed)
	}
	claimedSet := map[int64]struct{}{}
	for _, id := range claimed {
		claimedSet[id] = struct{}{}
	}
	for _, tier := range tiers {
		if _, ok := claimedSet[tier.ID]; ok {
			continue
		}
		hit := false
		switch tier.Metric {
		case "first_order":
			hit = p.FirstOrder == 1
		case "order_count":
			if tier.Operator == ">=" {
				hit = p.OrderCount >= tier.Threshold
			} else {
				hit = p.OrderCount == tier.Threshold
			}
		case "invite_count":
			if tier.Operator == ">=" {
				hit = p.InviteCount >= tier.Threshold
			} else {
				hit = p.InviteCount == tier.Threshold
			}
		}
		if !hit {
			continue
		}
		if err := s.grantTierRewards(ctx, t.ID, tier.ID, p.UserID, sourceType, sourceID, eventID); err != nil {
			return err
		}

		// 安全更新状态
		err := s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
			var latestP tcmodel.UserTaskProgress
			if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id=?", p.ID).First(&latestP).Error; err != nil {
				return err
			}
			var latestClaimed []int64
			if len(latestP.ClaimedTiers) > 0 {
				_ = json.Unmarshal([]byte(latestP.ClaimedTiers), &latestClaimed)
			}
			for _, id := range latestClaimed {
				if id == tier.ID {
					return nil
				}
			}
			latestClaimed = append(latestClaimed, tier.ID)
			b, _ := json.Marshal(latestClaimed)
			latestP.ClaimedTiers = datatypes.JSON(b)
			return tx.Model(&latestP).Update("claimed_tiers", latestP.ClaimedTiers).Error
		})
		if err != nil {
			return err
		}
	}
	return nil
}

func (s *service) grantTierRewards(ctx context.Context, taskID int64, tierID int64, userID int64, sourceType string, sourceID int64, eventID string) error {
	var rewards []tcmodel.TaskReward
	if err := s.repo.GetDbR().Where("task_id=? AND tier_id=?", taskID, tierID).Find(&rewards).Error; err != nil {
		return err
	}
	idk := fmt.Sprintf("%d:%d:%d:%s:%d", userID, taskID, tierID, sourceType, sourceID)
	var exists tcmodel.TaskEventLog
	if err := s.repo.GetDbR().Where("idempotency_key=?", idk).First(&exists).Error; err == nil && exists.ID > 0 {
		return nil
	}
	tx := s.repo.GetDbW().Begin()
	for _, r := range rewards {
		var err error
		switch r.RewardType {
		case "points":
			var pl struct{ Points int64 }
			_ = json.Unmarshal([]byte(r.RewardPayload), &pl)
			if pl.Points != 0 {
				err = s.userSvc.AddPoints(ctx, userID, pl.Points, "", "task_center", nil, nil)
			}
		case "coupon":
			var pl struct {
				CouponID int64
				Quantity int
			}
			_ = json.Unmarshal([]byte(r.RewardPayload), &pl)
			if pl.CouponID > 0 {
				qty := 1
				if pl.Quantity > 0 {
					qty = pl.Quantity
				}
				for i := 0; i < qty; i++ {
					if err = s.userSvc.AddCoupon(ctx, userID, pl.CouponID); err != nil {
						break
					}
				}
			}
		case "item_card":
			var pl struct {
				CardID   int64
				Quantity int
			}
			_ = json.Unmarshal([]byte(r.RewardPayload), &pl)
			if pl.CardID > 0 {
				if pl.Quantity <= 0 {
					pl.Quantity = 1
				}
				err = s.userSvc.AddItemCard(ctx, userID, pl.CardID, pl.Quantity)
			}
		case "title":
			var pl struct{ TitleID int64 }
			_ = json.Unmarshal([]byte(r.RewardPayload), &pl)
			if pl.TitleID > 0 {
				err = s.titleSvc.AssignUserTitle(ctx, userID, pl.TitleID, nil, "task_center")
			}
		}
		if err != nil {
			tx.Rollback()
			return err
		}
	}
	if err := tx.Create(&tcmodel.TaskEventLog{EventID: eventID, SourceType: sourceType, SourceID: sourceID, UserID: userID, TaskID: taskID, TierID: tierID, IdempotencyKey: idk, Status: "granted"}).Error; err != nil {
		tx.Rollback()
		return err
	}
	return tx.Commit().Error
}
