#pragma once

#include <balancer/kernel/helpers/errors.h>
#include <balancer/kernel/helpers/exceptionless.h>
#include <balancer/kernel/ssl/sslextdataindeces.h>
#include <balancer/kernel/thread/threadedqueue.h>
#include <balancer/kernel/fs/fs_validations.h>

#include <library/cpp/config/sax.h>
#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <util/generic/list.h>
#include <util/generic/hash_set.h>
#include <util/stream/file.h>
#include <util/system/fstat.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>

namespace NModSsl {
    /*
     * Class with TLS ticket keys
     */
    struct TSslTicketKey {
        unsigned char Name[16];
        unsigned char AesKey[16];
        unsigned char HmacKey[16];

        /**
         * Encrypt ticket for client
         *
         * @param[in] iv                     initialization vector
         * @param[in] cipherCtx              AES key for EVP
         * @param[in] hmacCtx                SHA256 key for HMAC
         */
        void Encrypt(unsigned char* iv, evp_cipher_ctx_st* cipherCtx, hmac_ctx_st* hmacCtx) const noexcept;

        /**
         * Decrypt ticket from client
         *
         * @param[in] iv                     initialization vector
         * @param[in] cipherCtx              AES key for EVP
         * @param[in] hmacCtx                SHA256 key for HMAC
         */
        void Decrypt(unsigned char* iv, evp_cipher_ctx_st* cipherCtx, hmac_ctx_st* hmacCtx) const noexcept;

        bool operator==(const TSslTicketKey& rhs) const {
            return Compare(rhs);
        }

        bool operator!=(const TSslTicketKey& rhs) const {
            return !Compare(rhs);
        }
    private:
        bool Compare(const TSslTicketKey& rhs) const {
            return (
                strncmp(reinterpret_cast<const char*>(rhs.Name), reinterpret_cast<const char*>(Name), 16) == 0 &&
                strncmp(reinterpret_cast<const char*>(rhs.AesKey), reinterpret_cast<const char*>(AesKey), 16) == 0 &&
                strncmp(reinterpret_cast<const char*>(rhs.HmacKey), reinterpret_cast<const char*>(HmacKey), 16) == 0);
        }
    };

    /*
     * Storage of all ticket keys.
     * Use this to store keys in the same order
     * as filenames are provided in config file.
     */
    struct TSslTicketKeyStorage {
    public:
        ssl_ctx_st* Ctx = nullptr;
        TVector<TList<TSslTicketKey>> Keys;
    public:
        TSslTicketKeyStorage() = default;

        explicit TSslTicketKeyStorage(ssl_ctx_st* ctx)
            : Ctx(ctx)
        {}

        /**
         * Find key by name
         *
         * @param[in] name                   key name
         *
         * @return                           const pointer to ticket keys
         */
        const TSslTicketKey* Find(unsigned char* name) const noexcept;

        /**
         * Get key first key in storage
         *
         * @return                           const pointer to ticket keys
         */
        const TSslTicketKey* Default() const {
            return !Keys.empty() && !Keys.front().empty() ? &Keys.front().front() : nullptr;
        }
    };

    /**
     * Main TLS callback responsible for tickets signing.
     *
     * SSL_SNI_TICKET_FAILED == 0 if something went wrong.
     * SSL_SNI_TICKET_OK == 1 if we accepted key and there is
     * no need to renew old key.
     * SSL_SNI_TICKET_RENEW == 2 send another one TLS message
     * this new key.
     *
     * More info https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_tlsext_ticket_key_cb.html
     *
     * @param[in] name                   key name from TSslTicketKey
     * @param[in] iv                     initialization vector
     * @param[in] cipherCtx              AES key for EVP
     * @param[in] hmacCtx                SHA256 key for HMAC
     * @param[in] init                   renew key state
     * @param[in] ticketKeys             pointer to all actual keys
     *
     * @return                           state
     */
    int SslTicketKeysRoutine(
        unsigned char* name, unsigned char* iv, evp_cipher_ctx_st* cipherCtx, hmac_ctx_st* hmacCtx,
        int init, const TSslTicketKeyStorage* ticketKeys) noexcept;

    /*
     * Wrapper for threaded reader
     */
    class ThreadedReadTicketsKeyCallback : public NSrvKernel::TThreadedQueue::ICallback {
    public:
        NSrvKernel::TError Error;
        TList<TSslTicketKey> Keys;
    private:
        const char* const Path_ = nullptr;
    public:
        explicit ThreadedReadTicketsKeyCallback(const char* path) noexcept
            : Path_(path)
        {}
    private:
        /**
         * Place new key to the list
         *
         * @param[in] data                   raw keys
         */
        void StoreKey(const unsigned char* data);
        /**
         * Read raw 48bit data and parse
         *
         * @return                           Error or nothing
         */
        [[nodiscard]] NSrvKernel::TError ReadRawKey();
        /**
         * Call BIO for data read
         *
         * @return                           Error or nothing
         */
        [[nodiscard]] NSrvKernel::TError ReadPemKeys();
        void DoRun() override;
    };

    /**
     * Read tickets from file in separate thread
     * to prevent locking in coroutines.
     * Be careful with files because they could
     * store more than one pair of AES/SHA256 keys.
     *
     * @param[in] path                   path to file
     * @param[in] queue                  queue for execution
     * @param[in] deadline               max read time
     *
     * @return                           Error or keys list
     */
    NSrvKernel::TErrorOr<TList<TSslTicketKey>> ThreadedReadTicketsKey(
        const char* path, NSrvKernel::TThreadedQueue* queue, TInstant deadline);

    /*
     * Ticket file name and unique priority
     */
    struct TTicketFile {
        TString FileName;
        int Priority = Min<int>();
    };
    using TTicketFiles = TVector<TTicketFile>;

    /**
     * Parse tickets config
     *
     * @param[in] config                 SAX config
     *
     * @return                           list of all ticket files
     */
    TTicketFiles ParseTickets(NConfig::IConfig* config);
}
