% Copyright (c) 2025 Onomondo ApS & sysmocom - s.f.m.c. GmbH. All rights reserved.
%
% SPDX-License-Identifier: AGPL-3.0-only
%
% Author: Philipp Maier <pmaier@sysmocom.de> / sysmocom - s.f.m.c. GmbH

-module(esipa_asn1_handler_utils).

-export([
    handle_euiccPackageResult/3,
    handle_ipaEuiccDataResponse/3,
    handle_eimPackageResultResponseError/3
]).

eimTransactionId_from_euiccPackageResult(EuiccPackageResult) ->
    case EuiccPackageResult of
        {euiccPackageResultSigned, EuiccPackageResultSigned} ->
            EuiccPackageResultDataSigned = maps:get(
                euiccPackageResultDataSigned, EuiccPackageResultSigned
            ),
            maps:get(eimTransactionId, EuiccPackageResultDataSigned, none);
        {euiccPackageErrorSigned, EuiccPackageErrorSigned} ->
            EuiccPackageErrorDataSigned = maps:get(
                euiccPackageErrorDataSigned, EuiccPackageErrorSigned
            ),
            maps:get(eimTransactionId, EuiccPackageErrorDataSigned, none);
        {euiccPackageErrorUnsigned, EuiccPackageErrorUnsigned} ->
            maps:get(eimTransactionId, EuiccPackageErrorUnsigned, none);
        _ ->
            none
    end.

eimTransactionId_from_ipaEuiccDataResponse(IpaEuiccDataResponse) ->
    case IpaEuiccDataResponse of
        {ipaEuiccData, IpaEuiccData} ->
            maps:get(eimTransactionId, IpaEuiccData, none);
        {ipaEuiccDataResponseError, IpaEuiccDataResponseError} ->
            maps:get(eimTransactionId, IpaEuiccDataResponseError, none);
        _ ->
            none
    end.

% Handle an EuiccPackageResult, this includes everything from the handling of the work items in mnesia_db, down to
% signature checks and the generation of an appropriate outcome for the REST API.
handle_euiccPackageResult(Pid, EuiccPackageResult, Debuginfo) ->
    EimTransactionId = eimTransactionId_from_euiccPackageResult(EuiccPackageResult),
    {EidValue, _, _} = mnesia_db_work:pickup(Pid, EimTransactionId),

    CheckCounterValue = fun(Map) ->
        CounterValueIpad = maps:get(counterValue, Map),
        {ok, CounterValueEim} = mnesia_db_euicc:state_get(EidValue, counterValue),
        case CounterValueIpad of
            CounterValueEim ->
                ok;
            _ ->
                logger:error(
                    "invalid eUICC signature, counterValue mismatch: CounterValueIpad=~p, CounterValueEim=~p~n",
                    [CounterValueIpad, CounterValueEim]
                ),
                error
        end
    end,

    Outcome =
        case EuiccPackageResult of
            {euiccPackageResultSigned, EuiccPackageResultSigned} ->
                case
                    crypto_utils:verify_euiccPackageResultSigned(EuiccPackageResultSigned, EidValue)
                of
                    ok ->
                        EuiccPackageResultDataSigned = maps:get(
                            euiccPackageResultDataSigned, EuiccPackageResultSigned
                        ),
                        case CheckCounterValue(EuiccPackageResultDataSigned) of
                            ok ->
                                esipa_rest_utils:euiccPackageResultDataSigned_to_outcome(
                                    EuiccPackageResultDataSigned
                                );
                            _ ->
                                [{[{euiccPackageErrorCode, counterValueMismatch}]}]
                        end;
                    _ ->
                        [{[{procedureError, euiccSignatureInvalid}]}]
                end;
            {euiccPackageErrorSigned, EuiccPackageErrorSigned} ->
                case
                    crypto_utils:verify_euiccPackageErrorSigned(EuiccPackageErrorSigned, EidValue)
                of
                    ok ->
                        EuiccPackageErrorDataSigned = maps:get(
                            euiccPackageErrorDataSigned, EuiccPackageErrorSigned
                        ),
                        EuiccPackageErrorCode = maps:get(
                            euiccPackageErrorCode, EuiccPackageErrorDataSigned
                        ),
                        case CheckCounterValue(EuiccPackageErrorDataSigned) of
                            ok ->
                                [{[{euiccPackageErrorCode, EuiccPackageErrorCode}]}];
                            _ ->
                                [{[{euiccPackageErrorCode, counterValueMismatch}]}]
                        end;
                    _ ->
                        [{[{procedureError, euiccSignatureInvalid}]}]
                end;
            {euiccPackageErrorUnsigned, _} ->
                [{[{euiccPackageErrorCode, undefinedError}]}]
        end,

    mnesia_db_work:finish(Pid, Outcome, Debuginfo).

% Handle an IpaEuiccDataResponse, this includes everything from the handling of the work items in mnesia_db as well
% as the generation of an appropriate outcome for the REST API. In case IpaEuiccDataResponse contains an eUICC public
% key, we will automatically store it so that we can use it to check the signatures of PSMOs and eCOs.
handle_ipaEuiccDataResponse(Pid, IpaEuiccDataResponse, Debuginfo) ->
    EimTransactionId = eimTransactionId_from_ipaEuiccDataResponse(IpaEuiccDataResponse),
    {EidValue, _, _} = mnesia_db_work:pickup(Pid, EimTransactionId),

    % drive-by store the eUICC public key so that we can use it later to check the signatures of PSMOs or eCOs
    crypto_utils:store_euicc_pubkey_from_ipaEuiccDataResponse(IpaEuiccDataResponse, EidValue),

    Outcome = esipa_rest_utils:ipaEuiccDataResponse_to_outcome(IpaEuiccDataResponse),
    mnesia_db_work:finish(Pid, Outcome, Debuginfo).

% Handle an EimPackageResultResponseError, this includes handling of the work items in mnesia_db and the generation of
% an appropriate REST outcome for the REST API.
handle_eimPackageResultResponseError(Pid, EimPackageResultResponseError, Debuginfo) ->
    EimTransactionId = maps:get(eimTransactionId, EimPackageResultResponseError),
    {_, _, _} = mnesia_db_work:pickup(Pid, EimTransactionId),
    EimPackageResultErrorCode = maps:get(eimPackageResultErrorCode, EimPackageResultResponseError),
    Outcome = [{[{eimPackageError, EimPackageResultErrorCode}]}],
    mnesia_db_work:finish(Pid, Outcome, Debuginfo).
