import BaseModel from './BaseModel';
import { index, getModelForClass, prop, DocumentType, Ref, mongoose } from '@typegoose/typegoose';
import Role, { } from './Role';
import { Defense, Attack, LineupCe, OppPlayer, HeroScore, } from '../domain/battleField/pvp';
import { PVP } from '../pubUtils/dicParam';

@index({ roleId: 1 })
@index({ score: 1 })
export default class PvpDefense extends BaseModel {
    @prop({ required: true })
    serverId: number;					// 区 id
    @prop({ required: true })
    roleId: string;					// 角色 id
    @prop({ required: true })
    roleName: string;				// 角色名称
    @prop({ ref: 'Role', type: mongoose.Schema.Types.ObjectId })
    role: Ref<Role>;
    @prop({ required: true, default: null, _id: false })
    hasDefense: boolean;
    @prop({ required: true, default: null, _id: false })
    defense: Defense;
    @prop({ required: true, default: null, _id: false })
    attack: Attack;
    @prop({ required: true, type: () => LineupCe, default: [], _id: false })
    lineupCe: LineupCe[];
    @prop({ required: true, type: () => OppPlayer, default: [], _id: false })
    oppPlayers: OppPlayer[];
    @prop({ required: true, type: () => OppPlayer, default: [], _id: false })
    oppBeforePlayers: OppPlayer[];
    @prop({ required: true, type: () => HeroScore, default: [], _id: false })
    heroScores: HeroScore[];

    @prop({ required: true, default: 0 })
    score: number;
    @prop({ required: true, default: 0 })
    hisScore: number;              //历史最高积分
    @prop({ required: true, default: 0 })
    winStreakNum: number;          //连胜次数
    @prop({ required: true, default: 0 })
    hisWinStreakNum: number;          //历史最高连胜次数

    @prop({ required: true, default: () => { return new Date() } })
    refDaily: Date;                  // 每日刷新，控制refOppCnt和setAttackCnt
    @prop({ required: true, default: 0 })
    refOppCnt: number;                  // 刷新对手总次数，消耗可根据消耗表算出
    @prop({ required: true, default: 0 })
    setAttackCnt: number;               // 设置挑战阵容次数
    @prop({ required: true, default: 0 })
    buyAttackCnt: number;               // 购买挑战阵容次数

    @prop({ required: true, default: 0 })
    challengeCnt: number;       // 可挑战次数，每2小时回复一次
    @prop({ required: true, default: 0 })
    challengeRefTime: number;    // 上一次刷新的时间

    @prop({ required: true, type: Number, default: [] })
    receivedBox: Array<number>;
    @prop({ required: true, default: 0 })
    seasonNum: number;
    @prop({ required: true, default: 0 })
    seasonWinNum: number;          // 本赛季胜利次数
    @prop({ required: true, default: 1 })
    rankSeasonNum: number;

    @prop({ required: true, default: 0 })
    defenseScoreCnt: number;
    @prop({ required: true })
    refDefenseScore: Date;

    public static async findByRoleId(roleId: string, getters = false) {
        const result: PvpDefenseType = await PvpDefenseModel.findOne({ roleId }).lean({ getters});
        return result;
    }
    
    public static async findByScale(roleId: string, min: number, max: number) {
        const result: PvpDefenseType[] = await PvpDefenseModel.find({ roleId: { $ne: roleId }, defCe: { $lte: max, $gte: min } })
        .populate('role', 'head frame spine heads frames spines topLineupCe roleId roleName lv globalCeAttr title')
        .populate('heroes.hero')
        .sort({ updatedAt: -1 }).limit(100).lean({ getters: true, virtuals: true });
        return result;
    }

    public static async createPvpDefense(seasonNum: number, params: pvpUpdateInter) {
        const doc = new PvpDefenseModel();

        const update = Object.assign(doc.toJSON(), params, { rankSeasonNum: seasonNum, challengeCnt: PVP.PVP_CHALLENGE_COUNTS });
        const defense: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({ roleId: params.roleId }, update, { upsert: true, new: true })
        .populate('role')
        .lean();
        return defense;

    }

    public static async addHeroToDefense(roleId: string, heroInfo: any, defCe: number, lean = true) {
        const defense: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({ roleId: roleId }, {
            $inc: { defCe },
            $push: { heroes: heroInfo }
        }, { upsert: true, new: true }).lean(lean);
        return defense;

    }

    public static async removeHeroFromDefense(roleId: string, hid: number, defCe: number, lean = true) {
        const defense: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({ roleId: roleId }, {
            $inc: { defCe: -1 * defCe },
            $pull: { heroes: { actorId: hid } }
        }, { upsert: true, new: true }).lean(lean);
        return defense;

    }

    public static async updateCe(roleId: string, hid: number, ce: number, lean = true) {   
        const rec: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({ roleId, 'lineupCe.hid': hid }, { $set: { 'lineupCe.$.ce': ce } }).lean(lean);
        return rec
    }

    public static async deleteAccount(roleId: string) {
        let result = await PvpDefenseModel.deleteMany({ roleId });
        return result;
    }

    public static async findByRoleIdIncludeAll(roleId: string) {
        const result: PvpDefenseType = await PvpDefenseModel.findOne({ roleId })
        .populate('role', 'head frame spine heads frames spines topLineupCe roleId roleName lv globalCeAttr title')
        .populate('heroes.hero')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine heads frames spines rankLv title lv pLv defCe heroes warId buff')
        .populate('oppBeforePlayers.oppDef', 'oppRoleId pos roleName head frame spine heads frames spines rankLv title lv pLv defCe heroes warId buff').lean({ getters: true, virtuals: true });
        return result;
    }

    public static async findByTeamLv(seasonNum: number, min: number, max: number) {
        const result: PvpDefenseType[] = await PvpDefenseModel.find({ seasonNum, hasDefense: true, 'defense.pLv': { $gte: min, $lte: max } })
        .populate('role', '_id head frame spine heads frames spines topLineupCe roleId roleName lv globalCeAttr title')
        .populate('heroes.hero')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine heads frames spines rankLv pLv title lv defCe heroes warId buff')
        .lean({ getters: true, virtuals: true });
        return result;
    }

    public static async findNeighborByScore(seasonNum: number, score: number) {
        const beforeData: PvpDefenseType[] = await PvpDefenseModel.find({
            seasonNum, hasDefense: true, score: { $lt: score }
        }).sort({ score: -1 }).limit(10)
        .populate('role', '_id head frame spine heads frames spines topLineupCe roleId roleName lv globalCeAttr title')
        .populate('heroes.hero')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine heads frames spines rankLv pLv title lv defCe heroes warId buff')
        .lean({ getters: true, virtuals: true });
        const afterData: PvpDefenseType[] = await PvpDefenseModel.find({
            seasonNum, hasDefense: true, score: { $gt: score }
        }).sort({ score: 1 }).limit(10)
        .populate('role', '_id head frame spine heads frames spines topLineupCe roleId roleName lv globalCeAttr title')
        .populate('heroes.hero')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine heads frames spines rankLv pLv title lv defCe heroes warId buff')
        .lean({ getters: true, virtuals: true });
        
        return [...beforeData, ...afterData];
    }

    public static async updateInfoAndInclude(roleId: string, update: pvpUpdateInter) {
        delete update._id;
        let result: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({roleId}, {$set:update}, {new: true})
        .populate('role', 'head frame spine heads frames spines topLineupCe roleId roleName lv title')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine heads frames spines rankLv pLv title lv defCe heroes warId buff').lean({ getters: true, virtuals: true });
        return result;
    }
    public static async updateInfo(roleId: string, update: pvpUpdateInter, lean = true) {
        let result: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({roleId}, {$set:update}, {new: true}).lean(lean);
        return result;
    }

    public static async getPvpDef(limit: number, page: number, lean = true) {
        let result: Array<PvpDefenseType> =  await PvpDefenseModel.find().sort({ score: -1 }).limit(limit).skip((page) * limit).lean(lean);
        return result;
    }

    public static async getRank(seasonNum: number, page = 1, limit = 1000) {
        let sortBy = { score: -1, updatedAt: 1 };
        const ranks: PvpDefenseType[] = await PvpDefenseModel.find({rankSeasonNum: seasonNum, score: { $gt: 0 }}).select('roleId role score updatedAt')
        .populate('role', 'roleId roleName head frame spine heads frames spines title lv vLv updatedAt serverId')
        .sort(sortBy).limit(limit).skip((page - 1) * limit).lean({ getters: true, virtuals: true });
        return ranks;
    }

    public static async deleteHero(roleId: string, hid: number) {
        let result:PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({roleId}, {$pull:{lineupCe: {hid}, heroScores: {hid}}, $set: {defense: null, attack: null}}, {new: true}).lean();
        return result;
    }

    public static async resetDefense() {
        await PvpDefenseModel.updateMany({}, { $set: { hasDefense: false, defense: null } });
    }

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

export const PvpDefenseModel = getModelForClass(PvpDefense);

export interface PvpDefenseType extends Pick<DocumentType<PvpDefense>, keyof PvpDefense> { };
export type pvpUpdateInter = Partial<PvpDefenseType>;