% 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(mnesia_db).
-include_lib("stdlib/include/qlc.hrl").
-include("mnesia_db_rest.hrl").
-include("mnesia_db_work.hrl").
-include("mnesia_db_euicc.hrl").

% Initialization, startup
-export([init/0]).

% Initialize databse scheme, tables and check the database for unfinished orders
init() ->
    % Create an initial mnesia scheme.
    mnesia:stop(),
    case mnesia:create_schema([node()]) of
        {error, {_, {already_exists, _}}} ->
            ok;
        ok ->
            logger:notice("    mnesia database schema created\n")
    end,
    ok = mnesia:start(),
    logger:notice("    mnesia started\n"),

    % The rest table is a persistent table, so even after a crash it will be possible to continue pending orders.
    case
        mnesia:create_table(
            rest,
            [
                {attributes, record_info(fields, rest)},
                {disc_copies, [node()]},
                {type, set}
            ]
        )
    of
        {aborted, {already_exists, rest}} ->
            ok;
        {atomic, ok} ->
            logger:notice("    rest table created\n")
    end,

    % The work table is volatile. It only contains intermediate results. When the eIM is restarted and there is a
    % pending order that is worked on. Then all the work is lost. This is intentional since we want to avoid keeping
    % states that have gotten inconsistent anyway.
    case
        mnesia:create_table(
            work,
            [
                {attributes, record_info(fields, work)},
                {type, set}
            ]
        )
    of
        {aborted, {already_exists, work}} ->
            ok;
        {atomic, ok} ->
            logger:notice("    work table created\n")
    end,

    % The euicc table will store eUICC states, such as the eID and the counterValue that is required for the replay
    % protection.
    case
        mnesia:create_table(
            euicc,
            [
                {attributes, record_info(fields, euicc)},
                {disc_copies, [node()]},
                {type, set}
            ]
        )
    of
        {aborted, {already_exists, euicc}} ->
            ok;
        {atomic, ok} ->
            logger:notice("    euicc table created\n")
    end,

    % Wait until the mnesia tables become available.
    case mnesia:wait_for_tables([rest, work], 60000) of
        {timeout, _} ->
            logger:error("    unable to synchronize mnesia tables\n"),
            throw("normal operation not possible");
        ok ->
            ok
    end,

    % Look through the rest table and mark all items that were in state "work" as "done" and put an appropriate outcome
    % into the rest table. This way we tell the REST API user that the processing of the order was interrupted due to a
    % crash/restart of the eIM. The API user is then expected to delete the item and (if necessary) re-submit the order.
    Trans = fun() ->
        Q = qlc:q([X#rest.resourceId || X <- mnesia:table(rest), X#rest.status == work]),
        ResourceIds = qlc:e(Q),
        lists:foreach(
            fun(ResourceId) ->
                mnesia_db_rest:trans_set_status(
                    ResourceId,
                    done,
                    [{[{procedureError, abortedOrder}]}],
                    none
                )
            end,
            ResourceIds
        )
    end,
    {atomic, ok} = mnesia:transaction(Trans),

    % Start recurring event cycles
    ok = mnesia_db_euicc:timer_rest(),
    ok = mnesia_db_rest:timer_cleanup(),

    ok.
