import { Injector } from "../src/backend/Injector/Injector";
import { FrontworkAdmin } from "../src/backend/Admin/Admin";
import { T1 } from "../src/backend/Types/Items";

import { RPCSocket } from "rpclibrary";
import { FrontcraftIfc, Auth, User, FrontcraftFeatureIfc, Raid, Character, Rank, Class, Race, SRPriority, Spec, Signup } from "../src/backend/Types/Types";
import { SpecT } from "../src/backend/Types/PlayerSpecs";


type protoAccount<C extends Class = Class> = {
    name : string,
    rank : Rank,
    race: Race,
    class: C,
    spec: SpecT[C]
}

const trialsAndUp = {
    ADMIN: true,
    Guildmaster: true,
    Officer: true,
    Classleader: true,
    Raider: true,
    Trial: true,
    Social: false,
    Guest: false
}

const adminsOnly = {
    ADMIN: true,
    Guildmaster: true,
    Officer: true,
    Classleader: false,
    Raider: false,
    Trial: false,
    Social: false,
    Guest: false
}

const defaultPermissions = [
{   rpcname: 'signup', ...trialsAndUp
},{ rpcname: 'reset', ...adminsOnly 
},{ rpcname: 'modifyPermissions', ...adminsOnly 
},{ rpcname: 'manageGuild', ...adminsOnly
},{ rpcname: 'managePriorities', ...adminsOnly
},{ rpcname: 'softreserveCurrency', ...adminsOnly
},{ rpcname: 'manageRaid', ...adminsOnly
}]

const testAccounts : protoAccount[] = [
    {
        name: 'Rain',
        race: 'Human',
        class: 'Warrior',
        spec: 'Protection',
        rank: 'Guildmaster'
    },{
        name: 'Celinda',
        class: 'Warrior',
        race: 'Night Elf',
        spec: 'Protection',
        rank: 'Officer'
    },{
        name: 'Dagger',
        race: 'Dwarf',
        class: 'Rogue',
        spec: 'Assassination',
        rank: 'Classleader'
    },{
        name: 'Hope',
        class: 'Paladin',
        race: 'Human',
        spec: 'Holy',
        rank: 'Classleader'
    },{
        name: 'Shrekd',
        class: 'Warrior',
        race: 'Dwarf',
        spec: 'Fury',
        rank: 'Classleader'
    },{
        name: 'Teenieweenie',
        class: 'Warlock',
        race: 'Gnome',
        spec: 'Demonology',
        rank: 'Classleader'
    },{
        name: 'Hagibaba',
        class: 'Priest',
        race: 'Human',
        spec: 'Discipline',
        rank: 'Classleader'
    },{
        name: 'Muffinbreak',
        class: 'Mage',
        race: 'Gnome',
        spec: 'Arcane',
        rank: 'Classleader'
    },
]


describe('Frontcraft', () => {
    let auth: Auth,
        adminUser : User,
        server: FrontworkAdmin,
        client : RPCSocket & FrontcraftIfc,
        adminClient : RPCSocket & FrontcraftFeatureIfc,
        raids: Raid[] = [],
        users : {[username in string] : { account: User, character: Character, auth: Auth, signup?:Signup, item?:string }} = {}

    const createAccount = (user: User) => {
        return client.UserManager.createUser(user)
    }

    const createAccountAndUser = async (acc : protoAccount) => {
        const account = await createAccount({
            pwhash: 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', //sha256("a")
            rank: acc.rank,
            username: acc.name
        })
        const auth = await client.UserManager.login(acc.name, 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb')
        const id = await client.CharacterManager.getSpecId(acc.class, acc.spec)
        const char = await client.CharacterManager.createCharacter(auth.token.value, {
            charactername: acc.name,
            specid: id,
            userid: account.id!,
            race: acc.race
        })
        return {
            account: account,
            character: char,
            auth: auth
        }
    }

    before(function (done){
        this.timeout(10000); 

        server = Injector.resolve<FrontworkAdmin>(FrontworkAdmin)
        server.start().then((_server) => {
            
            RPCSocket.makeSocket<FrontcraftIfc>(20000, 'localhost').then(_client => {
                client = _client
                

                createAccount({
                    pwhash: 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', //hash("a")
                    rank: 'ADMIN',
                    username: 'a',
                    currency: 2
                }).then(adminUser => {

                    client.UserManager.login(adminUser.username, 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb').then(auth => {
                        const sock = new RPCSocket(
                            auth.port, 
                            "localhost"
                        )

                        sock.connect<any>(auth.token.value).then(cli => {
                            adminClient = cli
                            sock.hook('kick', () => {
                                console.log("I got kicked");
                            })
                            sock.hook('getUserData', () => auth)
                            sock.hook('navigate', (where:string) => {
                                console.log("Nagivate client to "+where);
                            })
                            sock.on('error', (e) => {
                                console.log('Socket error',  e)
                            })
                            done()
                        })
                    })
                })
            })
        }).catch(done)
    })

    after(()=>{
        client.destroy()
        adminClient.destroy()
        server.stop()
    })

    it('create raids', (done) => {
        let insertRaid = <Raid>{
            description: "Test raid 1",
            title: 'MC',
            start: Date.now().toString()
        }

        adminClient.manageRaid.createRaid(insertRaid).then(() => {
            client.RaidManager.getRaids().then((r)=>{
                if(r[0].title === insertRaid.title 
                && r[0].description === insertRaid.description){
                    raids.push(r[0])
                    done()
                }
            }).catch(done)
        }).catch(done)
    })

    it('create users', (done)=>{
        Promise.all(testAccounts.map(acc => createAccountAndUser(acc))).then(accs => {
            if(accs.length === testAccounts.length){
                accs.forEach(acc => {
                    users[acc.account.username] = acc
                })
                done()
            }
        }).catch(done)
    })

    it('should sign up', (done)=>{
        Promise.all(Object.values(users).map((user) =>
            adminClient.signup.sign(user.auth.token.value, user.character, raids[0], false).catch(done)
        )).then(x => {
            adminClient.signup.getSignups(raids[0]).then(s => {
                if(s.length == testAccounts.length){
                    s.forEach(sign => {
                        users[sign.username].signup = sign
                    })
                    done()
                }else{
                    done("Unexpected number of signups: "+s.length)
                }
            })
        })
    })

    it('calculate priorities', (done)=>{
        const makePrio = async (itemname:string, spec?: Spec, race?:Race, mod:number = 0, description:string = "") => {
            let specid
            if(spec)
                specid = await client.CharacterManager.getSpecId(spec.class, <any>spec.specname)
            
            await adminClient.managePriorities.setPriority(itemname, {
                specid: specid,
                race: race,
                modifier: mod,
                description: description
            })
        }

              
        Promise.all([
            makePrio(
                'Bracers of Arcane Accuracy', 
                {class:'Warlock', specname:'Demonology'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Bracers of Arcane Accuracy', 
                {class:'Warlock', specname:'Affliction'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Bracers of Arcane Accuracy', 
                {class:'Warlock', specname:'Destruction'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Bracers of Arcane Accuracy', 
                {class:'Mage', specname:'Arcane'}, 
                undefined, 1, "hit bias"
            ),
            makePrio(
                'Bracers of Arcane Accuracy', 
                {class:'Mage', specname:'Frost'}, 
                undefined, 1, "hit bias"
            ),
            makePrio(
                'Bracers of Arcane Accuracy', 
                {class:'Mage', specname:'Fire'}, 
                undefined, 1, "hit bias"
            ),

            makePrio(
                'Maladath, Runed Blade of the Black Flight', 
                {class:'Warrior', specname:'Fury'}, 
                'Human', 2, "weapon skill bias"
            ),

            makePrio(
                'Cloak of Firemaw', 
                {class:'Rogue', specname:'Assassination'}, 
                undefined, 2, "agi-to-ap bias"
            ),
            makePrio(
                'Cloak of Firemaw', 
                {class:'Rogue', specname:'Combat'}, 
                undefined, 2, "agi-to-ap bias"
            ),
            makePrio(
                'Cloak of Firemaw', 
                {class:'Rogue', specname:'Subtlety'}, 
                undefined, 2, "agi-to-ap bias"
            ),

            makePrio(
                'Band of Forced Concentration', 
                {class:'Warlock', specname:'Demonology'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Band of Forced Concentration', 
                {class:'Warlock', specname:'Affliction'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Band of Forced Concentration', 
                {class:'Warlock', specname:'Destruction'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Band of Forced Concentration', 
                {class:'Mage', specname:'Arcane'}, 
                undefined, 1, "hit bias"
            ),
            makePrio(
                'Band of Forced Concentration', 
                {class:'Mage', specname:'Frost'}, 
                undefined, 1, "hit bias"
            ),
            makePrio(
                'Band of Forced Concentration', 
                {class:'Mage', specname:'Fire'}, 
                undefined, 1, "hit bias"
            ),

            makePrio(
                'Drake Fang Talisman', 
                {class:'Rogue', specname:'Assassination'}, 
                undefined, 4, "hit bias"
            ),
            makePrio(
                'Drake Fang Talisman', 
                {class:'Rogue', specname:'Combat'}, 
                undefined, 4, "hit bias"
            ),
            makePrio(
                'Drake Fang Talisman', 
                {class:'Rogue', specname:'Subtlety'}, 
                undefined, 4, "hit bias"
            ),
            makePrio(
                'Drake Fang Talisman', 
                {class:'Warrior', specname:'Fury'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Drake Fang Talisman', 
                {class:'Druid', specname:'Feral (DPS)'}, 
                undefined, 2, "hit bias"
            ),

            makePrio(
                'Circle of Applied Force', 
                {class:'Warrior', specname:'Fury'}, 
                undefined, 2, "str-to-ap bias"
            ),
            makePrio(
                'Circle of Applied Force', 
                {class:'Druid', specname:'Feral (DPS)'}, 
                undefined, 2, "str-to-ap bias"
            ),

            makePrio(
                'Empowered Leggings', 
                {class:'Paladin', specname:'Holy'}, 
                undefined, 2, "crit bias"
            ),

            makePrio(
                'Boots of the Shadow Flame', 
                {class:'Rogue', specname:'Assassination'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Boots of the Shadow Flame', 
                {class:'Rogue', specname:'Combat'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Boots of the Shadow Flame', 
                {class:'Rogue', specname:'Subtlety'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Boots of the Shadow Flame', 
                {class:'Druid', specname:'Feral (DPS)'}, 
                undefined, 2, "hit bias"
            ),

            makePrio(
                'Neltharion\'s Tear', 
                {class:'Warlock', specname:'Demonology'}, 
                undefined, 4, "hit bias"
            ),
            makePrio(
                'Neltharion\'s Tear', 
                {class:'Warlock', specname:'Affliction'}, 
                undefined, 4, "hit bias"
            ),
            makePrio(
                'Neltharion\'s Tear', 
                {class:'Warlock', specname:'Destruction'}, 
                undefined, 4, "hit bias"
            ),
            makePrio(
                'Neltharion\'s Tear', 
                {class:'Mage', specname:'Arcane'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Neltharion\'s Tear', 
                {class:'Mage', specname:'Frost'}, 
                undefined, 2, "hit bias"
            ),
            makePrio(
                'Neltharion\'s Tear', 
                {class:'Mage', specname:'Fire'}, 
                undefined, 2, "hit bias"
            ),

            makePrio(
                'Cloak of Draconic Might', 
                {class:'Warrior', specname:'Fury'}, 
                undefined, 2, "str-to-ap bias"
            ),
            makePrio(
                'Cloak of Draconic Might', 
                {class:'Druid', specname:'Feral (DPS)'}, 
                undefined, 2, "str-to-ap bias"
            ),
        ])


        const user = Object.values(users)[0]
        adminClient.managePriorities.setPriority(T1[0], {
            race: user.character.race,
            specid: user.character.specid,
            modifier: 1,
            description:'AAA'
        }).then(() => {
            adminClient.managePriorities.setPriority(T1[0], {
                race: user.character.race,
                specid: undefined,
                modifier: 2,
                description:'BBB'
            }).then(()=>{
                adminClient.managePriorities.setPriority(T1[0], {
                    race: undefined,
                    specid: user.character.specid,
                    modifier: 3,
                    description:'CCC'
                }).then(()=>{
                    client.ItemManager.calculatePriorities(T1[0], user.character).then(sum => {
                        if(sum === 6)
                            done()
                    })
                })
            })
        })
    })

    it('buy token', (done)=>{
        Promise.all(Object.values(users).map(async (user) =>{
            const itemname = T1[0]//T1[Math.floor(T1.length*Math.random())]
            const modifier = await client.ItemManager.calculatePriorities(itemname, user.character)

            const token = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!)
            users[user.account.username].item = itemname
            
            if(!token) return false
            return modifier+1 === token.level
        })).then(success => {
            if(success.reduce((prev, curr)=>prev&&curr, true)) done()
        })
    })

    it('not buy token without currency', (done)=>{
        const user = Object.values(users)[0]
        const itemname = T1[0]
        client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!).then(token => {
            if(!token) 
                done()
            else {
                console.log("Unexpected token", token);
            }
        })
    })

    it('upgrade token', (done)=>{
        const user = Object.values(users)[0]
        const itemname = T1[0]

        adminClient.softreserveCurrency.incrementCurrency(user.account, 2).then(async ()=>{
            const item = await client.ItemManager.getItem(itemname)
            const before = await client.ItemManager.getToken(user.character, item)
            
            if(!before || before.level !== 7) {
                console.log("expected level to be 7", before);
                return
            }
            
            await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!)
            const after = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!)

            if(!after || after.level !== 9) {
                console.log("expected level to be 9", after);
                return
            }
            done()
        })
    })

    it('should buy more tokens', (done) => {
        Promise.all(Object.values(users).map(async (user) => {
            await adminClient.softreserveCurrency.incrementCurrency(user.account, 1)
            return await client.ItemManager
            .buyToken(
                user.auth.token.value, 
                user.character.charactername, 
                T1[Math.floor(T1.length*Math.random())],
                user.signup!
            )
        })).then(_ => {
            done()
        })
    })

    it('can set permissions', (done) => {
        Promise.all(
            defaultPermissions.map(perm => adminClient.modifyPermissions.setPermission(perm)),
        ).then(_ => {
            done()
        })
    })

    it('start raid', (done) => {
        client.RaidManager.getRaids().then((r)=>{
            adminClient.manageRaid.startRaid(raids[0]).then(async data => {
                const dbRaids = await client.RaidManager.getRaids()
                if(dbRaids.length === 0){
                    await client.UserManager.getUser(testAccounts[0].name).then(dbUser => {
                        if(dbUser && dbUser.currency === 1){
                            adminClient.signup.getSignups(raids[0]).then(signups => {
                                if(signups.length === 0){
                                    done()
                                }else{
                                    console.log(signups);
                                }
                            })
                        }
                        else{
                            console.log("Bad user currency", dbUser);
                            
                        }
                    })
                }
            })
        })
    })

    it('reset system', (done) => {
        adminClient.reset.wipeCurrencyAndItems().then(() => {
            client.UserManager.getUser(testAccounts[0].name).then(user => {
                if(user && user.currency === 0){
                    client.ItemManager.getTokens(users[testAccounts[0].name.toLowerCase()].character).then(tokens => {
                        if(tokens.length === 0){
                            done()
                        }else{
                            console.log(tokens);
                        }
                    })
                }else{
                    console.log(user)
                }
            })
        })
        
    })
})