import BaseModel from './BaseModel';
import { index, getModelForClass, prop, DocumentType, mongoose, Ref } from '@typegoose/typegoose';
import { LadderDefense, LadderOppPlayerInDB } from '../domain/battleField/ladder';
import Role from './Role';

@index({ roleId: 1 })
@index({ serverId: 1, rank: 1 })
export default class LadderMatch extends BaseModel {
    @prop({ required: true, default: 0 })
    serverId: number;					// 角色 id
    @prop({ required: true, default: '' })
    roleId: string;					// 角色 id
    @prop({ ref: 'Role', type: mongoose.Schema.Types.ObjectId })
    role: Ref<Role>;
    @prop({ required: true, default: 0 })
    rank: number;				    // 排名
    @prop({ required: true, default: 0 })
    oldRank: number;				 // 交换前排名
    @prop({ required: true, default: 0 })
    historyRank: number;		    // 历史最高排名

    @prop({ required: true, default: 0 })
    locked: number;  // 是否被挑战

    @prop({ required: true, default: null, _id: false })
    defense: LadderDefense;
    @prop({ required: true, default: false })
    hasDefense: boolean;                    // 购买次数

    @prop({ required: true, type: LadderOppPlayerInDB, default: [], _id: false })
    oppPlayers: LadderOppPlayerInDB[];

    @prop({ required: true, default: () => { return new Date() } })
    refDaily: Date;                  // 每日刷新，控制refOppCnt和setAttackCnt
    @prop({ required: true, default: 0 })
    challengeCnt: number;              // 挑战次数
    @prop({ required: true, default: 0 })
    buyCnt: number;                    // 购买次数
    @prop({ required: true, default: 0 })
    refOppCnt: number;                // 刷新对手次数

    public static async findByRoleId(roleId: string) {
        const result: LadderMatchType = await LadderMatchModel.findOne({ roleId }).lean();
        return result;
    }

    public static async findByRank(serverId: number, rank: number) {
        const result: LadderMatchType = await LadderMatchModel.findOne({ serverId, rank }).lean();
        return result;
    }

    public static async isRankExist(serverId: number, rank: number) {
        const result = await LadderMatchModel.exists({ serverId, rank });
        return result;
    }
    public static async findByRoleIdAndInclude(roleId: string) {
        const result: LadderMatchType = await LadderMatchModel.findOne({ roleId })
            .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt')
            .populate('defense.heroes.hero', 'hid skinId quality star colorStar lv skins job artifact subHid')
            .lean();
        return result;
    }

    public static async createLadder(params: LadderUpdateInter) {
        const doc = new LadderMatchModel();

        const update = Object.assign(doc.toJSON(), params);
        const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ roleId: params.roleId }, { $set: update}, { upsert: true, new: true }).lean();
        return defense;

    }

    public static async updateByRoleId(roleId: string, params: LadderUpdateInter) {
        const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ roleId }, { $set: params}, { new: true }).lean();
        return defense;
    }

    public static async updateByRoleIdAndInclude(roleId: string, params: LadderUpdateInter) {
        const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ roleId }, { $set: params}, { new: true })
        .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt')
        .populate('defense.heroes.hero', 'hid skinId quality star colorStar lv skins job artifact subHid')
        .lean();
        return defense;
    }
    public static async findAll(serverId: number) {
        let result: LadderMatchType[] = [], latestTime = new Date();
        while(latestTime) {
            let ladderMatch = await LadderMatchModel.find({ serverId, rank: { $gt: 0 }, createdAt: { $lt: latestTime } })
                .sort({ createdAt: -1 })
                .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt')
                .limit(1000);
            result.push(...ladderMatch);
            latestTime = ladderMatch[ladderMatch.length - 1]?.createdAt;
        }
        return result
    }

    public static async lock(serverId: number, roleId: string, rank: number) {
        const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ serverId, roleId, rank, locked: 0 }, { $set: { locked: 1 }}, { new: true })
        .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt')
        .populate('defense.heroes.hero', 'hid skinId quality star colorStar lv skins job artifact subHid')
        .lean();
        return defense;
    }

    public static async unlock(serverId: number, roleId: string) {
        const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ serverId, roleId }, { $set: { locked: 0 }}, { new: true }).lean();
        return defense;
    }

    public static async changeRank(isSuccess: boolean, attackInfo: { roleId: string }, defenseInfo: { isRobot: boolean, roleId: string, oldRank: number }, times = 0) {
        if(times > 10) return { isChange: false }
        const session = await mongoose.connection.startSession();
        session.startTransaction()

        try {
    
            let newAtkLadderMatch: LadderMatchType, newDefLadderMatch: LadderMatchType, isChange = false, endTime = Date.now();
            if(defenseInfo.isRobot) {
                let atkLadderMatch = await LadderMatchModel.findOne({ roleId: attackInfo.roleId }).lean();
                if(isSuccess && atkLadderMatch && (atkLadderMatch.rank == 0 || atkLadderMatch.rank > defenseInfo.oldRank)) {
                    newAtkLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: attackInfo.roleId }, { $set: { rank: defenseInfo.oldRank, oldRank: atkLadderMatch.rank } }, { new: true, session }).lean();
                    isChange = true;
                    endTime = Date.now();
                } else {
                    newAtkLadderMatch = atkLadderMatch;
                }
            } else {
                let atkLadderMatch = await LadderMatchModel.findOne({ roleId: attackInfo.roleId }).lean();
                let defLadderMatch = await LadderMatchModel.findOne({ roleId: defenseInfo.roleId }).lean();
                if(isSuccess && atkLadderMatch && defLadderMatch && (atkLadderMatch.rank == 0 || atkLadderMatch.rank > defLadderMatch.rank)) {
                    newAtkLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: attackInfo.roleId }, { $set: { rank: defLadderMatch.rank, oldRank: atkLadderMatch.rank } }, { new: true, session }).lean();
                    newDefLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: defenseInfo.roleId }, { $set: { rank: atkLadderMatch.rank, oldRank: defLadderMatch.rank } }, { new: true, session }).lean();
                    isChange = true;
                    endTime = Date.now();
                } else {
                    newAtkLadderMatch = atkLadderMatch;
                    newDefLadderMatch = defLadderMatch;
                }
            }
            await session.commitTransaction()
            return { isChange, atkLadderMatch: newAtkLadderMatch, defLadderMatch: newDefLadderMatch, endTime }
        } catch(e) {
            return await this.changeRank(isSuccess, attackInfo, defenseInfo, times + 1);
        } finally {
            session.endSession()
        }
    }

    public static async updateCe(roleId: string, hid: number, ce: number) {   
        const rec: LadderMatchType = await LadderMatchModel.findOneAndUpdate(
            { roleId, hasDefense: true }, 
            { $set: { 'defense.heroes.$[t].ce': ce } },
            { arrayFilters: [{"t.actorId": hid}] }).lean();
        return rec
    }

    public static async removeBySub(roleId: string, subHid: number) {   
        await LadderMatchModel.updateMany({ roleId, hasDefense: true }, { $pull: { 'defense.heroes': { actorId: subHid } } });
    }
}

export const LadderMatchModel = getModelForClass(LadderMatch);

export interface LadderMatchType extends Pick<DocumentType<LadderMatch>, keyof LadderMatch> { };
export type LadderUpdateInter = Partial<LadderMatchType>;