import { KeetaAnchorMetadataServer } from '../../lib/anchor-metadata-server.js';
import { KeetaNet } from '../../client/index.js';
import { KeetaAnchorUserError } from '../../lib/error.js';
import { assertKeetaAssetMovementAnchorCreatePersistentForwardingRequest, assertKeetaAssetMovementAnchorCreatePersistentForwardingResponse, assertKeetaAssetMovementAnchorInitiateTransferRequest, assertKeetaAssetMovementAnchorInitiateTransferResponse, assertKeetaAssetMovementAnchorGetTransferStatusResponse, assertKeetaAssetMovementAnchorlistTransactionsRequest, assertKeetaAssetMovementAnchorlistPersistentForwardingTransactionsResponse, assertKeetaAssetMovementAnchorListPersistentForwardingRequest, assertKeetaAssetMovementAnchorListPersistentForwardingResponse, assertKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateRequest, assertKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateResponse, getKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateRequestSigningData, assertKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateRequest, assertKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateResponse, getKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateRequestSigningData, assertKeetaAssetMovementAnchorListForwardingAddressTemplateRequest, assertKeetaAssetMovementAnchorListForwardingAddressTemplateResponse, getKeetaAssetMovementAnchorListForwardingAddressTemplateRequestSigningData, getKeetaAssetMovementAnchorGetTransferStatusRequestSigningData, getKeetaAssetMovementAnchorInitiateTransferRequestSigningData, assertKeetaAssetMovementAnchorShareKYCResponse, assertKeetaAssetMovementAnchorShareKYCRequest, getKeetaAssetMovementAnchorShareKYCRequestSigningData, convertAssetLocationToString, commonJSONStringify, getKeetaAssetMovementAnchorCreatePersistentForwardingRequestSigningData, getKeetaAssetMovementAnchorlistTransactionsRequestSigningData, getKeetaAssetMovementAnchorListPersistentForwardingRequestSigningData, getKeetaAssetMovementAnchorExecuteTransferRequestSigningData, assertKeetaAssetMovementAnchorExecuteTransferRequest, assertKeetaAssetMovementAnchorExecuteTransferResponse, getKeetaAssetMovementAnchorSimulateTransferRequestSigningData, assertKeetaAssetMovementAnchorSimulateTransferRequest, assertKeetaAssetMovementAnchorSimulateTransferResponse } from './common.js';
import { VerifySignedData } from '../../lib/utils/signing.js';
import { assertHTTPSignedField, parseSignatureFromURL } from '../../lib/http-server/common.js';
;
function serializeTransactionResponse(transaction) {
    return ({
        ...transaction,
        from: {
            ...transaction.from,
            location: convertAssetLocationToString(transaction.from.location)
        },
        to: {
            ...transaction.to,
            location: convertAssetLocationToString(transaction.to.location)
        }
    });
}
function serializePersistentAddressTemplateResponse(template) {
    return ({
        ...template,
        location: convertAssetLocationToString(template.location)
    });
}
export class KeetaNetAssetMovementAnchorHTTPServer extends KeetaAnchorMetadataServer {
    homepage;
    assetMovement;
    constructor(config) {
        super(config);
        this.homepage = config.homepage ?? '';
        this.assetMovement = config.assetMovement;
    }
    async initRoutes(config) {
        const routes = {};
        /**
         * If a homepage is provided, setup the route for it
         */
        if ('homepage' in config) {
            routes['GET /'] = async function () {
                let homepageData;
                if (typeof config.homepage === 'string') {
                    homepageData = config.homepage;
                }
                else {
                    if (!config.homepage) {
                        throw (new Error('internal error: No homepage function provided'));
                    }
                    homepageData = await config.homepage();
                }
                return ({
                    output: homepageData,
                    contentType: 'text/html'
                });
            };
        }
        function addRoute(input) {
            const handler = config.assetMovement[input.handlerName];
            if (handler === undefined) {
                return;
            }
            if (typeof handler !== 'function') {
                throw (new Error(`internal error: handler for ${String(input.handlerName)} is not a function`));
            }
            const authenticationRequired = config.assetMovement.authenticationRequired === true && (input.overrideAllowUnauthenticated !== true);
            routes[`${input.method} /api/${input.pathName ?? input.handlerName}`] = async function (params, postData, _ignore_headers, url) {
                let request;
                if (input.method === 'GET') {
                    // For GET requests, we do not expect a body
                    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                    request = undefined;
                }
                else {
                    if (!input.assertRequest) {
                        throw (new Error('input.assertRequest is required when method = POST'));
                    }
                    request = input.assertRequest(postData);
                }
                let account = null;
                if (authenticationRequired || (request !== undefined && 'signed' in request)) {
                    let signed;
                    if (input.getSignatureFieldAccountFromRequest !== undefined) {
                        const parsed = input.getSignatureFieldAccountFromRequest({ body: postData, url });
                        if (!parsed.account || !parsed.signedField) {
                            throw (new KeetaAnchorUserError('Missing authentication information'));
                        }
                        account = parsed.account;
                        signed = parsed.signedField;
                    }
                    else if (request) {
                        if (!('account' in request) || !('signed' in request)) {
                            throw (new KeetaAnchorUserError('Missing authentication information'));
                        }
                        if (typeof request.account !== 'string') {
                            throw (new KeetaAnchorUserError('Invalid account public key'));
                        }
                        account = KeetaNet.lib.Account.fromPublicKeyString(request.account).assertAccount();
                        signed = assertHTTPSignedField(request.signed);
                    }
                    else {
                        throw (new Error('when request is not defined, getSignatureFieldAccountFromRequest must be provided'));
                    }
                    const signable = input.getSigningData ? input.getSigningData(request, params) : [];
                    const valid = await VerifySignedData(account, signable, signed);
                    if (!valid) {
                        throw (new KeetaAnchorUserError('Invalid signature'));
                    }
                }
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                let parsedRequest;
                if (input.parseRequestToArgs) {
                    parsedRequest = input.parseRequestToArgs({ body: request, params, url, account: account ?? null });
                }
                else {
                    parsedRequest = [request, account];
                }
                // @ts-ignore
                // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
                const result = await handler(...parsedRequest);
                if ('ok' in result && (result.ok !== undefined && result.ok !== true)) {
                    throw (new Error('internal error: response serialization must not have ok field'));
                }
                const resp = input.assertResponse({ ...result, ok: true });
                let serialized;
                if (input.serializeResponse) {
                    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                    serialized = input.serializeResponse(resp);
                }
                else {
                    serialized = resp;
                }
                return ({
                    output: commonJSONStringify(serialized)
                });
            };
        }
        addRoute({
            method: 'POST',
            handlerName: 'createPersistentForwarding',
            assertRequest: assertKeetaAssetMovementAnchorCreatePersistentForwardingRequest,
            assertResponse: assertKeetaAssetMovementAnchorCreatePersistentForwardingResponse,
            getSigningData: getKeetaAssetMovementAnchorCreatePersistentForwardingRequestSigningData,
            serializeResponse(data) {
                return ({
                    ...data,
                    sourceLocation: data.sourceLocation ? convertAssetLocationToString(data.sourceLocation) : undefined,
                    destinationLocation: data.destinationLocation ? convertAssetLocationToString(data.destinationLocation) : undefined
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'listPersistentForwarding',
            assertRequest: assertKeetaAssetMovementAnchorListPersistentForwardingRequest,
            assertResponse: assertKeetaAssetMovementAnchorListPersistentForwardingResponse,
            getSigningData: getKeetaAssetMovementAnchorListPersistentForwardingRequestSigningData,
            serializeResponse(data) {
                return ({
                    ...data,
                    addresses: data.addresses.map(addr => ({
                        ...addr,
                        sourceLocation: addr.sourceLocation ? convertAssetLocationToString(addr.sourceLocation) : undefined,
                        destinationLocation: addr.destinationLocation ? convertAssetLocationToString(addr.destinationLocation) : undefined
                    }))
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'initiateTransfer',
            getSigningData: getKeetaAssetMovementAnchorInitiateTransferRequestSigningData,
            assertRequest: assertKeetaAssetMovementAnchorInitiateTransferRequest,
            assertResponse: assertKeetaAssetMovementAnchorInitiateTransferResponse,
            serializeResponse(data) {
                return ({
                    ...data,
                    instructionChoices: data.instructionChoices.map(function (choice) {
                        const ret = { ...choice };
                        if ('location' in ret) {
                            ret.location = convertAssetLocationToString(ret.location);
                        }
                        return (ret);
                    })
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'simulateTransfer',
            overrideAllowUnauthenticated: true,
            getSigningData: getKeetaAssetMovementAnchorSimulateTransferRequestSigningData,
            assertRequest: assertKeetaAssetMovementAnchorSimulateTransferRequest,
            assertResponse: assertKeetaAssetMovementAnchorSimulateTransferResponse,
            serializeResponse(data) {
                return ({
                    ...data,
                    instructionChoices: data.instructionChoices.map(function (choice) {
                        const ret = { ...choice };
                        if ('location' in ret) {
                            ret.location = convertAssetLocationToString(ret.location);
                        }
                        return (ret);
                    })
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'executeTransfer',
            pathName: 'executeTransfer/:id',
            getSigningData: function (data, params) {
                const id = params.get('id');
                if (typeof id !== 'string' || id.length === 0) {
                    throw (new KeetaAnchorUserError('Missing or invalid id parameter'));
                }
                return (getKeetaAssetMovementAnchorExecuteTransferRequestSigningData({
                    id: id,
                    ...data
                }));
            },
            parseRequestToArgs: ({ body, params }) => {
                const id = params.get('id');
                if (typeof id !== 'string' || id.length === 0) {
                    throw (new KeetaAnchorUserError('Missing or invalid id parameter'));
                }
                if (!body) {
                    throw (new KeetaAnchorUserError('Missing or invalid request body'));
                }
                return [{ id, ...body }];
            },
            assertRequest: assertKeetaAssetMovementAnchorExecuteTransferRequest,
            assertResponse: assertKeetaAssetMovementAnchorExecuteTransferResponse,
            serializeResponse(data) {
                return ({
                    ...data,
                    transaction: serializeTransactionResponse(data.transaction)
                });
            }
        });
        addRoute({
            method: 'GET',
            handlerName: 'getTransferStatus',
            pathName: 'getTransferStatus/:id',
            assertRequest: (input) => {
                if (input !== undefined) {
                    throw (new KeetaAnchorUserError('No body expected for getTransferStatus'));
                }
                return (undefined);
            },
            assertResponse: assertKeetaAssetMovementAnchorGetTransferStatusResponse,
            getSignatureFieldAccountFromRequest: ({ url }) => parseSignatureFromURL(url),
            parseRequestToArgs: ({ params, account }) => {
                const id = params.get('id');
                if (typeof id !== 'string' || id.length === 0) {
                    throw (new KeetaAnchorUserError('Missing or invalid id parameter'));
                }
                return [id, account];
            },
            getSigningData(_ignore_request, params) {
                const id = params.get('id');
                if (typeof id !== 'string' || id.length === 0) {
                    throw (new KeetaAnchorUserError('Missing or invalid id parameter'));
                }
                return (getKeetaAssetMovementAnchorGetTransferStatusRequestSigningData({ id }));
            },
            serializeResponse(data) {
                return ({
                    ...data,
                    transaction: serializeTransactionResponse(data.transaction)
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'listTransactions',
            assertRequest: assertKeetaAssetMovementAnchorlistTransactionsRequest,
            assertResponse: assertKeetaAssetMovementAnchorlistPersistentForwardingTransactionsResponse,
            getSigningData: getKeetaAssetMovementAnchorlistTransactionsRequestSigningData,
            serializeResponse(data) {
                return ({
                    ...data,
                    transactions: data.transactions.map(tx => serializeTransactionResponse(tx))
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'initiatePersistentForwardingTemplate',
            assertRequest: assertKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateRequest,
            assertResponse: assertKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateResponse,
            getSigningData: getKeetaAssetMovementAnchorInitiatePersistentForwardingAddressTemplateRequestSigningData
        });
        addRoute({
            method: 'POST',
            handlerName: 'createPersistentForwardingTemplate',
            assertRequest: assertKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateRequest,
            assertResponse: assertKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateResponse,
            getSigningData: getKeetaAssetMovementAnchorCreatePersistentForwardingAddressTemplateRequestSigningData,
            serializeResponse(data) {
                return ({
                    ...data,
                    ...(serializePersistentAddressTemplateResponse(data))
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'listPersistentForwardingTemplate',
            assertRequest: assertKeetaAssetMovementAnchorListForwardingAddressTemplateRequest,
            assertResponse: assertKeetaAssetMovementAnchorListForwardingAddressTemplateResponse,
            getSigningData: getKeetaAssetMovementAnchorListForwardingAddressTemplateRequestSigningData,
            serializeResponse(data) {
                return ({
                    ...data,
                    templates: data.templates.map(template => serializePersistentAddressTemplateResponse(template))
                });
            }
        });
        addRoute({
            method: 'POST',
            handlerName: 'shareKYC',
            assertRequest: assertKeetaAssetMovementAnchorShareKYCRequest,
            assertResponse: assertKeetaAssetMovementAnchorShareKYCResponse,
            getSigningData: getKeetaAssetMovementAnchorShareKYCRequestSigningData
        });
        return (routes);
    }
    async buildServiceMetadata() {
        const operations = {};
        const routes = [
            'initiateTransfer',
            'simulateTransfer',
            'listTransactions',
            'initiatePersistentForwardingTemplate',
            'createPersistentForwardingTemplate',
            'listPersistentForwardingTemplate',
            'createPersistentForwarding',
            'listPersistentForwarding',
            'shareKYC',
            ['getTransferStatus', 'getTransferStatus/{id}'],
            ['executeTransfer', 'executeTransfer/{id}']
        ];
        for (const routeInput of routes) {
            let op;
            let url;
            if (Array.isArray(routeInput)) {
                op = routeInput[0];
                url = routeInput[1];
            }
            else {
                op = routeInput;
                url = routeInput;
            }
            if (this.assetMovement[op] !== undefined) {
                const computedURL = (new URL(`/api/${op}`, this.url)).toString() + url.slice(op.length);
                if (this.assetMovement.authenticationRequired) {
                    operations[op] = {
                        url: computedURL,
                        options: { authentication: { method: 'keeta-account', type: 'required' } }
                    };
                }
                else {
                    operations[op] = computedURL;
                }
            }
        }
        if (Object.keys(operations).length === 0) {
            throw (new KeetaAnchorUserError('No operations are supported on this server'));
        }
        return ({
            ...(this.assetMovement.legal ? { legal: this.assetMovement.legal } : {}),
            ...(this.assetMovement.locationMetadata ? { locationMetadata: this.assetMovement.locationMetadata } : {}),
            operations: operations,
            supportedAssets: this.assetMovement.supportedAssets
        });
    }
}
//# sourceMappingURL=server.js.map