package live

import (
	"encoding/json"
	"fmt"
	unitmodel "honoka-chan/internal/model/unit"
	usermodel "honoka-chan/internal/model/user"
	liveschema "honoka-chan/internal/schema/live"
	"honoka-chan/internal/session"
	"honoka-chan/pkg/utils"
	"math"
)

type liveSettingData struct {
	NotesSettingAsset string `xorm:"notes_setting_asset"`
	ARankScore        int    `xorm:"a_rank_score"`
	BRankScore        int    `xorm:"b_rank_score"`
	CRankScore        int    `xorm:"c_rank_score"`
	SRankScore        int    `xorm:"s_rank_score"`
	AcFlag            int    `xorm:"ac_flag"`
	SwingFlag         int    `xorm:"swing_flag"`
}

type leaderSkillData struct {
	AttributeID      int     `xorm:"attribute_id"`
	MainEffectValue  float64 `xorm:"main_effect_value"`
	ExtraEffectValue float64 `xorm:"extra_effect_value"`
	MemberTagID      int     `xorm:"member_tag_id"`
}

type museumBuff struct {
	Smile float64 `xorm:"smile_buff"`
	Pure  float64 `xorm:"pure_buff"`
	Cool  float64 `xorm:"cool_buff"`
}

type accessoryBonus struct {
	Smile float64 `xorm:"smile_max"`
	Pure  float64 `xorm:"pure_max"`
	Cool  float64 `xorm:"cool_max"`
}

type memberTagMatchKey struct {
	UnitTypeID  int
	MemberTagID int
}

type accessoryBonusRow struct {
	AccessoryOwningUserID int     `xorm:"accessory_owning_user_id"`
	Smile                 float64 `xorm:"smile_max"`
	Pure                  float64 `xorm:"pure_max"`
	Cool                  float64 `xorm:"cool_max"`
}

type removableSkillMeta struct {
	UnitRemovableSkillID int     `xorm:"unit_removable_skill_id"`
	EffectRange          int     `xorm:"effect_range"`
	EffectType           int     `xorm:"effect_type"`
	EffectValue          float64 `xorm:"effect_value"`
	FixedValueFlag       int     `xorm:"fixed_value_flag"`
	TargetReferenceType  int     `xorm:"target_reference_type"`
}

func loadLiveSetting(ss *session.Session, difficultyID int) (liveSettingData, error) {
	setting := liveSettingData{}
	sql := `
SELECT notes_setting_asset,
	a_rank_score,
	b_rank_score,
	c_rank_score,
	s_rank_score,
	ac_flag,
	swing_flag
FROM live_setting_m
WHERE live_setting_id IN (
	SELECT live_setting_id
	FROM normal_live_m
	WHERE live_difficulty_id = ?
	UNION ALL
	SELECT live_setting_id
	FROM special_live_m
	WHERE live_difficulty_id = ?
)
	`
	has, err := ss.MainEng.SQL(sql, difficultyID, difficultyID).Get(&setting)
	if err != nil {
		return liveSettingData{}, err
	}
	if !has {
		return liveSettingData{}, fmt.Errorf("live setting not found: %d", difficultyID)
	}
	return setting, nil
}

func loadLiveNotes(notesSettingAsset string) ([]liveschema.NotesList, error) {
	notes := []liveschema.NotesList{}
	noteData := utils.ReadAllText("./assets/serverdata/beatmaps/" + notesSettingAsset)
	err := json.Unmarshal([]byte(noteData), &notes)
	return notes, err
}

func buildRankInfo(setting liveSettingData) []liveschema.RankInfo {
	return []liveschema.RankInfo{
		{Rank: 5, RankMin: 0, RankMax: setting.CRankScore},
		{Rank: 4, RankMin: setting.CRankScore + 1, RankMax: setting.BRankScore},
		{Rank: 3, RankMin: setting.BRankScore + 1, RankMax: setting.ARankScore},
		{Rank: 2, RankMin: setting.ARankScore + 1, RankMax: setting.SRankScore},
		{Rank: 1, RankMin: setting.SRankScore + 1, RankMax: 0},
	}
}

func getMuseumBuff(ss *session.Session) (museumBuff, error) {
	buff := museumBuff{}
	_, err := ss.MainEng.Table("museum_contents_m").
		Select("SUM(smile_buff) AS smile_buff,SUM(pure_buff) AS pure_buff,SUM(cool_buff) AS cool_buff").
		Get(&buff)
	return buff, err
}

func getDeckCenterUnitID(ss *session.Session, deckID int) (int, error) {
	var centerUnitID int
	_, err := ss.UserEng.Table("user_deck_unit").
		Join("LEFT", "user_deck", "user_deck_unit.user_deck_id = user_deck.id").
		Where("user_deck.deck_id = ? AND user_deck.user_id = ? AND user_deck_unit.position = 5", deckID, ss.UserID).
		Cols("user_deck_unit.unit_id").
		Get(&centerUnitID)
	return centerUnitID, err
}

func getLeaderSkillData(ss *session.Session, unitID int) (leaderSkillData, error) {
	data := leaderSkillData{}
	_, err := ss.MainEng.Table("unit_m").
		Join("LEFT", "unit_leader_skill_m", "unit_m.default_leader_skill_id = unit_leader_skill_m.unit_leader_skill_id").
		Join("LEFT", "unit_leader_skill_extra_m", "unit_m.default_leader_skill_id = unit_leader_skill_extra_m.unit_leader_skill_id").
		Where("unit_m.unit_id = ?", unitID).
		Cols(`
			unit_m.attribute_id,
			unit_leader_skill_m.effect_value AS main_effect_value,
			unit_leader_skill_extra_m.effect_value AS extra_effect_value,
			unit_leader_skill_extra_m.member_tag_id
		`).
		Get(&data)
	return data, err
}

func applyAttributeBonus(attributeID int, effectValue, smile, pure, cool float64) (float64, float64, float64) {
	switch attributeID {
	case 1:
		return math.Ceil(smile * (effectValue / 100)), 0, 0
	case 2:
		return 0, math.Ceil(pure * (effectValue / 100)), 0
	case 3:
		return 0, 0, math.Ceil(cool * (effectValue / 100))
	default:
		return 0, 0, 0
	}
}

func matchMemberTag(ss *session.Session, cache map[memberTagMatchKey]bool, unitTypeID, memberTagID int) (bool, error) {
	if memberTagID <= 0 {
		return false, nil
	}

	key := memberTagMatchKey{UnitTypeID: unitTypeID, MemberTagID: memberTagID}
	if matched, ok := cache[key]; ok {
		return matched, nil
	}

	matched, err := ss.MainEng.Table("unit_type_member_tag_m").
		Where("unit_type_id = ? AND member_tag_id = ?", unitTypeID, memberTagID).
		Exist()
	if err != nil {
		return false, err
	}
	cache[key] = matched
	return matched, nil
}

func loadDeckUnitDataMap(ss *session.Session, unitOwningUserIDs []int) (map[int]unitmodel.UnitDataMap, error) {
	unitRows := []unitmodel.UnitDataMap{}
	err := ss.GetBasicUnitInfo().
		In("a.unit_owning_user_id", unitOwningUserIDs).
		Find(&unitRows)
	if err != nil {
		return nil, err
	}

	unitMap := make(map[int]unitmodel.UnitDataMap, len(unitRows))
	for _, row := range unitRows {
		unitMap[row.UnitOwningUserID] = row
	}
	return unitMap, nil
}

func loadAccessoryBonusMap(ss *session.Session, userID int, unitOwningUserIDs []int) (map[int]accessoryBonus, error) {
	wears := []usermodel.UserAccessoryWear{}
	err := ss.UserEng.Table(new(usermodel.UserAccessoryWear)).
		Where("user_id = ?", userID).
		In("unit_owning_user_id", unitOwningUserIDs).
		Find(&wears)
	if err != nil {
		return nil, err
	}
	if len(wears) == 0 {
		return map[int]accessoryBonus{}, nil
	}

	accessoryIDs := make([]int, 0, len(wears))
	unitToAccessoryID := make(map[int]int, len(wears))
	for _, wear := range wears {
		if wear.AccessoryOwningUserID <= 0 {
			continue
		}
		accessoryIDs = append(accessoryIDs, wear.AccessoryOwningUserID)
		unitToAccessoryID[wear.UnitOwningUserID] = wear.AccessoryOwningUserID
	}
	if len(accessoryIDs) == 0 {
		return map[int]accessoryBonus{}, nil
	}

	rows := []accessoryBonusRow{}
	err = ss.MainEng.Table("common_accessory_m").
		Join("LEFT", "accessory_m", "common_accessory_m.accessory_id = accessory_m.accessory_id").
		In("common_accessory_m.accessory_owning_user_id", accessoryIDs).
		Cols("common_accessory_m.accessory_owning_user_id,smile_max,pure_max,cool_max").
		Find(&rows)
	if err != nil {
		return nil, err
	}

	accessoryMap := make(map[int]accessoryBonus, len(rows))
	for _, row := range rows {
		accessoryMap[row.AccessoryOwningUserID] = accessoryBonus{
			Smile: row.Smile,
			Pure:  row.Pure,
			Cool:  row.Cool,
		}
	}

	result := make(map[int]accessoryBonus, len(unitToAccessoryID))
	for unitOwningUserID, accessoryOwningUserID := range unitToAccessoryID {
		if bonus, ok := accessoryMap[accessoryOwningUserID]; ok {
			result[unitOwningUserID] = bonus
		}
	}
	return result, nil
}

func loadDeckRemovableSkillMap(ss *session.Session, userID int, unitOwningUserIDs []int) (map[int][]int, error) {
	rows := []usermodel.UserUnitSkillEquip{}
	err := ss.UserEng.Table(new(usermodel.UserUnitSkillEquip)).
		Where("user_id = ?", userID).
		In("unit_owning_user_id", unitOwningUserIDs).
		Find(&rows)
	if err != nil {
		return nil, err
	}

	skillMap := make(map[int][]int, len(unitOwningUserIDs))
	for _, row := range rows {
		skillMap[row.UnitOwningUserID] = append(skillMap[row.UnitOwningUserID], row.UnitRemovableSkillID)
	}
	return skillMap, nil
}

func loadRemovableSkillMetaMap(ss *session.Session, skillIDs []int) (map[int]removableSkillMeta, error) {
	if len(skillIDs) == 0 {
		return map[int]removableSkillMeta{}, nil
	}

	rows := []removableSkillMeta{}
	err := ss.MainEng.Table("unit_removable_skill_m").
		In("unit_removable_skill_id", skillIDs).
		Cols("unit_removable_skill_id,effect_range,effect_type,effect_value,fixed_value_flag,target_reference_type").
		Find(&rows)
	if err != nil {
		return nil, err
	}

	metaMap := make(map[int]removableSkillMeta, len(rows))
	for _, row := range rows {
		metaMap[row.UnitRemovableSkillID] = row
	}
	return metaMap, nil
}
