
import { AuthToken, HttpApi, JSONParser } from '@3kles/3kles-corebe';
import { Request, Response, NextFunction } from 'express';
import { Mutex, MutexInterface } from 'async-mutex';
import * as jwt from 'jsonwebtoken';
import * as base64 from 'base-64';

export class AuthService extends AuthToken {
    protected httpAPI: HttpApi;

    private token: { token_type: string, access_token: string };
    private mutex: Mutex;
    private deltaTime: number = +process.env.DELTA_TIME || 1000;

    constructor(params: any) {
        super(params);
        this.mutex = new Mutex();
        this.secretKey = process.env.cs;
        this.httpAPI = new HttpApi('https');
        this.httpAPI.setResponseParser(new JSONParser());
        this.httpAPI.setErrorParser(new JSONParser());
    }

    public async authenticate(req: Request, res: Response, next: NextFunction): Promise<any> {
        try {
            const response = await this.getIONBEServiceToken();
            return res.status(200).json(response);
        } catch (err) {
            res.status(404).json(err);
            return next(err);
        }
    }

    public async checkAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
        try {
            if (req.headers['authorization']) {
                const token = (req.headers['authorization'].split(' ').length > 1) ? req.headers['authorization'].split(' ')[1] : req.headers['authorization'];
                const decodedToken: any = jwt.decode(token, {
                    complete: true
                });
                const expired = Date.now() >= +(decodedToken?.payload?.exp) * 1000;
                if (expired) {
                    const authTokenObj = await this.getIONBEServiceToken();
                    const authToken = this.formatAuthToken(authTokenObj);
                    res.set('authorization', authToken);
                    next();
                } else {
                    next();
                }
            } else {
                const authTokenObj = await this.getIONBEServiceToken();
                const token = this.formatAuthToken(authTokenObj);
                res.set('authorization', token);
                next();
            }
        } catch (err) {
            console.error('[Ion Service]: An error occurred while retrieving the token');
            console.error(err);
            res.status(err.statusCode || 500).json({ error: 'An error occurred while retrieving the token' });
        }
    }

    public async getIONBEServiceToken(): Promise<{ token_type: string, access_token: string }> {
        try {
            await this.mutex.acquire();
            if (!this.token || this.isTokenExpired(this.token)) {
                try {
                    this.token = await this.loadIONBEServiceToken();
                } catch (err) {
                    console.error('[Ion Service]: Error authentication=', err);
                    this.token = null;
                }
            }
            return this.token;
        } catch (err) {
            throw err;
        } finally {
            await this.mutex.release();
        }
    }

    public isTokenExpired(token: any): boolean {
        const decodedToken: any = jwt.decode(this.formatToken(token), {
            complete: true
        });

        if (!decodedToken?.payload) {
            if (+token?.expireTime > 0) {
                return Date.now() >= +token.expireTime;
            }
            return true;
        }
        return Date.now() >= +(decodedToken?.payload?.exp) * 1000;
    }

    public formatAuthToken(tokenObj: any): string {
        if (tokenObj.token_type && tokenObj.access_token) {
            return tokenObj.token_type + ' ' + tokenObj.access_token;
        }
        return tokenObj;
    }

    public formatToken(tokenObj: any): string {
        if (tokenObj.token_type && tokenObj.access_token) {
            return tokenObj.access_token;
        }
        return tokenObj;
    }

    public async loadIONBEServiceToken(): Promise<any> {
        console.log('[Ion Service]: Loading ION Token');
        const ci = process.env.ci;
        const cs = process.env.cs;
        const combine = ci + ":" + cs;
        const cicsbase64 = base64.encode(`${combine}`);
        const saak = encodeURIComponent(process.env.saak);
        const sask = encodeURIComponent(process.env.sask);

        const url = new URL(process.env.pu);
        const hostname = url.hostname;
        const port = (url.host.split(':').length > 1) ? +url.host.split(':')[1] : 443;
        const path = url.pathname + process.env.ot;

        const options1 = {
            method: 'POST',
            hostname,
            port,
            path,
            headers: {
                'Cache-Control': 'no-cache',
                'Authorization': `Basic ${cicsbase64}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            form: false,
            data: `grant_type=password&username=${saak}&password=${sask}`,
            rejectUnauthorized: false
        };

        const response = await this.httpAPI.executeRequest(options1);
        const body = response?.body || {};
        body.expireTime = Date.now() + ((~~response?.body?.expires_in - this.deltaTime) * 1000);
        return response?.body;
    }

}
