package friend

import (
	"errors"
	"honoka-chan/internal/middleware"
	unitmodel "honoka-chan/internal/model/unit"
	usermodel "honoka-chan/internal/model/user"
	"honoka-chan/internal/router"
	friendschema "honoka-chan/internal/schema/friend"
	"honoka-chan/internal/session"
	honokautils "honoka-chan/internal/utils"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
)

type friendListRow struct {
	FriendUserID           int    `xorm:"friend_user_id"`
	Status                 int    `xorm:"status"`
	IsNew                  bool   `xorm:"is_new"`
	InsertDate             int64  `xorm:"insert_date"`
	UpdateDate             int64  `xorm:"update_date"`
	InviteCode             string `xorm:"invite_code"`
	UserName               string `xorm:"user_name"`
	UserLevel              int    `xorm:"user_level"`
	UserDesc               string `xorm:"user_desc"`
	AwardID                int    `xorm:"award_id"`
	LastLoginTime          int64  `xorm:"last_login_time"`
	CenterUnitOwningUserID int    `xorm:"center_unit_owning_user_id"`
}

func list(ctx *gin.Context) {
	ss := session.Get(ctx)
	defer ss.Finalize()

	listReq := friendschema.ListReq{}
	err := honokautils.ParseRequestData(ctx, &listReq)
	if ss.CheckErr(err) {
		return
	}

	friendStatus, err := mapFriendListTypeToStatus(listReq.Type)
	if ss.CheckErr(err) {
		return
	}

	totalCount, err := ss.UserEng.Table(new(usermodel.UserFriend)).
		Where("user_id = ?", ss.UserID).
		Where("status = ?", friendStatus).
		Count()
	if ss.CheckErr(err) {
		return
	}

	page := max(listReq.Page, 0)
	offset := page * usermodel.FriendListPageSize

	rows := []friendListRow{}
	err = ss.UserEng.Table(new(usermodel.UserFriend)).Alias("uf").
		Join("LEFT", "user_pref up", "up.user_id = uf.friend_user_id").
		Join("LEFT", "users u", "u.user_id = uf.friend_user_id").
		Join("LEFT", "user_deck ud", "ud.user_id = uf.friend_user_id AND ud.main_flag = 1").
		Join("LEFT", "user_deck_unit udu", "udu.user_deck_id = ud.id AND udu.position = 5").
		Join("LEFT", "common_unit_data cud", "cud.unit_id = udu.unit_id").
		Where("uf.user_id = ?", ss.UserID).
		Where("uf.status = ?", friendStatus).
		Select(`
			uf.friend_user_id,
			uf.status,
			uf.is_new,
			uf.insert_date,
			uf.update_date,
			up.invite_code,
			up.user_name,
			up.user_level,
			up.user_desc,
			up.award_id,
			u.last_login_time,
			COALESCE(udu.unit_owning_user_id, up.unit_owning_user_id) AS center_unit_owning_user_id
		`).
		OrderBy(friendListOrderBy(listReq.Sort)).
		Limit(usermodel.FriendListPageSize, offset).
		Find(&rows)
	if ss.CheckErr(err) {
		return
	}

	friendList := make([]friendschema.FriendList, 0, len(rows))
	for _, row := range rows {
		item, err := buildFriendListItem(ss, row)
		if ss.CheckErr(err) {
			return
		}
		friendList = append(friendList, item)
	}

	if len(rows) > 0 {
		switch friendStatus {
		case usermodel.FriendStatusApproved, usermodel.FriendStatusAwaitingApproval:
			_, err = ss.UserEng.Table(new(usermodel.UserFriend)).
				Where("user_id = ?", ss.UserID).
				Where("status = ?", friendStatus).
				Where("is_new = ?", true).
				Cols("is_new", "update_date").
				Update(&usermodel.UserFriend{
					IsNew:      false,
					UpdateDate: time.Now().Unix(),
				})
			if ss.CheckErr(err) {
				return
			}
		}
	}

	ss.Respond(friendschema.ListResp{
		ResponseData: friendschema.ListData{
			ItemCount:       totalCount,
			FriendList:      friendList,
			NewFriendList:   []any{},
			ServerTimestamp: time.Now().Unix(),
		},
		ReleaseInfo: []any{},
		StatusCode:  200,
	})
}

func buildFriendListItem(ss *session.Session, row friendListRow) (friendschema.FriendList, error) {
	appliedAt := max(row.UpdateDate, row.InsertDate)

	centerUnitInfo, err := buildCenterUnitInfo(ss, row.FriendUserID, row.CenterUnitOwningUserID, row.AwardID)
	if err != nil {
		return friendschema.FriendList{}, err
	}

	return friendschema.FriendList{
		UserData: friendschema.UserData{
			UserID:                 displayFriendUserID(row.InviteCode, row.FriendUserID),
			Name:                   row.UserName,
			Level:                  row.UserLevel,
			ElapsedTimeFromLogin:   formatElapsedTime(row.LastLoginTime),
			ElapsedTimeFromApplied: formatElapsedTime(appliedAt),
			Comment:                row.UserDesc,
		},
		CenterUnitInfo: centerUnitInfo,
		SettingAwardID: row.AwardID,
		IsNew:          row.IsNew,
	}, nil
}

func buildCenterUnitInfo(ss *session.Session, userID, unitOwningUserID, awardID int) (friendschema.CenterUnitInfo, error) {
	info := friendschema.CenterUnitInfo{
		SettingAwardID:    awardID,
		RemovableSkillIds: []int{},
	}
	if unitOwningUserID <= 0 {
		return info, nil
	}

	unitData := unitmodel.UnitDataMap{}
	has, err := ss.GetBasicUnitInfo().
		Where("a.user_id = ?", userID).
		Where("a.unit_owning_user_id = ?", unitOwningUserID).
		Get(&unitData)
	if err != nil {
		return info, err
	}
	if !has {
		return info, nil
	}

	var accessoryInfo friendschema.AccessoryInfo
	accessoryWear := usermodel.UserAccessoryWear{}
	has, err = ss.UserEng.Table(new(usermodel.UserAccessoryWear)).
		Where("user_id = ? AND unit_owning_user_id = ?", userID, unitOwningUserID).
		Get(&accessoryWear)
	if err != nil {
		return info, err
	}
	if has && accessoryWear.AccessoryOwningUserID > 0 {
		var accessoryID, exp int
		_, err = ss.MainEng.Table("common_accessory_m").
			Where("accessory_owning_user_id = ?", accessoryWear.AccessoryOwningUserID).
			Cols("accessory_id,exp").
			Get(&accessoryID, &exp)
		if err != nil {
			return info, err
		}
		accessoryInfo = friendschema.AccessoryInfo{
			AccessoryOwningUserID: accessoryWear.AccessoryOwningUserID,
			AccessoryID:           accessoryID,
			Exp:                   exp,
			NextExp:               0,
			Level:                 8,
			MaxLevel:              8,
			RankUpCount:           4,
			FavoriteFlag:          true,
		}
	}

	removableSkillIDs := []int{}
	err = ss.UserEng.Table(new(usermodel.UserUnitSkillEquip)).
		Where("user_id = ? AND unit_owning_user_id = ?", userID, unitOwningUserID).
		Cols("unit_removable_skill_id").
		Find(&removableSkillIDs)
	if err != nil {
		return info, err
	}

	info = friendschema.CenterUnitInfo{
		UnitOwningUserID:           int64(unitData.UnitOwningUserID),
		UnitID:                     unitData.UnitID,
		Exp:                        unitData.Exp,
		NextExp:                    0,
		Level:                      unitData.Level,
		LevelLimitID:               unitData.LevelLimitID,
		MaxLevel:                   unitData.MaxLevel,
		Rank:                       unitData.Rank,
		MaxRank:                    unitData.MaxRank,
		Love:                       unitData.Love,
		MaxLove:                    unitData.MaxLove,
		UnitSkillLevel:             unitData.UnitSkillLevel,
		MaxHp:                      unitData.MaxHp,
		FavoriteFlag:               unitData.FavoriteFlag,
		DisplayRank:                unitData.DisplayRank,
		UnitSkillExp:               unitData.UnitSkillExp,
		UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
		Attribute:                  unitData.Attribute,
		Smile:                      unitData.Smile,
		Cute:                       unitData.Cute,
		Cool:                       unitData.Cool,
		IsLoveMax:                  unitData.IsLoveMax,
		IsLevelMax:                 unitData.IsLevelMax,
		IsRankMax:                  unitData.IsRankMax,
		IsSigned:                   unitData.IsSigned,
		IsSkillLevelMax:            unitData.IsSkillLevelMax,
		SettingAwardID:             awardID,
		RemovableSkillIds:          removableSkillIDs,
		AccessoryInfo:              accessoryInfo,
	}

	return info, nil
}

func mapFriendListTypeToStatus(listType int) (int, error) {
	switch listType {
	case 0:
		return usermodel.FriendStatusApproved, nil
	case 1:
		return usermodel.FriendStatusPending, nil
	case 2:
		return usermodel.FriendStatusAwaitingApproval, nil
	default:
		return 0, errors.New("invalid friend list type")
	}
}

func friendListOrderBy(sort int) string {
	switch sort {
	case 1:
		return "up.user_level ASC"
	case 2:
		return "up.user_level DESC"
	case 3:
		return "cud.smile ASC"
	case 4:
		return "cud.smile DESC"
	case 5:
		return "cud.cool ASC"
	case 6:
		return "cud.cool DESC"
	case 7:
		return "cud.cute ASC"
	case 8:
		return "cud.cute DESC"
	case 9:
		return "u.last_login_time ASC"
	case 10:
		return "u.last_login_time DESC"
	case 11:
		return "uf.insert_date ASC"
	case 12:
		return "uf.insert_date DESC"
	default:
		return "uf.insert_date DESC"
	}
}

func displayFriendUserID(inviteCode string, fallback int) int {
	if id, err := strconv.Atoi(inviteCode); err == nil && id > 0 {
		return id
	}
	return fallback
}

func formatElapsedTime(ts int64) string {
	if ts <= 0 {
		return "刚刚"
	}

	delta := time.Since(time.Unix(ts, 0))
	if delta < time.Minute {
		return "刚刚"
	}
	if delta < time.Hour {
		return strconv.Itoa(int(delta.Minutes())) + "分钟前"
	}
	if delta < 24*time.Hour {
		return strconv.Itoa(int(delta.Hours())) + "小时前"
	}
	return strconv.Itoa(int(delta.Hours())/24) + "天前"
}

func init() {
	router.AddHandler("main.php", "POST", "/friend/list", middleware.Common, list)
}
