#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "../atom.h"
#include "../dectai.h"
#include "../err.h"
#include "../items.h"
#include "../schema.h"
#include "pub.h"

#include "pub.schema.keks.c.in"

enum KEKSErr
KEKSCMPubParse(
    struct KEKSCMPub *cer,
    size_t *off,
    char **failReason,
    const unsigned char *buf,
    const size_t len)
{
    assert(cer != NULL);
    assert(off != NULL);
    assert(failReason != NULL);
    (*failReason) = "unspecified";
    struct KEKSItems schema;
    enum KEKSErr err = KEKSItemsInit(&schema, 512);
    if (err != KEKSErrNo) {
        (*failReason) = "init schema";
        return err;
    }
    err = KEKSItemsParse(&schema, off, KEKSCMPubSchemaRaw, KEKSCMPubSchemaRawLen);
    if (err != KEKSErrNo) {
        (*failReason) = "parse schema";
        return err;
    }
    size_t idxSchema = KEKSItemsGetByKey(&schema, 0, "pub");
    if (idxSchema == 0) {
        KEKSItemsFree(&schema);
        (*failReason) = "schema: no pub";
        return KEKSErrUnsatisfiedSchema;
    }
    struct KEKSItems *items = &(cer->items);
    err = KEKSItemsInit(items, 512);
    if (err != KEKSErrNo) {
        KEKSItemsFree(&schema);
        (*failReason) = "init items";
        return err;
    }
    (*off) = 0;
    err = KEKSItemsParse(items, off, buf, len);
    if (err != KEKSErrNo) {
        KEKSItemsFree(&schema);
        (*failReason) = "parse items";
        return err;
    }
    struct KEKSSchemaErr errSchema = KEKSSchemaValidate(&schema, items, idxSchema, 0);
    KEKSItemsFree(&schema);
    if (errSchema.code != KEKSSchemaErrNo) {
        (*failReason) = "schema failed";
        return KEKSErrUnsatisfiedSchema;
    }
    size_t idx = KEKSItemsGetByKey(items, 0, "data");
    cer->pub = KEKSItemsGetByKey(items, idx, "pub");
    cer->pub = items->list[cer->pub].atom.v.list.head;
    cer->sub = KEKSItemsGetByKey(items, idx, "sub");
    cer->pubId = items->list[KEKSItemsGetByKey(items, idx, "id")].atom.v.str.ptr;
    cer->sig = 0;
    cer->cid = NULL;
    cer->sid = NULL;
    idx = KEKSItemsGetByKey(items, 0, "sigs");
    if (idx == 0) {
        return KEKSErrNo;
    }
    if (items->list[idx].atom.v.list.len != 1) {
        (*failReason) = "len(/sigs) != 1";
        return KEKSErrUnsatisfiedSchema;
    }
    cer->sig = items->list[idx].atom.v.list.head;
    idx = KEKSItemsGetByKey(items, cer->sig, "tbs");
    cer->cid = items->list[KEKSItemsGetByKey(items, idx, "cid")].atom.v.str.ptr;
    cer->sid = items->list[KEKSItemsGetByKey(items, idx, "sid")].atom.v.str.ptr;
    idx = KEKSItemsGetByKey(items, idx, "exp");
    err = KEKSTAI64ToTimespec(
        &(cer->since),
        items->list[idx + 1].atom.v.str.ptr,
        items->list[idx + 1].atom.v.str.len);
    assert(err == KEKSErrNo);
    err = KEKSTimespecToUTC(&(cer->since));
    assert(err == KEKSErrNo);
    err = KEKSTAI64ToTimespec(
        &(cer->till),
        items->list[idx + 2].atom.v.str.ptr,
        items->list[idx + 2].atom.v.str.len);
    assert(err == KEKSErrNo);
    err = KEKSTimespecToUTC(&(cer->till));
    assert(err == KEKSErrNo);
    return KEKSErrNo;
}

bool
KEKSCMPubVerify(
    char **failReason,
    struct KEKSCMPub **verifier,
    struct KEKSCMPub *cer,
    struct KEKSCMPub *pool,
    const size_t poolLen,
    const struct KEKSCMPubVerifyOpts opts)
{
    assert(failReason != NULL);
    assert(verifier != NULL);
    assert(cer != NULL);
    assert(pool != NULL);
    (*failReason) = "unspecified";
    if (opts.t.tv_sec <= cer->since.tv_sec) {
        (*failReason) = "unsatisfied since";
        return false;
    }
    if (opts.t.tv_sec >= cer->till.tv_sec) {
        (*failReason) = "unsatisfied till";
        return false;
    }
    (*verifier) = NULL;
    for (size_t i = 0; i < poolLen; i++) {
        if (memcmp(cer->sid, pool[i].pubId, 32) == 0) {
            (*verifier) = &(pool[i]);
            break;
        }
    }
    if ((*verifier) == NULL) {
        (*failReason) = "no verifier found";
        return false;
    }
    struct KEKSItem *pubA = &((*verifier)->items.list[KEKSItemsGetByKey(
        &((*verifier)->items), (*verifier)->pub, "a")]);
    struct KEKSItem *pubV = &((*verifier)->items.list[KEKSItemsGetByKey(
        &((*verifier)->items), (*verifier)->pub, "v")]);
    size_t signIdx = KEKSItemsGetByKey(&(cer->items), cer->sig, "sign");
    struct KEKSItem *sigV =
        &(cer->items.list[KEKSItemsGetByKey(&(cer->items), signIdx, "v")]);
    {
        struct KEKSItem *sigA =
            &(cer->items.list[KEKSItemsGetByKey(&(cer->items), signIdx, "a")]);
        if ((sigA->atom.v.str.len != pubA->atom.v.str.len) ||
            (memcmp(sigA->atom.v.str.ptr, pubA->atom.v.str.ptr, sigA->atom.v.str.len) !=
             0)) {
            (*failReason) = "sign.a != pub.a";
            return false;
        }
    }
    const size_t cap = (size_t)1 << (uint8_t)10;
    unsigned char buf[(uint16_t)1 << (uint8_t)10] = {0};
    size_t off = 0;
    if (!KEKSItemsEncode(
            &(cer->items),
            KEKSItemsGetByKey(&(cer->items), 0, "data"),
            &off,
            buf,
            cap)) {
        (*failReason) = "can not prepare tbs: data";
        return false;
    }
    {
        size_t off2 = 0;
        if (!KEKSItemsEncode(
                &(cer->items),
                KEKSItemsGetByKey(&(cer->items), 0, "tbs"),
                &off2,
                buf + off,
                cap - off)) {
            (*failReason) = "can not prepare tbs: tbs";
            return false;
        }
        off += off2;
        off2 = 0;
        if (!KEKSItemsEncode(
                &(cer->items),
                KEKSItemsGetByKey(&(cer->items), cer->sig, "tbs"),
                &off2,
                buf + off,
                cap - off)) {
            (*failReason) = "can not prepare tbs: sig-tbs";
            return false;
        }
        off += off2;
    }

    for (size_t i = 0; opts.sigVerifiers[i].algo != NULL; i++) {
        if (!KEKSStrEqual(&(pubA->atom), opts.sigVerifiers[i].algo)) {
            continue;
        }
        return opts.sigVerifiers[i].func(
            failReason,
            pubA->atom.v.str.ptr,
            pubA->atom.v.str.len,
            sigV->atom.v.str.ptr,
            sigV->atom.v.str.len,
            pubV->atom.v.str.ptr,
            pubV->atom.v.str.len,
            buf,
            off);
    }
    (*failReason) = "no signature verifier found";
    return false;
}
