/**
 * Custom Interceptor: Of Context Params (SFA / Open Finance Chile)
 *
 * Resolve, a partir do path do recurso chamado, o `type` RAR (RFC 9396) e as
 * `actions` que aquele endpoint exige, e injeta esses valores como context
 * variables (`context-authorization-type` / `context-required-actions`) para o
 * interceptor `of-consent-validation` consumir logo em seguida.
 *
 * Motivação: centralizar o mapeamento recurso -> (type, actions) num único
 * ponto, em vez de configurar as context variables manualmente em cada
 * API/operação no API Manager. O mapeamento cobre todas as APIs consent-guarded
 * do SFA (base paths e sub-rotas conforme os specs do dev-portal CMF):
 *   - Produtos/transações (GET): Accounts, CreditCardAccounts, Loans, Investments,
 *     Insurances, Customers (customer/pn|pj), Resources, PaymentReports.
 *   - Iniciação de pagos (POST): SinglePayments, ScheduledPayments,
 *     RecurringPayments, VariableRecurringPayments, FundsConfirmation.
 * Types/actions seguem o vocabulário do AS (SfaRarTypeRegistry / RAR+GM guide
 * §8.1.1). APIs open-data (canales, opendata-*) e grant management (/grants) NÃO
 * são mapeadas de propósito — não exigem consentimento RAR.
 *
 * Execution Point: Request (deve rodar ANTES do of-consent-validation; a ordem
 *   típica do flow é: certificate-client-metadata -> access-token-authorization
 *   -> of-context-params -> of-consent-validation).
 *
 * Não depende do token: resolve tudo pelo path. Endpoints de dados são GET, por
 * isso o casamento é por path; recursos de escrita (ex.: pagamentos, POST) devem
 * casar também por método — ver comentário em RESOURCE_ROUTES.
 *
 * Variáveis de CONTEXTO (contextVariables):
 *   - context-authorization-type  : (saída) type RAR resolvido. Se já vier
 *                                   configurada manualmente no recurso, é
 *                                   respeitada (override manual) e o interceptor
 *                                   não sobrescreve.
 *   - context-required-actions    : (saída) CSV das actions exigidas pelo endpoint.
 *   - context-params-strict       : (opcional, entrada) 'true' => path não mapeado
 *                                   responde 500 (recurso sem mapeamento = misconfig).
 *                                   Default: apenas registra aviso e segue (deixa o
 *                                   of-consent-validation decidir).
 *
 * @version 1.0.0
 * @author adelino.avelino@sensedia.com
 */

const VERSION = '1.0.0'

const CustomHeaderNames = {
    FAPI_INTERACTION_ID: 'x-fapi-interaction-id'
}

const ContextVariablesNames = {
    AUTHORIZATION_TYPE: 'context-authorization-type',
    REQUIRED_ACTIONS: 'context-required-actions',
    STRICT: 'context-params-strict'
}

const ERROR_TYPE_BASE = 'https://api.sfa-chile.cl/errors/'

const Errors = {
    INTERNAL: {
        status: 500,
        code: 'SFA_ERR_INTERNAL',
        title: 'Internal Server Error',
        typeUri: ERROR_TYPE_BASE + 'internal-error',
        detail: 'Ocurrió un error en el gateway de la API'
    }
}

/*
 * Tabela de rotas: primeira que casar vence, então as sub-rotas mais específicas
 * (balance/transactions/...) vêm ANTES da genérica (lista/detalhe) de cada type.
 * `re` casa o path (sem query string). Um endpoint pode exigir mais de uma action
 * — nesse caso use `actions: ['A', 'B']`.
 *
 * Para recursos de escrita (pagamentos), adicione um campo `method` e cheque-o em
 * RouteResolver.resolve (o $call.request expõe o verbo HTTP).
 */
const RESOURCE_ROUTES = [
    // ================= Productos y transacciones (lectura / GET) =================

    // ---- Accounts  (/accounts/v1) ----
    { re: /\/accounts\/v\d+\/accounts\/[^/]+\/balance\b/i, type: 'Accounts', actions: ['ReadAccountsBalance'] },
    { re: /\/accounts\/v\d+\/accounts\/[^/]+\/current-overdraft-limit\b/i, type: 'Accounts', actions: ['ReadAccountsCurrentOverdraftLimit'] },
    { re: /\/accounts\/v\d+\/accounts\/[^/]+\/overdraft\b/i, type: 'Accounts', actions: ['ReadAccountsOverdraft'] },
    { re: /\/accounts\/v\d+\/accounts\/[^/]+\/transactions\b/i, type: 'Accounts', actions: ['ReadAccountsTransactions'] },
    { re: /\/accounts\/v\d+\/accounts(?:\/[^/]+)?\/?$/i, type: 'Accounts', actions: ['ReadAccounts'] },

    // ---- CreditCardAccounts  (/credit-card-accounts/v1) ----
    { re: /\/credit-card-accounts\/v\d+\/accounts\/[^/]+\/current-balance\b/i, type: 'CreditCardAccounts', actions: ['ReadCreditCardCurrentBalance'] },
    { re: /\/credit-card-accounts\/v\d+\/accounts\/[^/]+\/balance\b/i, type: 'CreditCardAccounts', actions: ['ReadCreditCardBalance'] },
    { re: /\/credit-card-accounts\/v\d+\/accounts\/[^/]+\/limit\b/i, type: 'CreditCardAccounts', actions: ['ReadCreditCardLimit'] },
    { re: /\/credit-card-accounts\/v\d+\/accounts\/[^/]+\/transactions\b/i, type: 'CreditCardAccounts', actions: ['ReadCreditCardTransactions'] },
    { re: /\/credit-card-accounts\/v\d+\/accounts(?:\/[^/]+)?\/?$/i, type: 'CreditCardAccounts', actions: ['ReadCreditCardAccounts'] },

    // ---- Loans / operaciones de crédito  (/loans/v1) ----
    { re: /\/loans\/v\d+\/loans\/[^/]+\/balance\b/i, type: 'Loans', actions: ['ReadLoansBalance'] },
    { re: /\/loans\/v\d+\/loans\/[^/]+\/current-transactions\b/i, type: 'Loans', actions: ['ReadLoansCurrentTransactions'] },
    { re: /\/loans\/v\d+\/loans(?:\/[^/]+)?\/?$/i, type: 'Loans', actions: ['ReadLoans'] },

    // ---- Investments / instrumentos de inversión  (/investments/v1) ----
    { re: /\/investments\/v\d+\/investments\/[^/]+\/balance\b/i, type: 'Investments', actions: ['ReadInvestmentsBalance'] },
    { re: /\/investments\/v\d+\/investments\/[^/]+\/transactions\b/i, type: 'Investments', actions: ['ReadInvestmentsTransactions'] },
    { re: /\/investments\/v\d+\/investments(?:\/[^/]+)?\/?$/i, type: 'Investments', actions: ['ReadInvestments'] },

    // ---- Insurances / pólizas de seguro  (/insurances/v1) ----
    { re: /\/insurances\/v\d+\/insurances\/[^/]+\/transactions\b/i, type: 'Insurances', actions: ['ReadAccountsInsuranceTransactions'] },
    { re: /\/insurances\/v\d+\/insurances(?:\/[^/]+)?\/?$/i, type: 'Insurances', actions: ['ReadAccountsInsurance'] },

    // ---- Customers / enrolamiento  (/customers/v1) ----
    { re: /\/customers\/v\d+\/customer\/pn\b/i, type: 'Customers', actions: ['ReadCustomersPn'] },
    { re: /\/customers\/v\d+\/customer\/pj\b/i, type: 'Customers', actions: ['ReadCustomersPj'] },

    // ---- Resources / recursos  (/resources/v1) ----
    { re: /\/resources\/v\d+\/resources\b/i, type: 'Resources', actions: ['ReadResources'] },

    // ---- PaymentReports / operación de tarjetas  (/payment-reports/v1) ----
    { re: /\/payment-reports\/v\d+\/payment-reports\b/i, type: 'PaymentReports', actions: ['ReadPaymentReports'] },

    // ================= Iniciación de pagos (escritura / POST) =================
    // Cada type de pago tem uma única action Create*, então o path já basta para o
    // gate por type. Retrieve (GET status) usa o mesmo type. Os paths trazem o
    // participantType (pn/pj) como primeiro segmento: /{api}/v1/{pn|pj|...}/{api}.
    // VariableRecurring vem antes de Recurring (base path mais específico primeiro).
    { re: /\/variable-recurring-payments\/v\d+\/[^/]+\/variable-recurring-payments\b/i, type: 'VariableRecurringPayments', actions: ['CreateVariableRecurringPayments'] },
    { re: /\/recurring-payments\/v\d+\/[^/]+\/recurring-payments\b/i, type: 'RecurringPayments', actions: ['CreateRecurringPayments'] },
    { re: /\/scheduled-payments\/v\d+\/[^/]+\/scheduled-payments\b/i, type: 'ScheduledPayments', actions: ['CreateScheduledPayments'] },
    { re: /\/single-payments\/v\d+\/[^/]+\/single-payments\b/i, type: 'SinglePayments', actions: ['CreateSinglePayments'] },

    // ---- FundsConfirmation  (/funds-confirmation/v1/{pn|pj}/funds-confirmation) ----
    // type FundsConfirmation tem DUAS actions (CreateFundsConfirmation / ReadFundsConfirmation)
    // que dependem do MÉTODO (POST vs GET); como casamos só por path, validamos apenas
    // a presença do type. Para granularidade por método, ver nota em RESOURCE_ROUTES.
    { re: /\/funds-confirmation\/v\d+\/[^/]+\/funds-confirmation\b/i, type: 'FundsConfirmation', actions: [] }
]

function CustomError(error) {
    this.status = error.status
    this.code = error.code
    this.title = error.title
    this.typeUri = error.typeUri
    this.detail = error.detail
    this.description = error.description
}

function buildError(baseError, description) {
    return {
        status: baseError.status,
        code: baseError.code,
        title: baseError.title,
        typeUri: baseError.typeUri,
        detail: baseError.detail,
        description: description
    }
}

const ValidationHelper = {
    isEmpty: function (parameter) {
        return (parameter === undefined || parameter === null || parameter === '' || parameter === 'undefined' || parameter === 'null')
    }
}

const RouteResolver = {
    getPath: function () {
        return String($call.request.getRequestedUrl().getPath())
    },
    resolve: function (path) {
        for (var i = 0; i < RESOURCE_ROUTES.length; i++) {
            if (RESOURCE_ROUTES[i].re.test(path)) {
                return RESOURCE_ROUTES[i]
            }
        }
        return null
    }
}

function isStrict() {
    return String($call.contextVariables.get(ContextVariablesNames.STRICT)) === 'true'
}

function getInteractionId() {
    return $call.request.getHeader(CustomHeaderNames.FAPI_INTERACTION_ID) || uuidv4()
}

function sendHttpError(error, interactionId) {
    const errorResponseBody = {
        type: String(error.typeUri),
        title: String(error.title),
        status: Number(error.status),
        code: String(error.code),
        detail: String(error.detail),
        instance: String($call.request.getRequestedUrl().getPath())
    }
    errorResponseBody[CustomHeaderNames.FAPI_INTERACTION_ID] = String(interactionId)

    $call.stopFlow = true
    $call.decision.setAccept(false)
    $call.response = new com.sensedia.interceptor.externaljar.dto.ApiResponse()
    $call.response.setStatus(Number(error.status))
    $call.response.getBody().setString(JSON.stringify(errorResponseBody), 'utf-8')
    $call.response.setHeader('Content-Type', 'application/json')
    $call.response.setHeader(CustomHeaderNames.FAPI_INTERACTION_ID, String(interactionId))

    $console.debug('DEBUG: errorResponseBody', errorResponseBody)
}

function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
        return v.toString(16)
    })
}

/*
 * Main
 */
function main() {
    $call.tracer.trace('SFA Custom Javascript Interceptor: Name [Of Context Params] version [' + VERSION + ']')

    try {
        // Respeita configuração manual: se o recurso já define o type, não sobrescreve.
        const preset = $call.contextVariables.get(ContextVariablesNames.AUTHORIZATION_TYPE)
        if (!ValidationHelper.isEmpty(preset)) {
            $console.debug('DEBUG: context-authorization-type já configurado manualmente, mantendo', String(preset))
            return
        }

        const path = RouteResolver.getPath()
        const route = RouteResolver.resolve(path)

        if (route === null) {
            const msg = 'of-context-params: recurso no mapeado para path "' + path + '"'
            if (isStrict()) {
                throw new CustomError(buildError(Errors.INTERNAL, msg + ' (context-params-strict=true)'))
            }
            $call.tracer.trace('AVISO: ' + msg + ' — nenhuma context var setada; of-consent-validation decidirá')
            return
        }

        const actionsCsv = route.actions.join(',')
        $call.contextVariables.put(ContextVariablesNames.AUTHORIZATION_TYPE, route.type)
        $call.contextVariables.put(ContextVariablesNames.REQUIRED_ACTIONS, actionsCsv)
        $console.debug('DEBUG: context-params resolvido', { path: path, type: route.type, requiredActions: actionsCsv })
    } catch (err) {
        const interactionId = getInteractionId()
        if (err instanceof CustomError) {
            $call.tracer.trace('CustomError: ' + JSON.stringify(err))
            sendHttpError(err, interactionId)
        } else {
            $call.tracer.trace('Exception message => ' + err.message + ' <= in line => ' + err.stack)
            sendHttpError(buildError(Errors.INTERNAL, 'Error inesperado en el gateway de la API.'), interactionId)
        }
    }
}

main()
