#include "Room/internal.h"
#include "Room/V1/Auth/internal.h"

#include <Schema/PduV1.h>

#include <Cytoplasm/Log.h>

#include <Parser.h>

static bool
VerifyPDUV1(PduV1 *auth_pdu)
{
    /* TODO: The PDU could come from an unknown source, which may lack 
     * the tools to verify softfailing(or we may not trust them)*/
    /* TODO: 
     * https://spec.matrix.org/v1.7/server-server-api/
     * #checks-performed-on-receipt-of-a-pdu */
    if (RoomIsRejectedV1(*auth_pdu))
    {
        Log(LOG_ERR, "Auth PDU has been rejected.");
        return false;
    }
    if (RoomIsSoftfailedV1(*auth_pdu))
    {
        Log(LOG_ERR, "Auth PDU has been softfailed.");
    }
    return true; /* This only shows whenever an event was rejected, not 
                   * soft-failed */
}
static bool
ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu)
{
    if (IsState(auth_pdu, "m.room.create", ""))
    {
        return true;
    }
    if (IsState(auth_pdu, "m.room.power_levels", ""))
    {
        /* TODO: Check if it's the latest in terms of [pdu] */
        return true;
    }
    if (IsState(auth_pdu, "m.room.member", pdu->sender))
    {
        /* TODO: Check if it's the latest in terms of [pdu] */
        return true;
    }
    if (StrEquals(pdu->type, "m.room.member"))
    {
        char *membership =
            JsonValueAsString(JsonGet(pdu->content, 1, "membership"));
        JsonValue *third_pid =
            JsonGet(pdu->content, 1, "third_party_invite");

        /* The PDU's state_key is the target. So we check if if the
         * auth PDU would count as the target's membership. Was very fun to
         * find that out when I wanted to kick my 'yukari' alt. */
        if (IsState(auth_pdu, "m.room.member", pdu->state_key))
        {
            /* TODO: Check if it's the latest in terms of [pdu] */
            return true;
        }
        if ((StrEquals(membership, "join") ||
            StrEquals(membership, "invite")) &&
            IsState(auth_pdu, "m.room.join_rules",""))
        {
            /* TODO: Check if it's the latest in terms of [pdu] */
            return true;
        }
        if (StrEquals(membership, "invite") && third_pid)
        {
            HashMap *tpid = JsonValueAsObject(third_pid);
            JsonValue *token =
                JsonGet(tpid, 2, "signed", "token");
            char *token_str = JsonValueAsString(token);
            if (IsState(auth_pdu, "m.room.third_party_invite", token_str))
            {
                /* TODO: Check if it is the latest. */
                return true;
            }
        }
        /* V1 simply doesn't have the concept of restricted rooms,
         * so we can safely skip this one for this function. */
    }
    return false;
}
bool 
ConsiderAuthEventsV1(Room * room, PduV1 pdu, char **errp)
{
    char *ignored;
    size_t i;
    bool room_create = false;
    HashMap *state_keytype;
    state_keytype = HashMapCreate();
    for (i = 0; i < ArraySize(pdu.auth_events); i++)
    {
        char *event_id = JsonValueAsString(ArrayGet(pdu.auth_events, i));
        HashMap *event = RoomEventFetchRaw(room, event_id);
        PduV1 auth_pdu = { 0 };

        char *key_type_id;

        if (!PduV1FromJson(event, &auth_pdu, &ignored))
        {
            JsonFree(event);
            HashMapFree(state_keytype);
            if (errp)
            {
                *errp = "Couldn't parse an auth event";
            }
            return false; /* Yeah... we aren't doing that. */
        }

        /* TODO: Find a better way to do this. Using HashMaps as sets 
         * *works*, but it's not the best of ideas here. Also, we're using 
         * strings to compare things, which yeah. */
        key_type_id = StrConcat(3, auth_pdu.type, ",", auth_pdu.state_key);

        if (HashMapGet(state_keytype, key_type_id))
        {
            /* Duplicate found! We actually ignore it's actual value. */
            if (errp)
            {
                *errp = "Duplicate auth event was found";
            }

            JsonFree(event);
            PduV1Free(&auth_pdu);

            HashMapFree(state_keytype);
            Free(key_type_id);
            return false;
        }
        /* Whenever event is valid or not really doesn't matter, as we're 
         * not using it's value anywhere. */
        HashMapSet(state_keytype, key_type_id, event);
        Free(key_type_id);

        /* Step 2.2: If there are entries whose type and state_key don't 
         * match those specified by the auth events selection algorithm 
         * described in the server specification, reject. */
        if (!ValidAuthEventV1(&auth_pdu, &pdu))
        {
            if (errp)
            {
                *errp = "Invalid authevent given.";
            }
            JsonFree(event);
            PduV1Free(&auth_pdu);
            HashMapFree(state_keytype);
            return false;
        }
        /* Step 2.3: If there are entries which were themselves rejected 
         * under the checks performed on receipt of a PDU, reject. 
         * TODO */
        if (!VerifyPDUV1(&auth_pdu))
        {
            if (errp)
            {
                *errp = "Event depends on rejected PDUs";
            }
            PduV1Free(&auth_pdu);
            JsonFree(event);
            HashMapFree(state_keytype);
            return false;
        }

        /* Step 2.4: If there is no m.room.create event among the entries, 
         * reject. */
        if (!room_create && IsState(&auth_pdu, "m.room.create", ""))
        {
            room_create = true; /* Here, we check for the opposite. */
        }

        JsonFree(event);
        PduV1Free(&auth_pdu);
    }
    HashMapFree(state_keytype);
    if (!room_create && errp)
    {
        *errp = "Room creation event was not in the PDU's auth events";
    }
    return room_create; /* Step 2.4 is actually done here. */
}