import BaseModel from './BaseModel';
import { index, getModelForClass, prop, DocumentType, Ref, mongoose } from '@typegoose/typegoose';
import Hero, { } from './Hero';
import Role, { } from './Role';
import { PVP_PLAYER_POS, PVP_HERO_POS } from '../consts';
import PvpHistoryOpp from './PvpHistoryOpp';
import { reduceCe } from '../pubUtils/util';

export class Heroes {
    @prop({ required: true })
    actorId: number;        // 武将id
    @prop({ ref: Hero, type: mongoose.Schema.Types.ObjectId })
    hero: Ref<Hero>;
    @prop({ required: true })
    ce: number; // 战斗力
    @prop({ required: true })
    dataId: number;
    @prop({ required: true })
    order: number;
}

// 初始化
function getInitialOppPlayers() {
    let players = new Array<OppPlayers>();
    for(let i = PVP_PLAYER_POS.START; i <= PVP_PLAYER_POS.END; i++) {
        let p = new OppPlayers();
        p.pos = i;
        players.push(p);
    }
    return players;
}

// 初始化
function getInitialOppHeros() {
    let heros = new Array<Heroes>();
    for(let i = PVP_HERO_POS.START; i <= PVP_HERO_POS.END; i++) {
        let p = new Heroes();
        p.dataId = i;
        p.order = i;
        heros.push(p);
    }
    return heros;
}

export class OppPlayers {
    @prop({ required: true })
    roleId: string;
    @prop({ ref: 'PvpHistoryOpp', type: mongoose.Schema.Types.ObjectId })
    oppDef: Ref<PvpHistoryOpp>;
    @prop({ required: true })
    pos: number;
    @prop({ required: true })
    isRobot: boolean;
}

export class HeroScores {
    @prop({ required: true })
    hid: number;
    @prop({ required: true })
    score: number;
}

@index({ roleId: 1 })
export default class PvpDefense extends BaseModel {
    @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, type: Heroes, default: getInitialOppHeros(), _id: false })
    heroes: Array<Heroes>;
    @prop({ required: true, default: 0 })
    score: number;
    @prop({ required: true, default: 1 })
    pLv: number;
    @prop({ required: true, default: 0 })
    hisScore: number;              //历史最高积分
    @prop({ required: true, default: 0 })
    winStreakNum: number;          //连胜次数
    @prop({ required: true, default: 0, get: (val: number) => reduceCe(val), set: (val: number) => val })
    defCe: number;                 //防守ce
    @prop({ required: true, type: OppPlayers, default: getInitialOppPlayers(), _id: false })
    oppPlayers: Array<OppPlayers>;
    @prop({ required: true, type: HeroScores, default: [], _id: false })
    heroScores: Array<HeroScores>;
    @prop({ required: true, default: true })
    isDefaultHero: boolean;
    @prop({ required: true, default: 0 })
    refOppCnt: number;                  // 刷新对手总次数，消耗可根据消耗表算出
    @prop({ required: true, default: new Date() })
    refOppTime: Date;                  // 刷新对手时间
    @prop({ required: true, default: 0 })
    challengeCnt: number;       // 可挑战次数
    @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: true })
    isFirstEntry: boolean;
    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 topLineupCe roleId roleName lv globalCeAttr')
        .populate('heroes.hero')
        .sort({ updatedAt: -1 }).limit(100).lean({ getters: true, virtuals: true });
        return result;
    }

    public static async createPvpDefense(params: { roleId: string, roleName: string, role: string, heroes: Array<Heroes>, oppPlayers: Array<OppPlayers>, defCe: number, seasonNum: number,challengeCnt: number }, lean = true) {
        const doc = new PvpDefenseModel();
        const update = Object.assign(doc.toJSON(), params);
        const defense: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({ roleId: params.roleId }, update, { upsert: true, new: true }).lean(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, defCe: number, lean = true) {   
        const rec: PvpDefenseType = await PvpDefenseModel.findOneAndUpdate({ roleId, 'heroes.actorId': hid }, { $inc: { defCe } }).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 topLineupCe roleId roleName lv globalCeAttr')
        .populate('heroes.hero')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine rankLv title lv pLv defCe heroes').lean({ getters: true, virtuals: true });
        return result;
    }

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

    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 topLineupCe roleId roleName lv')
        .populate('oppPlayers.oppDef', 'oppRoleId pos roleName head frame spine rankLv pLv title lv defCe heroes').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({seasonNum}).select('roleId role score updatedAt')
        .populate('role', 'roleId roleName head frame spine title lv vLv')
        .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:{heroes: {actorId:hid}}}, {new: true}).lean();
        return result;
    }
}

export const PvpDefenseModel = getModelForClass(PvpDefense);

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