import BaseModel from './BaseModel';
import { index, getModelForClass, prop, DocumentType, mongoose, Ref } from '@typegoose/typegoose';
import Role, { RoleType } from '../db/Role';
import { genCode } from '../pubUtils/util';
import { GuildType } from './Guild';
import { LEAGUE_AUTH } from '../consts';

class Member {
    @prop({ required: true }) 
    roleId: string;
    @prop({ required: true }) 
    guildCode: string;
    @prop({ required: true }) 
    auth: number;
}

@index({ leagueCode: 1, status: 1 })
@index({ guildCodes: 1, status: 1 })
@index({ 'members.roleId': 1, status: 1 })
export default class GVGLeague extends BaseModel {

    @prop({ required: true })    
    leagueCode: string;       // 唯一id

    @prop({ required: true, type: String }) 
    guildCodes: string[];    // 包含军团

    @prop({ required: true, ref: () => Role, type: mongoose.Schema.Types.ObjectId })
    leader: Ref<Role>;

    @prop({ required: true, default: '' }) 
    name: string;  // 名称

    @prop({ required: true, default: 1 }) 
    icon: number;  // 图标

    @prop({ required: true, default: '' }) 
    notice: string;  // 公告

    @prop({ required: true, default: 0 }) 
    guildCnt: number;  // 军团数量

    @prop({ required: true, default: 0 }) 
    memberCnt: number;  // 玩家人数

    @prop({ required: true, default: false }) 
    isAuto: boolean;  // 是否是自动创建的

    @prop({ required: true, default: 1 }) 
    status: number;  // 1-健在 0-解散

    @prop({ required: true, default: 1 }) 
    type: number;  // 1-单服 2-跨服

    @prop({ required: true, default: 1 }) 
    serverId: number;  // 服务器id

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

    public static async checkName(name: string) {
        const result = await GVGLeagueModel.exists({ name, status: 1 });
        return result;
    }

    public static async createLeague(guild: GuildType, params: GVGLeagueUpdate) {
        const { code, name, memberCnt, leader, members } = guild;
        const doc = new GVGLeagueModel();
        const leagueCode = genCode(6);
        let _leader = <RoleType>leader;
        let _members = members.map(roleId => ({ roleId, auth: _leader.roleId == roleId? LEAGUE_AUTH.LEADER: LEAGUE_AUTH.MEMBER, guildCode: code }));
        const update = Object.assign(doc.toJSON(), { guildCodes: [code], name: `${name}联军`, guildCnt: 1, memberCnt, isAuto: true, leader: _leader?._id, members: _members, leagueCode }, params);
        delete update._id;

        const result: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ guildCodes: code, status: 1 }, { $setOnInsert: update }, { upsert: true, new: true })
        .select({ _id: 0, __v: 0, createdAt: 0, updatedAt: 0 })
        .populate('leader', { roleId: 1, roleName: 1, head: 1, frame: 1, spine: 1, lv: 1, quitTime: 1, ce: 1, title: 1, serverId: 1, _id: 0 }, 'Role')
        .lean();
        return result;
    }

    public static async findLeagueByGuild(guildCode: string) {
        const league: GVGLeagueType = await GVGLeagueModel.findOne({ guildCodes: guildCode, status: 1 })
        .populate('leader', { roleId: 1, roleName: 1, head: 1, frame: 1, spine: 1, lv: 1, quitTime: 1, ce: 1, title: 1, serverId: 1, _id: 0 }, 'Role')
        .lean();
        return league
    }

    public static async findByCondition(type: number, serverIds: number[], page: number, name: string) {
        let query = {
            status: 1, type, serverId: { $in: serverIds }
        };
        if(name) {
            query['name'] = { $regex: new RegExp(name, 'i') };
        }
        const league: GVGLeagueType[] = await GVGLeagueModel.find(query)
        .populate('leader', { roleId: 1, roleName: 1, head: 1, frame: 1, spine: 1, lv: 1, quitTime: 1, ce: 1, title: 1, serverId: 1, _id: 0 }, 'Role')
        .skip((page - 1) * 30).limit(30)
        .lean();
        return league
    }

    public static async findByCode(leagueCode: string, select = '') {
        const league: GVGLeagueType = await GVGLeagueModel.findOne({ leagueCode, status: 1 })
        .populate('leader', { roleId: 1, roleName: 1, head: 1, frame: 1, spine: 1, lv: 1, quitTime: 1, ce: 1, title: 1, serverId: 1, _id: 0 }, 'Role')
        .select(select)
        .lean();
        return league
    }

    public static async findByCodeWithoutPopulate(leagueCode: string, select = '') {
        const league: GVGLeagueType = await GVGLeagueModel.findOne({ leagueCode, status: 1 }).select(select).lean();
        return league
    }

    public static async findByCodes(leagueCodes: string[]) {
        const leagues: GVGLeagueType[] = await GVGLeagueModel.find({ leagueCode: { $in: leagueCodes }, status: 1 })
        .populate('leader', { roleId: 1, roleName: 1, head: 1, frame: 1, spine: 1, lv: 1, quitTime: 1, ce: 1, title: 1, serverId: 1, guildName: 1, _id: 0 }, 'Role')
        .lean();
        return leagues
    }

    public static async findByCodesWithoutPopulate(leagueCodes: string[], select = '') {
        const leagues: GVGLeagueType[] = await GVGLeagueModel.find({ leagueCode: { $in: leagueCodes }, status: 1 }).select(select).lean();
        return leagues
    }

    public static async joinGuild(leagueCode: string, guild: GuildType) {
        const { code, memberCnt, leader, members } = guild;
        let _leader = <RoleType>leader;
        let _members = members.map(roleId => ({ roleId, auth: _leader.roleId == roleId? LEAGUE_AUTH.SUB_LEADER: LEAGUE_AUTH.MEMBER, guildCode: code }));

        const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, 
            { $push: { guildCodes: code, members: { $each: _members } }, $inc: { guildCnt: 1, memberCnt: memberCnt }
        }, { new: true }).lean();
        return league
    }

    public static async findAllLeague() {
        const leagues: { leagueCode: string, memberCnt: number, serverId: number }[] = await GVGLeagueModel.aggregate([
            { $match: { status: 1 } },
            { $project: { leagueCode: 1, memberCnt: 1, serverId: 1 } }
        ]);
        return leagues;
    }

    public static async quitGuild(leagueCode: string, guild: GuildType) {
        const { code, memberCnt, members } = guild;
        const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, 
            { $pull: { guildCodes: code, members: { roleId: { $in: members } } }, $inc: { guildCnt: -1, memberCnt: -memberCnt }
        }, { new: true }).lean();
        return league
    }

    public static async dismiss(leagueCode: string) {
        const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, { $set: { status: 0 }}, { new: true }).lean();
        return league
    }

    public static async dismissByIds(_ids: string[]) {
        await GVGLeagueModel.updateMany({ _id: { $in: _ids } }, { $set: { status: 0 }});
    }

    public static async abdicate(leagueCode: string, fromRoleId: string, toRoleId: string, leader: string) {
        let league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1, 'members.roleId': fromRoleId }, { $set: { 'members.$.auth': LEAGUE_AUTH.SUB_LEADER }}, { new: true }).lean();
        league = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1, 'members.roleId': toRoleId }, { $set: { 'members.$.auth': LEAGUE_AUTH.LEADER, leader }}, { new: true }).lean();
        return league
    }

    public static async setInfo(leagueCode: string, name: string, notice: string, icon: number) {
        let updateInfo: GVGLeagueUpdate = {};
        if(name != undefined) updateInfo.name = name;
        if(notice != undefined) updateInfo.notice = notice;
        if(icon != undefined) updateInfo.icon = icon;
        const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, { $set: updateInfo }, { new: true }).lean();
        return league;
    }

    public static async joinMember(leagueCode: string, roleId: string, guildCode: string) {
        const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, { $push: { members: { roleId, auth: LEAGUE_AUTH.MEMBER, guildCode } }, $inc: { memberCnt: 1 } }, { new: true }).lean();
        return league;
    }

    public static async quitMember(leagueCode: string, roleId: string) {
        const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, { $pull: { members: { roleId } }, $inc: { memberCnt: -1 } }, { new: true }).lean();
        return league;
    }

    public static async abdicateGuild(leagueCode: string, fromRoleId: string, toRoleId: string, leader: string) {
        let league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1, 'members.roleId': fromRoleId }, { $set: { 'members.$.auth': LEAGUE_AUTH.MEMBER }}).lean();
        const member = league.members.find(cur => cur.roleId == fromRoleId);
        const updateInfo = { 'members.$.auth': member?.auth };
        if(member?.auth == LEAGUE_AUTH.LEADER) updateInfo['leader'] = leader;
        league = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1, 'members.roleId': toRoleId }, { $set: updateInfo }, { new: true }).lean();
        return league
    }

    public static async findAutoCreateLeague() {
        let leagues: GVGLeagueType[] = await GVGLeagueModel.find({ isAuto: true, status: 1 }).lean();
        return leagues
    }
}

export const GVGLeagueModel = getModelForClass(GVGLeague);

export interface GVGLeagueType extends Pick<DocumentType<GVGLeague>, keyof GVGLeague> {
    id: number;
};
export type GVGLeagueUpdate = Partial<GVGLeagueType>; // 将所有字段变成可选项
