import axios from 'axios';
import { encode, decode } from 'js-base64';

type JunoPixParams = {
    clientId: string;
    clientSecret: string;
    resourceToken: string;
}

type JunoPixCharge = {
    calendario: {
        expiracao: number;
    };
    devedor: {
        cpf?: string;
        cnpj?: string;
        nome: string;
    },
    valor: {
        original: string;
    },
    chave: string;
    solicitacaoPagador: string;
    infoAdicionais: JuniPixChargeInfoAdd[];
}

type JuniPixChargeInfoAdd = {
    nome: string;
    valor: string;
}

type JunoPixEventType = 'CHARGE_STATUS_CHANGED' | 'TRANSFER_STATUS_CHANGED' | 'PAYMENT_NOTIFICATION' | 'P2P_TRANSFER_STATUS_CHANGED';

export interface JunoPixHeader {
    'Content-Type': string;
    'Authorization': string;
    'X-API-Version': number;
    'X-Resource-Token': string;
    'X-Idempotency-Key'?: string;
}

export interface JunoWebhooks {
    id: string;
    url: string;
    secret: string;
    status: string;
    eventTypes: JunoWebhookEventType[]
}
export interface JunoWebhookEventType {
    id: string;
    name: string;
    label: string;
    status: string;
}

export function JunoPIX({
    clientId,
    clientSecret,
    resourceToken
}: JunoPixParams) {

    const baseUrl = 'https://api.juno.com.br/';

    const accessHeader = async () => {
        const accessToken = await getAccessToken();
        const headers: JunoPixHeader = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + accessToken,
            'X-API-Version': 2,
            'X-Resource-Token': resourceToken,
        };
        return headers;
    }

    const getHttp = async (url: string, headers: any) => {
        return await axios.get(url, { headers });
    }

    const postHttp = async (url: string, headers: any, data?: any) => {
        return await axios.post(url, data, { headers });
    }

    const getBasicCredentials = () => encode(`${clientId}:${clientSecret}`);

    const getAccessToken = async () => {
        const basicCredential = await getBasicCredentials();
        const headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Basic ' + basicCredential
        };

        const data = await postHttp(`${baseUrl}authorization-server/oauth/token?grant_type=client_credentials`, headers);

        if (!data?.data?.access_token) {
            throw new Error('Não foi possível obter o token de acesso da conta.');
        }
        return data.data.access_token;
    }

    /**
     * Configura o webhook
     */
    const webhook = async () => {
        const headers = await accessHeader();

        const create = async (url: string, eventTypes: JunoPixEventType[]) => {
            const ret = await postHttp(`${baseUrl}notifications/webhooks`, headers, { url, eventTypes });
            if (!ret.data.id) {
                throw new Error('Não foi possível registrar o webhook.');
            }
            return ret.data;
        }

        const list = async (ignoreError?: boolean) => {
            const ret = await getHttp(`${baseUrl}notifications/webhooks`, headers);
            if (!ret.data || !ret.data._embedded || !ret.data._embedded.webhooks) {
                if(!ignoreError) {
                    throw new Error('Não foi possível listar o(s) webhook(s).');
                } else {
                    return [];
                }
            }
            const webhooks: JunoWebhooks[] = [];
            await Promise.all(ret.data._embedded.webhooks.map((hook: any) => {
                const webhook: JunoWebhooks = {
                    id: hook.id,
                    secret: hook.url,
                    status: hook.secret,
                    url: hook.status,
                    eventTypes: hook.eventTypes
                }
                webhooks.push(webhook);
            }));
            
            return webhooks;
        }

        const findByEventType = async (event: JunoPixEventType) => {
            const hooks     = await list(true);

            if(hooks && hooks.length > 0) {
                let webhook: JunoWebhookEventType = { id: '',status: '',label: '',name: '' };

                await Promise.all(hooks.map( async (hook) => {
                    await Promise.all(hook.eventTypes.map(eventType => {
                        if(eventType.name.toLocaleLowerCase() === event.toLocaleLowerCase()) {
                            webhook     = eventType;
                        }
                    }))
                }))
                
                return webhook;
            }
            return undefined;
        }

        return { create, findByEventType, list }

    }

    /**
     * Manuseio de cobranças
     * @returns 
     */
    const charge = async () => {

        const create = async (dados: JunoPixCharge) => {
            const headers = await accessHeader();
            const ret = await postHttp(`${baseUrl}pix-api/v2/cob`, headers, dados);
            if (!ret.data.txid) {
                throw new Error('Falha ao gerar cobrança.');
            }
            return ret.data.txid;
        }

        const info = async (txId: string) => {
            const headers = await accessHeader();
            const ret = await getHttp(`${baseUrl}pix-api/v2/cob/${txId}`, headers);
            if (!ret.data.txid) {
                throw new Error('Não foi possível encontrar esta cobrança.');
            }
            const data = ret.data;
            return {
                txId: data.txid,
                status: data.status,
                calendario: data.calendario,
                valor: data.valor.original,
                referencia: data.solicitacaoPagador
            };
        }

        const pix = async (txId: string) => {
            const headers = await accessHeader();
            const ret = await getHttp(`${baseUrl}pix-api/qrcode/v2/${txId}/imagem`, headers);
            if (!ret.data.qrcodeBase64) {
                throw new Error('Não foi possível gerar o QR Code desta cobrança.');
            }
            const data = ret.data;
            const pixDados = decode(data.qrcodeBase64);
            return { pixDados: pixDados };
        }

        return { create, info, pix }
    }

    return {
        webhook,
        charge
    }

}