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

import { Token, Auth, User, _Class, FrontcraftFeatureIfc, SomeOf, FrontcraftIfc, } from '../../../../../backend/Types/Types'
import { CookieService } from 'ngx-cookie-service';
import { Router } from '@angular/router';

@Injectable()
export class LoginApiService{
    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', () => this.logout())
            sock.hook('getUserData', () => auth)
            sock.hook('navigate', (where:string) => {
                this.ngZone.run(() => {
                    this.injector.get(Router).navigateByUrl(where);
                })
            })
            sock.on('error', (e) => {
                console.log('Socket error',  e)
                this.logout()
            })

            this.auth = auth
            this.privSocket = authSock
            this.cookieSvc.set('token', JSON.stringify(auth))
            return authSock
        }catch(e){ 
            //login failed
            throw new Error(e)
        }
    }

    getFeature = async <K extends keyof FrontcraftFeatureIfc>(feature : K) : Promise<void | FrontcraftFeatureIfc[K]> => {
        try{
            const sock = await this.getPrivilegedSocket(this.auth)
            if(sock[feature]) return <FrontcraftFeatureIfc[K]> sock[feature]
        }catch(e){
            return
        }
    }

    getCurrentUser = () : User | undefined => 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.Authenticator.login(username, pwHash)

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

    logout = async () => {
        this.cookieSvc.set('token', undefined)
        if(this.auth) await this.socket.Authenticator.logout(this.auth.user.name, 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.Authenticator.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 = await this.socket.Authenticator.checkToken(this.auth.token.value, this.auth.user.rank)
        if(valid) return true
        await this.logout()
        return false
    }
}

function str2arraybuf(str:string): ArrayBuffer {
    return new Buffer(str)
  }
  
function buf2hex(buffer) { // buffer is an ArrayBuffer
    return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}

export async function hash(value:string) : Promise<string>{
    const buf = str2arraybuf(value)
    const pwHash = await crypto.subtle.digest('SHA-256', buf);
    return buf2hex(pwHash)
}

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