import { Injectable, Injector, NgZone } from "@angular/core";
import {RPCSocket} from 'rpclibrary/js/src/Frontend'

import { Auth, User, _Class, FrontcraftFeatureIfc, SomeOf, FrontcraftIfc, } from '../../../../../backend/Types/Types'
import { CookieService } from 'ngx-cookie-service';
import { Router } from '@angular/router';
import { ShoutMessage } from '../../../../../backend/Components/Shoutbox/Interface';
import { saltedHash } from '../../../../../backend/Util/hash';

@Injectable()
export class ApiService{
    private socket:RPCSocket & FrontcraftIfc;
    private auth:Auth
    private privSocket: RPCSocket & SomeOf<FrontcraftFeatureIfc>

    constructor(
        private injector: Injector,
        private cookieSvc : CookieService,
        private ngZone : NgZone
    ){}

    getUnprivilegedSocket = () : RPCSocket & FrontcraftIfc => this.socket

    private getPrivilegedSocket = async (auth:Auth) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
        if(this.privSocket) return this.privSocket
        if(auth == null) throw new Error("Bad Auth")

        try{
            const sock = new RPCSocket(
                auth.port, 
                window.location.hostname
            )
           
            const authSock = await sock.connect<RPCSocket & SomeOf<FrontcraftFeatureIfc>>(auth.token.value)

            sock.hook('kick', () => {
                console.log("I got kicked");
            })
            sock.hook('getUserData', () => auth)
            sock.hook('navigate', (where:string) => {
                this.ngZone.run( () => {
                    this.injector.get(Router).navigateByUrl(where)
                })
            })
            sock.on('error', (e) => {
                sock.destroy();
                
            })
            
            this.auth = auth
            this.privSocket = authSock
            this.cookieSvc.set('token', JSON.stringify(auth))
            return authSock
        }catch(e){ 
            //login failed
            throw new Error(e)
        }
    }

    get = <K extends (keyof FrontcraftIfc | keyof FrontcraftFeatureIfc)>(feature : K) : K extends keyof FrontcraftIfc?FrontcraftIfc[K]:
                                                                                        K extends keyof FrontcraftFeatureIfc?(FrontcraftFeatureIfc[K] | void): 
                                                                                        never => {
        try{
            //@ts-ignore
            if(this.socket && this.socket[feature]) return this.socket[feature]
            //@ts-ignore
            if(this.privSocket && this.privSocket[feature]) return this.privSocket[feature]
        }catch(e){
            return
        } 
    }

    getCurrentUser = () : User | undefined => {
        return this.auth?this.auth.user:undefined
    }

    login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
        const pwHash = await hash(password)
        const auth = await this.socket.UserManager.login(username, pwHash)

        if(!auth){ 
            await this.logout()
            throw new Error("Login failed")
        }
        const sock = await this.getPrivilegedSocket(auth)
        return sock
    }

    async connectShoutbox(handler) : Promise<Function>{
        const res = await this.get('Shoutbox').subscribe(handler)
        return async (msg: ShoutMessage) => await this.get('Shoutbox').shout(res.uuid, msg)
    }

    logout = async () => {
        this.cookieSvc.set('token', undefined)
        if(this.auth) await this.socket.UserManager.logout(this.auth.user.username, this.auth.token.value)
        if(this.privSocket) this.privSocket.destroy()
        this.privSocket = null
        this.auth  = null
        this.ngZone.run(() => {
            this.injector.get(Router).navigate(['/auth']);
        })
    }

    initialize = async () : Promise<any> => {
        const sock = await RPCSocket.makeSocket<FrontcraftIfc>(20000, window.location.hostname)

        this.socket = sock
        try{
            const cookie = JSON.parse(this.cookieSvc.get('token'))
            
            if(cookie != null) {
                try{
                    const auth = await sock.UserManager.getAuth(cookie.token.value)
                    if(!auth) return sock
                    return await this.getPrivilegedSocket(auth)
                }catch(e){
                    await this.logout()
                    return
                }
            }
        }catch(e){
        }
    }

    getAuth = () => this.auth

    async checkLogin() : Promise<boolean>{
        if(!this.auth) return false
        const valid = this.socket.UserManager.checkToken(this.auth.token.value, this.auth.user.rank)
        if(valid) return true
        await this.logout()
        return false
    }
}

export async function hash(value:string) : Promise<string>{
    return saltedHash(value, "")
}

//angular depenency manager requires this
export function initializeLoginSvc(svc: ApiService): () => Promise<any> {
    return ()=>svc.initialize?svc.initialize():undefined
}