import BaseModel from './BaseModel';
import { index, getModelForClass, prop, Ref, mongoose, DocumentType } from '@typegoose/typegoose';
import Equip, { } from './Equip';
import { CounterModel } from './Counter';
import { COUNTER, EQUIP_TYPE } from '../consts';
import { reduceCe } from '../pubUtils/util';
import Skin from './Skin';

class CeAttrData {
    @prop({ required: true })
    id: number = 0;
    @prop({ required: true })
    base: number = 0;
    @prop({ required: true })
    ratioUp: number = 0;
    @prop({ required: true })
    fixUp: number = 0;
    @prop({ required: true })
    equipUp: number = 0;

    constructor(id: number) {
        this.id = id;
    }
}

/**
  * 英雄表
*/

export class Connect {
    @prop({ required: true })
    shipId: number;
    @prop({ required: true })
    level: number;
}

export class HeroSkin {
    @prop({ required: true })
    id: number;
    @prop({ ref: 'Skin', type: mongoose.Schema.Types.ObjectId })
    skin: Ref<Skin>;
    @prop({ required: true })
    enable: boolean;
}

export class EPlace {
    @prop({ required: true })
    id: number;
    @prop({ ref: Equip, type: mongoose.Schema.Types.ObjectId })
    equip: Ref<Equip>;
    @prop({ required: true })
    lv: number;
    @prop({ required: true })
    refineLv: number;
}

// 初始化
function getInitialEplace() {
    let ePlace = new Array<EPlace>();
    for (let i = EQUIP_TYPE.START; i <= EQUIP_TYPE.END; i++) {
        let p = new EPlace();
        p.id = i;
        p.equip = null;
        p.lv = 0;
        p.refineLv = 0;

        ePlace.push(p);
    }
    return ePlace;
}

@index({ roleId: 1, hid: 1 })
@index({ roleId: 1, seqId: 1 })
export default class Hero extends BaseModel {

    @prop({ required: true })
    roleId: string;					// 角色 id
    @prop({ required: true })
    roleName: string;				// 角色名称
    @prop({ required: true })
    serverId: number;               // 区服 id

    @prop({ required: true })
    hid: number;						// 武将 id
    @prop({ required: true })
    hName: string;					// 武将名
    @prop({ required: true })
    seqId: number;					// 武将表自增 id

    @prop({ required: true, default: 0 })
    exp: number;						// 经验值
    @prop({ required: true, default: 1 })
    lv: number;							// 武将等级
    @prop({ required: true, default: 0, set: (val: number) => val, get: (val: number) => reduceCe(val) })
    ce: number;							// 武将战力
    @prop({ required: false, set: (val: boolean) => val, get: () => true })
    isReducedCe: boolean;                 // 如果战力没有缩过就会返回false，缩过了就会返回true
    @prop({ required: true, default: 0 })
    historyCe: number;			        // 武将历史最高战力
    @prop({ required: true, type: CeAttrData, default: [], _id: false })
    attr: CeAttrData[];                     // 影响战力的属性

    @prop({ required: true, default: 1 })
    star: number;						// 星级
    @prop({ required: true, default: 0 })
    starStage: number;		  // 星级六维阶段
    @prop({ required: true, default: 0 })
    colorStar: number;						// 觉醒， 彩星
    @prop({ required: true, default: 0 })
    colorStarStage: number;			// 觉醒六维阶段

    @prop({ required: true, default: 0 })
    quality: number;			  // 品质

    @prop({ required: true, default: false })
    scrollActive: boolean;               // 是否在名将谱中激活
    @prop({ required: true, default: 0 })
    scrollId: number;                   // 名将谱id
    @prop({ required: true, default: 0 })
    scrollStar: number;                  // 名将谱中保存的星级
    @prop({ required: true, default: 0 })
    scrollColorStar: number;             // 名将谱中保存的觉醒等级
    @prop({ required: true, default: 0 })
    scrollQuality: number;               // 名将谱中保存的品质

    @prop({ required: true, default: 0 })
    job: number;					  // 职业
    @prop({ required: true, default: 0 })
    jobStage: number;			  // 职阶

    @prop({ required: true, default: 0 })
    favour: number;					// 好感度
    @prop({ required: true, default: 1 })
    favourLv: number;				// 好感等级
    @prop({ required: true, type: Connect, default: [], _id: false })
    connections: Connect[];					// 羁绊
    @prop({ required: true, type: HeroSkin, default: [], _id: false })
    skins: HeroSkin[];								// 皮肤

    @prop({ required: true, type: EPlace, default: getInitialEplace(), _id: false })
    ePlace: EPlace[]; // 武将装备引用数组

    public static async findByRole(roleId: string, sort: { field: string, sortBy: number }[] = [], select?: string, getters = false) {
        let sortParam = {};
        for (let { field, sortBy } of sort) {
            sortParam[field] = sortBy;
        }
        const heros: HeroType[] = await HeroModel.find({ roleId }).sort(sortParam).select(select).lean({ getters });
        return heros;
    }

    public static async findBySeqIdRange(seqIds: Array<number>, roleId: string, lean = true) {
        const hero: HeroType[] = await HeroModel.find({ seqId: { $in: seqIds }, roleId }).lean(lean);
        return hero;
    }

    public static async findByHidRange(hids: Array<number>, roleId: string, select?: string, getters = false) {
        const hero: HeroType[] = await HeroModel.find({ hid: { $in: hids }, roleId }).select(select).lean({ getters });
        return hero;
    }

    public static async findMapByHidRange(hids: Array<number>, roleId: string, select?: string, getters = false) {
        const hero = await HeroModel.findByHidRange(hids, roleId, select, getters);
        let map = new Map<number, HeroType>();
        for (let h of hero) {
            map.set(h.hid, h);
        }
        return map;
    }

    public static async findBySeqIdAndRole(seqId: number, roleId: string, lean = true) {
        const hero: HeroType = await HeroModel.findOne({ seqId, roleId }).lean(lean);
        return hero;
    }
    public static async findByHidAndRole(hid: number, roleId: string, select?: string, getters = false) {
        const hero: HeroType = await HeroModel.findOne({ hid, roleId }).select(select).lean({ getters });
        return hero;
    }

    public static async findByHidAndRoleWithEquip(hid: number, roleId: string, lean = true) {
        const hero: HeroType = await HeroModel.findOne({ hid, roleId }).populate('ePlace.equip').lean(lean);
        return hero;
    }

    public static async addEquip(roleId: string, hid: number, ePlaceId: number, equipId: string, lean = true) {
        const hero: HeroType = await HeroModel.findOneAndUpdate(
            { roleId, hid, 'ePlace.id': ePlaceId },
            { $set: { 'ePlace.$.equip': equipId } },
            { new: true }).populate('ePlace.equip').lean(lean);
        if (hero) {
            await Equip.putOnOrOff(equipId, hero.hid);
        }
        return hero;
    }

    public static async removeEquip(roleId: string, hid: number, ePlaceId: number, equipId: string, lean = true) {
        const hero: HeroType = await HeroModel.findOneAndUpdate(
            { roleId, hid, 'ePlace.id': ePlaceId },
            { $set: { 'ePlace.$.equip': null } },
            { new: true }).populate('ePlace.equip').lean(lean);
        if (hero) {
            await Equip.putOnOrOff(equipId, 0);
        }
        return hero;
    }

    public static async createHero(heroInfo: HeroUpdate, lean = true) {
        const doc = new HeroModel();
        const seqId = await CounterModel.getNewCounter(COUNTER.HID) || -1;
        const update = Object.assign(doc.toJSON(), heroInfo, { seqId });
        delete update._id;
        const hero: HeroType = await HeroModel.findOneAndUpdate({ roleId: heroInfo.roleId, hid: heroInfo.hid }, update, { upsert: true, new: true }).lean(lean);
        return hero;
    }

    public static async sumTopHeroCe(roleId: string, num: number) {
        let ce: Array<{ historyCe: number }> = await HeroModel.aggregate([
            { $match: { roleId } },
            { $sort: { historyCe: -1 } },
            { $limit: num },
            { $group: { _id: null, historyCe: { $sum: '$historyCe' } } }
        ]);
        return ce.length > 0 ? ce[0].historyCe : 0;
    }

    public static async sumHeroCe(roleId: string) {
        let ce: Array<{ ce: number }> = await HeroModel.aggregate([
            { $match: { roleId } },
            { $group: { _id: null, ce: { $sum: '$ce' } } }
        ]);
        return ce.length > 0 ? ce[0].ce : 0;
    }

    public static async getTopHero(roleId: string, num: number, lean = true) {
        const heroes: HeroType[] = await HeroModel.find({ roleId }).limit(num).sort({ ce: -1 }).lean(lean);
        return heroes;
    }

    public static async updateCe(roleId: string, hid: number, ce: number, oldCe: number, historyCe: number, lean = true) {
        let distance = ce - oldCe;
        let historyDistance = ce > historyCe ? ce - historyCe : 0;
        let result: HeroType = await HeroModel.findOneAndUpdate({ roleId, hid }, { $inc: { ce: distance, historyCe: historyDistance } }).lean(lean);
        return result;
    }

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

    public static async deleteHero(roleId: string, hid: number) {
        let result = await HeroModel.deleteMany({ roleId, hid });
        return result;
    }

    public static async updateHeroInfo(roleId: string, hid: number, heroUpdate: HeroUpdate, select?: string, getters = false) {
        delete heroUpdate._id;
        let result: HeroType = await HeroModel.findOneAndUpdate({ roleId, hid }, { $set: heroUpdate }, { new: true }).select(select).lean({ getters });
        return result;
    }

    public static async unloadHeroAndEquip(roleId: string, hid: number, id: number, lean = true) {
        const hero: HeroType = await HeroModel.findOneAndUpdate(
            { roleId, hid, 'ePlace.id': id },
            { $set: { 'ePlace.$.equip': null } },
            { new: true }).lean(lean);
        return hero;
    }

    public static async findByField(field: string, value?: number | string, lean = true) {
        let searchObj = {};
        if (field != 'all') {
            if (field == 'roleName') {
                searchObj['roleName'] = { $regex: new RegExp(value.toString(), 'i') }
            } else {
                searchObj[field] = value;
            }
        }
        //.select('uid tel username')
        const user: HeroType[] = await HeroModel.find(searchObj).lean(lean);
        return user;
    }

    public static async getAllRank(serverId: number, select?: string, limit = 200) {
        let result: HeroType[] = await HeroModel.find({ serverId }, { _id: false }).select(select).limit(limit).sort({ ce: -1, updatedAt: 1 }).lean({ getters: true });
        return result;
    }

    public static async getMyTopHero(roleId: string, select?: string) {
        let result: HeroType = await HeroModel.findOne({ roleId }, { _id: false }).select(select).sort({ ce: -1, updatedAt: 1 }).lean({ getters: true });
        return result;
    }

    public static async getRank(hid: number, serverId: number, select?: string, limit = 200) {
        let result: HeroType[] = await HeroModel.find({ serverId, hid }, { _id: false }).select(select).limit(limit).lean({ getters: true });
        return result;
    }
}

export const HeroModel = getModelForClass(Hero);

export interface HeroType extends Pick<DocumentType<Hero>, keyof Hero> { };
export type HeroUpdate = Partial<HeroType>; // 将所有字段变成可选项
