import { lib as KeetaNetLib } from '@keetanetwork/keetanet-client';
import * as CurrencyInfo from '@keetanetwork/currency-info';
import { createIs } from 'typia';
import { getDefaultResolver } from '../../config.js';
import { Certificate as KYCCertificate } from '../../lib/certificates.js';
import { verifySignedData, generateSignedData } from './common.js';
import crypto from '../../lib/utils/crypto.js';
import { validateURL } from '../../lib/utils/url.js';
const PARANOID = true;
const isKeetaKYCAnchorCreateVerificationResponse = (() => { const _io0 = input => true === input.ok && "string" === typeof input.id && ("object" === typeof input.expectedCost && null !== input.expectedCost && _io1(input.expectedCost)) && "string" === typeof input.webURL; const _io1 = input => "string" === typeof input.min && "string" === typeof input.max && ("string" === typeof input.token && (RegExp(/^keeta_am(.*)/).test(input.token) || RegExp(/^keeta_an(.*)/).test(input.token) || RegExp(/^keeta_ao(.*)/).test(input.token) || RegExp(/^keeta_ap(.*)/).test(input.token) || RegExp(/^tyblocks_am(.*)/).test(input.token) || RegExp(/^tyblocks_an(.*)/).test(input.token) || RegExp(/^tyblocks_ao(.*)/).test(input.token) || RegExp(/^tyblocks_ap(.*)/).test(input.token))); const _io2 = input => false === input.ok && "string" === typeof input.error; const _iu0 = input => (() => {
    if (true === input.ok)
        return _io0(input);
    else if (false === input.ok)
        return _io2(input);
    else
        return false;
})(); return input => "object" === typeof input && null !== input && _iu0(input); })();
const isKeetaKYCAnchorGetCertificateResponse = (() => { const _io0 = input => true === input.ok && (Array.isArray(input.results) && input.results.every(elem => "object" === typeof elem && null !== elem && _io1(elem))); const _io1 = input => "string" === typeof input.certificate && (undefined === input.intermediates || Array.isArray(input.intermediates) && input.intermediates.every(elem => "string" === typeof elem)); const _io2 = input => false === input.ok && "string" === typeof input.error; const _iu0 = input => (() => {
    if (true === input.ok)
        return _io0(input);
    else if (false === input.ok)
        return _io2(input);
    else
        return false;
})(); return input => "object" === typeof input && null !== input && _iu0(input); })();
async function getEndpoints(resolver, request) {
    const response = await resolver.lookup('kyc', {
        countryCodes: request.countryCodes
    });
    if (response === undefined) {
        return (null);
    }
    const serviceInfoPromises = Object.entries(response).map(async function ([id, serviceInfo]) {
        const countryCodesPromises = (await serviceInfo.countryCodes?.('array'))?.map(async function (countryCode) {
            return (new CurrencyInfo.Country(CurrencyInfo.Country.assertCountryCode(await countryCode('string'))));
        });
        let countryCodes;
        if (countryCodesPromises !== undefined) {
            const countryCodesResults = await Promise.allSettled(countryCodesPromises);
            countryCodes = countryCodesResults.map(function (result) {
                if (result.status === 'fulfilled') {
                    return (result.value);
                }
                throw (result.reason);
            });
        }
        const operations = await serviceInfo.operations('object');
        const operationsFunctions = {};
        for (const [key, operation] of Object.entries(operations)) {
            if (operation === undefined) {
                continue;
            }
            Object.defineProperty(operationsFunctions, key, {
                get: async function () {
                    const url = await operation('string');
                    return (function (params) {
                        let substitutedURL = url;
                        for (const [paramKey, paramValue] of Object.entries(params ?? {})) {
                            substitutedURL = substitutedURL.replace(`{${paramKey}}`, encodeURIComponent(paramValue));
                        }
                        return (validateURL(substitutedURL));
                    });
                },
                enumerable: true,
                configurable: true
            });
        }
        return ([
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            id,
            {
                countryCodes: countryCodes,
                operations: operationsFunctions,
                ca: async function () {
                    const certificatePEM = await serviceInfo.ca?.('string');
                    const certificate = new KYCCertificate(certificatePEM);
                    return (certificate);
                }
            }
        ]);
    });
    const retval = Object.fromEntries(await Promise.all(serviceInfoPromises));
    return (retval);
}
/**
 * Represents an in-progress KYC verification request.
 */
class KeetaKYCVerification {
    providerID;
    id;
    serviceInfo;
    request;
    logger;
    client;
    response;
    constructor(args, response) {
        this.providerID = args.id;
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
        this.id = response.id;
        this.serviceInfo = args.serviceInfo;
        this.request = args.request;
        this.client = args.client;
        this.logger = args.logger;
        this.response = response;
        this.logger?.debug(`Created KYC verification for provider ID: ${this.providerID}, request: ${JSON.stringify(args.request)}, response: ${JSON.stringify(response)}`);
    }
    static async start(args) {
        args.logger?.debug(`Starting KYC verification for provider ID: ${args.id}, request: ${JSON.stringify(args.request)}`);
        const endpoints = args.operations;
        const createVerification = endpoints.createVerification?.();
        if (createVerification === undefined) {
            throw (new Error('KYC verification service does not support createVerification operation'));
        }
        const requestInformation = await fetch(createVerification, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                request: args.request
            })
        });
        const requestInformationJSON = await requestInformation.json();
        if (!isKeetaKYCAnchorCreateVerificationResponse(requestInformationJSON)) {
            throw (new Error(`Invalid response from KYC verification service: ${JSON.stringify(requestInformationJSON)}`));
        }
        if (!requestInformationJSON.ok) {
            throw (new Error(`KYC verification request failed: ${requestInformationJSON.error}`));
        }
        args.logger?.debug(`KYC verification request successful, request ID ${requestInformationJSON.id}`);
        return (new KeetaKYCVerification(args, requestInformationJSON));
    }
    get expectedCost() {
        return (this.response.expectedCost);
    }
    get webURL() {
        return (validateURL(this.response.webURL));
    }
    async getProviderIssuerCertificate() {
        return (await this.serviceInfo.ca());
    }
    getCertificates() {
        return (this.client.getCertificates(this.providerID, {
            id: this.id,
            ...this.request
        }));
    }
}
/**
 * Represents the KYC operations for a specific provider
 */
class KeetaKYCProvider {
    id;
    serviceInfo;
    request;
    logger;
    client;
    operations;
    cachedCA;
    constructor(args) {
        this.id = args.id;
        this.serviceInfo = args.serviceInfo;
        this.request = args.request;
        this.client = args.client;
        this.operations = args.operations;
        this.logger = args.logger;
        this.logger?.debug(`Created KYC verification for provider ID: ${args.id}, request: ${JSON.stringify(args.request)}`);
    }
    async countryCodes() {
        return (this.serviceInfo.countryCodes);
    }
    async ca() {
        if (this.cachedCA !== undefined) {
            return (this.cachedCA);
        }
        this.cachedCA = await this.serviceInfo.ca();
        return (this.cachedCA);
    }
    async startVerification() {
        return (await KeetaKYCVerification.start({
            id: this.id,
            serviceInfo: this.serviceInfo,
            request: this.request,
            client: this.client,
            operations: this.operations,
            logger: this.logger
        }));
    }
}
class KeetaKYCAnchorClient {
    resolver;
    id;
    logger;
    constructor(client, config = {}) {
        this.resolver = config.resolver ?? getDefaultResolver(client, config);
        this.id = config.id ?? crypto.randomUUID();
        this.logger = config.logger;
    }
    async createVerification(request) {
        const signedData = await generateSignedData(request.account.assertAccount());
        if (PARANOID) {
            const check = await verifySignedData({ account: request.account.publicKeyString.get(), signed: signedData });
            if (!check) {
                throw (new Error('Failed to verify signed data'));
            }
        }
        const signedRequest = {
            ...request,
            account: request.account.publicKeyString.get(),
            signed: signedData
        };
        const endpoints = await getEndpoints(this.resolver, signedRequest);
        if (endpoints === null) {
            throw (new Error('No KYC endpoints found for the given criteria'));
        }
        const validEndpoints = await Promise.allSettled(Object.entries(endpoints).map(async ([id, serviceInfo]) => {
            const endpoints = serviceInfo.operations;
            /*
             * Verify that we have the required operations
             * available to perform a KYC verification.
             */
            const createVerification = await endpoints.createVerification;
            const getCertificates = await endpoints.getCertificates;
            if (createVerification === undefined || getCertificates === undefined) {
                this.logger?.warn(`KYC verification provider ${id} does not support required operations (createVerification, getCertificates)`);
                return (null);
            }
            /*
             * We can safely cast the ID to a ProviderID because it's a branded type
             * for this specific type
             */
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            const providerID = id;
            return (new KeetaKYCProvider({
                id: providerID,
                serviceInfo: serviceInfo,
                request: signedRequest,
                client: this,
                logger: this.logger,
                operations: {
                    createVerification,
                    getCertificates
                }
            }));
        }));
        /*
         * Filter out any endpoints that were not able to be resolved
         * or that did not have the required operations.
         */
        const retval = validEndpoints.map(function (result) {
            if (result.status !== 'fulfilled') {
                return (null);
            }
            if (result.value === null) {
                return (null);
            }
            return (result.value);
        }).filter(function (result) {
            return (result !== null);
        });
        if (retval.length === 0) {
            throw (new Error('No valid KYC verification endpoints found'));
        }
        return (retval);
    }
    async getCertificates(providerID, request) {
        const endpoints = await getEndpoints(this.resolver, request);
        if (endpoints === null) {
            throw (new Error('No KYC endpoints found for the given criteria'));
        }
        const providerEndpoints = endpoints[providerID];
        if (providerEndpoints === undefined) {
            throw (new Error(`No KYC endpoints found for provider ID: ${providerID}`));
        }
        const requestID = request.id;
        const operations = providerEndpoints.operations;
        const getCertificate = (await operations.getCertificates)?.({ id: requestID });
        if (getCertificate === undefined) {
            throw (new Error('internal error: KYC verification service does not support getCertificate operation'));
        }
        const response = await fetch(getCertificate, {
            method: 'GET',
            headers: {
                'Accept': 'application/json'
            }
        });
        /*
         * Handle retryable errors by passing them up to the caller to
         * retry.
         */
        if (response.status === 404) {
            return ({
                ok: false,
                retryAfter: 500,
                reason: 'Certificate not found'
            });
        }
        /*
         * Handle other errors as fatal errors that should not be retried.
         */
        if (!response.ok) {
            throw (new Error(`Failed to get certificate: ${response.statusText}`));
        }
        const responseJSON = await response.json();
        if (!isKeetaKYCAnchorGetCertificateResponse(responseJSON)) {
            throw (new Error(`Invalid response from KYC certificate service: ${JSON.stringify(responseJSON)}`));
        }
        if (!responseJSON.ok) {
            throw (new Error(`KYC certificate request failed: ${responseJSON.error}`));
        }
        return ({
            ok: true,
            results: responseJSON.results.map(function (result) {
                const intermediates = result.intermediates?.map(function (intermediate) {
                    return (new KeetaNetLib.Utils.Certificate.Certificate(intermediate));
                });
                let intermediatesSet;
                if (intermediates !== undefined && intermediates.length > 0) {
                    intermediatesSet = new Set(intermediates);
                }
                return ({
                    certificate: new KYCCertificate(result.certificate),
                    intermediates: intermediatesSet
                });
            })
        });
    }
    async getSupportedCountries() {
        return (await this.resolver.listSupportedKYCCountries());
    }
    async getEstimate(..._ignore_args) {
        throw (new Error('not implemented'));
    }
    async checkLocality(..._ignore_args) {
        throw (new Error('not implemented'));
    }
}
export default KeetaKYCAnchorClient;
//# sourceMappingURL=client.js.map