import { RPCInterface } from "rpclibrary";
import { Inject, Injectable } from "../../Injector/ServiceDecorator";
import { TableDefiniton, Character, Spec, User, Class, _Rank, Rank } from "../../Types/Types";
import { CharacterManagerIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { getSpecTableData, SpecT } from "../../Types/PlayerSpecs";
import { IAdmin } from "../../Admin/Interface";
import { IUserManager } from "../User/Interface";
import { ICharacterManager } from "./Interface";

@Injectable(ICharacterManager)
export class CharacterManager
implements FrontworkComponent<CharacterManagerIfc, RPCInterface>, ICharacterManager{
    name = "CharacterManager" as "CharacterManager";   
    
    @Inject(IAdmin)
    private admin: IAdmin   

    @Inject(IUserManager)
    private loginManager : IUserManager

    exportRPCs = () => [
            this.getSpecId,
            this.getCharacterByName,
            this.getCharacters,
            this.getCharactersOfUser,
            this.createCharacter,
            this.getUserOfCharacter,
            this.getHeadCount
    ]

    exportRPCFeatures = () => [
    ]
    
    getTableDefinitions = (): TableDefiniton[] => [
        {
            name: 'characters',
            tableBuilder: (table) => {
                table.increments("id").primary()
                table.string("charactername").notNullable().unique()
                table.integer("specid").notNullable()
                table.foreign("specid").references("specs.id")
                table.integer("userid").notNullable()
                table.foreign("userid").references("users.id")
                table.string("race").notNullable()
                table.boolean('alt').defaultTo(false)
            }
        },{
            name: 'specs',
            tableBuilder: (table) => {
                table.increments("id")
                table.string('class')
                table.string('specname')
                table.unique(['class', 'specname'])
            }
        }
    ]

    private initialized = false
    
    initialize = async () => {
        if(this.initialized) return
        this.initialized = true
        await this.admin.knex('specs').insert(getSpecTableData()).catch(e => { console.log("skipping spec insertion") })
    }

    createCharacter = async (userToken: string, character : Character) : Promise<Character> => {
        try{
            character.charactername = character.charactername.toLowerCase()
            const user = this.loginManager.getUserRecordByToken(userToken)
            await this.admin.knex('characters').insert(character)
            const char : Character = await this.admin.knex.select('*').from('characters').where(character).first()
            return char
        }catch(e){
            console.log(e);
        }
        throw new Error('Unable to create character')
    }

    getCharacters = async() : Promise<(Character & Spec & User)[]> => {
        return this.admin.knex
        .select('*')
        .from('characters as c')
        .join('specs as sp', 'c.specid', 'sp.id')
        .join('users as u', 'c.userid', 'u.id')
    }

    getCharacterByName = async(charactername: string) : Promise<(Character & User & Spec) | void> => {
        charactername = charactername.toLowerCase()
        return await this.admin.knex('characters as c')
        .join('specs as s', 's.id', '=', 'c.specid')
        .join('users as u', 'u.id', '=', 'c.userid')
        .select('c.id as id', 'charactername', 'race', 'specid', 'class', 'specname', 'username', 'rank')
        .where('charactername', '=', charactername)
        .first()
    }

    getCharactersOfUser = async(username: string) : Promise<(Character & Spec)[]> => {
        username = username.toLowerCase()
        return await this.admin.knex('characters as c')
        .join('users as u', 'u.id', '=', 'c.userid')
        .join('specs as s', 's.id', '=', 'c.specid')
        .select('class', 'charactername', 'class', 'race', 'specname', 'userid', 'c.id as id', 'specid')
        .where('u.username', '=', username)
    }

    getUserOfCharacter = async(character: Character) : Promise<User> => {
        return await this.admin.knex('users').where({
            id: character.userid
        }).first()
    }

    getSpecId = async <c extends keyof SpecT>(clazz: c, name: SpecT[c]) => await this.admin.knex
    .from('specs')
    .select('id')
    .where(<Spec>{
        class: clazz,
        specname: name
    })
    .first()
    .then(spec => spec.id)

    getHeadCount = async(c:Class):Promise<number> => {
        const cnt = await this.admin
        .knex('characters as c')
        .join('users as u', 'c.userid', '=', 'u.id')
        .join('specs as s', 'c.specid', '=', 's.id')
        .where({
            class: c
        }).andWhere(function(){
            this.whereNotIn('u.rank', <Rank[]>[
                'Guest'
            ])
            this.whereNot('c.alt', '1')
        })
        .count('*')
        return cnt[0]['count(*)'] as number
    }
}