package repository

import (
	"errors"
	"strings"
	"time"

	"hyapp-admin-server/internal/model"

	mysqlDriver "github.com/go-sql-driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

var (
	ErrHostAgencySalaryPolicyActiveScopeConflict = errors.New("host agency salary policy active scope conflict")
	ErrHostAgencySalaryPolicyConcurrentUpdate    = errors.New("host agency salary policy was updated concurrently")
)

type HostAgencySalaryPolicyListOptions struct {
	AppCode               string
	Keyword               string
	PolicyType            string
	RegionID              int64
	CycleKey              string
	Status                string
	SettlementMode        string
	SettlementTriggerMode string
	Page                  int
	PageSize              int
}

func (s *Store) ListHostAgencySalaryPolicies(options HostAgencySalaryPolicyListOptions) ([]model.HostAgencySalaryPolicy, int64, error) {
	// 工资政策是应用隔离配置，所有查询都必须先限定 app_code，再叠加运营筛选条件。
	db := s.db.Model(&model.HostAgencySalaryPolicy{}).
		Where("app_code = ?", strings.TrimSpace(options.AppCode))
	if options.RegionID > 0 {
		// 区域筛选走关联表索引；一份政策可覆盖多区，不能再只检查兼容用的主表 region_id。
		db = db.Joins("JOIN admin_host_agency_salary_policy_regions policy_region ON policy_region.policy_id = admin_host_agency_salary_policies.id").
			Where("policy_region.region_id = ?", options.RegionID)
	}
	if policyType := strings.TrimSpace(options.PolicyType); policyType != "" {
		db = db.Where("policy_type = ?", policyType)
	}
	if cycleKey := strings.TrimSpace(options.CycleKey); cycleKey != "" {
		db = db.Where("cycle_key = ?", cycleKey)
	}
	if status := strings.TrimSpace(options.Status); status != "" {
		db = db.Where("status = ?", status)
	}
	if mode := strings.TrimSpace(options.SettlementMode); mode != "" {
		db = db.Where("settlement_mode = ?", mode)
	}
	if triggerMode := strings.TrimSpace(options.SettlementTriggerMode); triggerMode != "" {
		// 触发方式是自动任务和人工结算入口的分界，列表需要能直接筛出需要人工处理的政策。
		db = db.Where("settlement_trigger_mode = ?", triggerMode)
	}
	if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
		like := "%" + keyword + "%"
		db = db.Where("name LIKE ? OR description LIKE ?", like, like)
	}

	var total int64
	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	page, pageSize := normalizePage(options.Page, options.PageSize)
	var items []model.HostAgencySalaryPolicy
	// 显式字段选择只能放在 Count 之后；GORM 会把既有 Select 改写为 COUNT(选择项)，
	// 提前写成 table.* 会生成 MySQL 不支持的 COUNT(table.*)。取数阶段仍限定主表字段，避免 JOIN 区域表污染模型映射。
	err := db.Select("admin_host_agency_salary_policies.*").Preload("Levels", func(tx *gorm.DB) *gorm.DB {
		return tx.Order("level_no ASC")
	}).Order("admin_host_agency_salary_policies.region_id ASC, admin_host_agency_salary_policies.cycle_key DESC, admin_host_agency_salary_policies.policy_version DESC, admin_host_agency_salary_policies.id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&items).Error
	if err != nil {
		return nil, 0, err
	}
	itemRefs := make([]*model.HostAgencySalaryPolicy, len(items))
	for index := range items {
		itemRefs[index] = &items[index]
	}
	if err := s.loadHostAgencySalaryPolicyRegions(s.db, itemRefs); err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

func (s *Store) GetHostAgencySalaryPolicy(appCode string, id uint) (model.HostAgencySalaryPolicy, error) {
	var item model.HostAgencySalaryPolicy
	// 单条读取也预加载等级，更新时会用完整等级包替换原明细。
	err := s.db.Preload("Levels", func(tx *gorm.DB) *gorm.DB {
		return tx.Order("level_no ASC")
	}).Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).First(&item).Error
	if err != nil {
		return item, err
	}
	if err := s.loadHostAgencySalaryPolicyRegions(s.db, []*model.HostAgencySalaryPolicy{&item}); err != nil {
		return model.HostAgencySalaryPolicy{}, err
	}
	return item, nil
}

// ListHostAgencySalaryPolicyRegionIDsForCycle 返回目标月已经被任意显式或自动政策占用的区域。
// region_ids 来自单份政策的有界集合，查询走关联表 (region_id, policy_id) 主键/索引，不扫描工资或钱包流水。
func (s *Store) ListHostAgencySalaryPolicyRegionIDsForCycle(appCode string, cycleKey string, regionIDs []int64) ([]int64, error) {
	if len(regionIDs) == 0 {
		return nil, nil
	}
	var covered []int64
	err := s.db.Model(&model.HostAgencySalaryPolicyRegion{}).
		Distinct("admin_host_agency_salary_policy_regions.region_id").
		Joins("JOIN admin_host_agency_salary_policies policy ON policy.id = admin_host_agency_salary_policy_regions.policy_id").
		Where("policy.app_code = ? AND policy.cycle_key = ? AND admin_host_agency_salary_policy_regions.region_id IN ?", strings.TrimSpace(appCode), strings.TrimSpace(cycleKey), regionIDs).
		Order("admin_host_agency_salary_policy_regions.region_id ASC").
		Pluck("admin_host_agency_salary_policy_regions.region_id", &covered).Error
	return covered, err
}

// ListPublishedActiveHostAgencySalaryPoliciesForCycle 返回可安全继承的完整不可变版本。
// 只复制已成功发布且仍启用的政策，避免把草稿或失败配置悄悄带入新工资月。
func (s *Store) ListPublishedActiveHostAgencySalaryPoliciesForCycle(appCode string, cycleKey string) ([]model.HostAgencySalaryPolicy, error) {
	var items []model.HostAgencySalaryPolicy
	err := s.db.Preload("Levels", func(tx *gorm.DB) *gorm.DB {
		return tx.Order("level_no ASC")
	}).Where(
		"app_code = ? AND cycle_key = ? AND status = ? AND publish_status = ?",
		strings.TrimSpace(appCode), strings.TrimSpace(cycleKey), "active", "published",
	).Order("id ASC").Find(&items).Error
	if err != nil {
		return nil, err
	}
	refs := make([]*model.HostAgencySalaryPolicy, len(items))
	for index := range items {
		refs[index] = &items[index]
	}
	if err := s.loadHostAgencySalaryPolicyRegions(s.db, refs); err != nil {
		return nil, err
	}
	return items, nil
}

func (s *Store) CreateHostAgencySalaryPolicy(item *model.HostAgencySalaryPolicy) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		// GORM 创建主表时不让它自动级联 levels；先拿到 policy_id，再批量写明细，约束更直观。
		levels := append([]model.HostAgencySalaryLevel(nil), item.Levels...)
		item.Levels = nil
		if err := tx.Create(item).Error; err != nil {
			return err
		}
		// 主键在同一事务内作为不可变版本号；它全局唯一，也让旧数据可以无歧义回填 policy_version=id。
		item.PolicyVersion = uint64(item.ID)
		if err := tx.Model(item).UpdateColumn("policy_version", item.PolicyVersion).Error; err != nil {
			return err
		}
		if err := replaceHostAgencySalaryPolicyRegions(tx, item.ID, item.RegionIDs); err != nil {
			return err
		}
		if err := replaceHostAgencySalaryPolicyActiveScopes(tx, item); err != nil {
			return err
		}
		for index := range levels {
			levels[index].PolicyID = item.ID
		}
		if len(levels) > 0 {
			if err := tx.Create(&levels).Error; err != nil {
				return err
			}
		}
		// 恢复内存对象，service 可以把创建后的完整对象直接转成响应。
		item.Levels = levels
		return nil
	})
}

func (s *Store) UpdateHostAgencySalaryPolicy(item *model.HostAgencySalaryPolicy, expectedPolicyVersion uint64) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		// 同一已发布政策可在当前月生成新版本；先锁主行并校验调用方读到的版本，避免两个运营提交互相覆盖配置和等级。
		var locked model.HostAgencySalaryPolicy
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Select("id", "policy_version").
			Where("app_code = ? AND id = ?", item.AppCode, item.ID).First(&locked).Error; err != nil {
			return err
		}
		if locked.PolicyVersion != expectedPolicyVersion {
			return ErrHostAgencySalaryPolicyConcurrentUpdate
		}
		// 等级是整包配置，不做逐条 diff；主表保存成功后删除旧明细，再插入新梯度。
		levels := append([]model.HostAgencySalaryLevel(nil), item.Levels...)
		item.Levels = nil
		if err := tx.Save(item).Error; err != nil {
			return err
		}
		if err := replaceHostAgencySalaryPolicyRegions(tx, item.ID, item.RegionIDs); err != nil {
			return err
		}
		if err := replaceHostAgencySalaryPolicyActiveScopes(tx, item); err != nil {
			return err
		}
		if err := tx.Where("policy_id = ?", item.ID).Delete(&model.HostAgencySalaryLevel{}).Error; err != nil {
			return err
		}
		for index := range levels {
			levels[index].ID = 0
			levels[index].PolicyID = item.ID
		}
		if len(levels) > 0 {
			if err := tx.Create(&levels).Error; err != nil {
				return err
			}
		}
		// 重新挂回最新明细，保证返回给前端的是本次提交后的等级集。
		item.Levels = levels
		return nil
	})
}

func (s *Store) UpdateHostAgencySalaryPolicyPublishState(appCode string, id uint, status string, publishError string, publishedAtMS int64, actorID uint) error {
	// 发布状态属于 admin 配置到 wallet 运行快照的同步结果，不影响政策主配置字段和等级明细。
	return s.db.Model(&model.HostAgencySalaryPolicy{}).
		Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).
		Updates(map[string]any{
			"publish_status":        strings.TrimSpace(status),
			"publish_error":         strings.TrimSpace(publishError),
			"published_at_ms":       publishedAtMS,
			"published_by_admin_id": actorID,
			"updated_by_admin_id":   actorID,
		}).Error
}

func (s *Store) UpdateHostAgencySalaryPolicyPublishFailureIfNotPublished(appCode string, id uint, publishError string, actorID uint) error {
	// wallet 提交与 admin 状态回写跨库，失败回写必须带状态条件：并发成功发布已经写入 published 后，
	// 较晚返回的重复发布错误不能把真实成功状态覆盖成 failed；published_at_ms 保留上次成功时间，表明该政策身份已经发布过。
	return s.db.Model(&model.HostAgencySalaryPolicy{}).
		Where("app_code = ? AND id = ? AND publish_status <> ?", strings.TrimSpace(appCode), id, "published").
		Updates(map[string]any{
			"publish_status":        "failed",
			"publish_error":         strings.TrimSpace(publishError),
			"published_by_admin_id": actorID,
			"updated_by_admin_id":   actorID,
		}).Error
}

func (s *Store) DeleteHostAgencySalaryPolicy(appCode string, id uint) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		// 先删明细再删主表，兼容没有外键级联的部署环境。
		if err := tx.Where("policy_id = ?", id).Delete(&model.HostAgencySalaryPolicyActiveScope{}).Error; err != nil {
			return err
		}
		if err := tx.Where("policy_id = ?", id).Delete(&model.HostAgencySalaryPolicyRegion{}).Error; err != nil {
			return err
		}
		if err := tx.Where("policy_id = ?", id).Delete(&model.HostAgencySalaryLevel{}).Error; err != nil {
			return err
		}
		return tx.Where("app_code = ? AND id = ?", strings.TrimSpace(appCode), id).Delete(&model.HostAgencySalaryPolicy{}).Error
	})
}

func (s *Store) HasActiveHostAgencySalaryPolicyForCycle(appCode string, regionID int64, cycleKey string, excludeID uint) (bool, error) {
	// 工资政策以周期为唯一选择边界；关联表索引先按区域收敛，再由主表校验 App、周期和状态。
	db := s.db.Model(&model.HostAgencySalaryPolicy{}).
		Joins("JOIN admin_host_agency_salary_policy_regions policy_region ON policy_region.policy_id = admin_host_agency_salary_policies.id").
		Where("admin_host_agency_salary_policies.app_code = ? AND policy_region.region_id = ? AND admin_host_agency_salary_policies.cycle_key = ? AND admin_host_agency_salary_policies.status = ?", strings.TrimSpace(appCode), regionID, strings.TrimSpace(cycleKey), "active")
	if excludeID > 0 {
		db = db.Where("id <> ?", excludeID)
	}
	var count int64
	if err := db.Count(&count).Error; err != nil {
		return false, err
	}
	return count > 0, nil
}

func (s *Store) loadHostAgencySalaryPolicyRegions(db *gorm.DB, items []*model.HostAgencySalaryPolicy) error {
	if len(items) == 0 {
		return nil
	}
	policyIDs := make([]uint, 0, len(items))
	byPolicyID := make(map[uint]int, len(items))
	for index := range items {
		policyIDs = append(policyIDs, items[index].ID)
		byPolicyID[items[index].ID] = index
	}
	var rows []model.HostAgencySalaryPolicyRegion
	// 映射表按 policy_id 主键前缀批量读取，列表页只额外增加一次有界查询，避免 N+1。
	if err := db.Where("policy_id IN ?", policyIDs).Order("policy_id ASC, region_id ASC").Find(&rows).Error; err != nil {
		return err
	}
	for _, row := range rows {
		if index, ok := byPolicyID[row.PolicyID]; ok {
			items[index].RegionIDs = append(items[index].RegionIDs, row.RegionID)
		}
	}
	for index := range items {
		if len(items[index].RegionIDs) == 0 && items[index].RegionID > 0 {
			items[index].RegionIDs = []int64{items[index].RegionID}
		}
	}
	return nil
}

func replaceHostAgencySalaryPolicyRegions(tx *gorm.DB, policyID uint, regionIDs []int64) error {
	if err := tx.Where("policy_id = ?", policyID).Delete(&model.HostAgencySalaryPolicyRegion{}).Error; err != nil {
		return err
	}
	rows := make([]model.HostAgencySalaryPolicyRegion, 0, len(regionIDs))
	nowMS := time.Now().UTC().UnixMilli()
	for _, regionID := range regionIDs {
		rows = append(rows, model.HostAgencySalaryPolicyRegion{PolicyID: policyID, RegionID: regionID, CreatedAtMS: nowMS})
	}
	return tx.Create(&rows).Error
}

func replaceHostAgencySalaryPolicyActiveScopes(tx *gorm.DB, item *model.HostAgencySalaryPolicy) error {
	// 先释放当前政策旧占位；删除锁会一直持有到事务提交，后续任一新区域冲突都会让整个事务回滚，
	// 因而 active->disabled、区域变更和创建都不会暴露半套占位。
	if err := tx.Where("policy_id = ?", item.ID).Delete(&model.HostAgencySalaryPolicyActiveScope{}).Error; err != nil {
		return err
	}
	if strings.TrimSpace(item.Status) != "active" {
		return nil
	}
	nowMS := time.Now().UTC().UnixMilli()
	rows := make([]model.HostAgencySalaryPolicyActiveScope, 0, len(item.RegionIDs))
	for _, regionID := range item.RegionIDs {
		rows = append(rows, model.HostAgencySalaryPolicyActiveScope{
			AppCode: item.AppCode, RegionID: regionID, CycleKey: item.CycleKey, PolicyID: item.ID,
			CreatedAtMS: nowMS, UpdatedAtMS: nowMS,
		})
	}
	if err := tx.Create(&rows).Error; err != nil {
		var mysqlErr *mysqlDriver.MySQLError
		if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
			return ErrHostAgencySalaryPolicyActiveScopeConflict
		}
		return err
	}
	return nil
}
