/*
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
#pragma once

#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/crypto.h>
#include <openssl/md5.h>
#include <openssl/hmac.h>

#include "tsutil/StringConvert.h"
#include "ts/ts.h"
#include "ts/remap.h"

#include "cripts/Lulu.hpp"

namespace cripts::Crypto
{
class Base64
{
  using self_type = Base64;

public:
  Base64()                          = delete;
  Base64(const self_type &)         = delete;
  void operator=(const self_type &) = delete;

  static cripts::string Encode(cripts::string_view str);
  static cripts::string Decode(cripts::string_view str);

}; // End class Crypto::Base64

class Escape
{
  using self_type = Escape;

public:
  Escape()                          = delete;
  Escape(const self_type &)         = delete;
  void operator=(const self_type &) = delete;

  static cripts::string Encode(cripts::string_view str);
  static cripts::string Decode(cripts::string_view str);

}; // End class Crypto::Escape

// These are various (real) crypto functions, all using the Crypto::Base class
namespace detail
{
  class Digest
  {
    using self_type = Digest;

  public:
    Digest()                          = delete;
    void operator=(const self_type &) = delete;
    Digest(self_type &&that)          = default; // For later use

    Digest(size_t len) : _length(len) { TSAssert(len <= EVP_MAX_MD_SIZE); }

    [[nodiscard]] cripts::string
    Hex() const
    {
      return ts::hex({reinterpret_cast<const char *>(_hash), _length});
    }

    operator cripts::string() const { return Hex(); }

    [[nodiscard]] cripts::string
    String() const
    {
      return {reinterpret_cast<const char *>(_hash), _length};
    }

    [[nodiscard]] cripts::string
    Base64() const
    {
      return Crypto::Base64::Encode(cripts::string_view(reinterpret_cast<const char *>(_hash), _length));
    }

    [[nodiscard]] const unsigned char *
    Hash() const
    {
      return _hash;
    }

  protected:
    // Size for the bigest digest
    unsigned char _hash[EVP_MAX_MD_SIZE] = {};
    size_t        _length                = EVP_MAX_MD_SIZE;

  }; // End class Crypto::Digest;

  class Cipher
  {
    using self_type = Cipher;

  public:
    Cipher()                          = delete;
    void operator=(const self_type &) = delete;

    ~Cipher() { EVP_CIPHER_CTX_free(_ctx); }

    virtual void                Encrypt(cripts::string_view str);
    virtual cripts::string_view Finalize();

    operator cripts::string_view() const { return {_message}; }

    [[nodiscard]] cripts::string
    Message() const
    {
      return _message;
    }

    [[nodiscard]] cripts::string
    Base64() const
    {
      return Crypto::Base64::Encode(_message);
    }

    [[nodiscard]] cripts::string
    Hex() const
    {
      return ts::hex(_message);
    }

    operator cripts::string() const { return Hex(); }

  protected:
    Cipher(const unsigned char *key, int len) : _key_len(len) { memcpy(_key, key, len); }
    virtual void _initialize();

    cripts::string    _message;
    unsigned char     _key[EVP_MAX_KEY_LENGTH];
    int               _key_len = 0;
    int               _length  = 0;
    EVP_CIPHER_CTX   *_ctx     = nullptr;
    const EVP_CIPHER *_cipher  = nullptr;

  }; // End class Cipher

} // namespace detail

class SHA256 : public detail::Digest
{
  using super_type = detail::Digest;
  using self_type  = SHA256;

  SHA256(SHA256 &&that) = default;

public:
  SHA256() : detail::Digest(SHA256_DIGEST_LENGTH){};

  SHA256(const self_type &)         = delete;
  void operator=(const self_type &) = delete;

  static self_type Encode(cripts::string_view str);
}; // End class SHA256

class SHA512 : public detail::Digest
{
  using super_type = detail::Digest;
  using self_type  = SHA512;

  SHA512(SHA512 &&that) = default;

public:
  SHA512() : detail::Digest(SHA512_DIGEST_LENGTH){};

  SHA512(const self_type &)         = delete;
  void operator=(const self_type &) = delete;

  static self_type Encode(cripts::string_view str);
}; // End class SHA512

class MD5 : public detail::Digest
{
  using self_type  = MD5;
  using super_type = detail::Digest;

  MD5(MD5 &&that) = default;

public:
  MD5() : detail::Digest(MD5_DIGEST_LENGTH){};

  MD5(const self_type &)            = delete;
  void operator=(const self_type &) = delete;

  static self_type Encode(cripts::string_view str);
}; // End class MD5

class AES256 : public detail::Cipher
{
  using super_type = detail::Cipher;
  using self_type  = AES256;

  AES256(AES256 &&that) noexcept = default;

public:
  using super_type::Cipher;
  using super_type::Encrypt;

  AES256(const SHA256 &key) : super_type(key.Hash(), SHA256_DIGEST_LENGTH) {}
  AES256(const unsigned char *key) : super_type(key, SHA256_DIGEST_LENGTH) {}

  AES256(const self_type &)         = delete;
  void operator=(const self_type &) = delete;

  // The key has to be 256-bit afaik
  static self_type Encrypt(cripts::string_view str, const unsigned char *key);

  static self_type
  Encrypt(cripts::string_view str, SHA256 &key)
  {
    return Encrypt(str, key.Hash());
  }

private:
  // The initialization has to be deferred, since it's virtual, after the ctor.
  void
  _initialize() override
  {
    _cipher = EVP_aes_256_cbc();
    super_type::_initialize();
  }
}; // End class AES256

namespace HMAC
{
  class SHA256 : public detail::Digest
  {
    using super_type = detail::Digest;
    using self_type  = SHA256;

    SHA256(SHA256 &&that) = default;

  public:
    SHA256() : detail::Digest(SHA256_DIGEST_LENGTH){};

    SHA256(const self_type &)         = delete;
    void operator=(const self_type &) = delete;

    static self_type Encrypt(cripts::string_view str, const cripts::string &key);
  }; // End class SHA256
} // namespace HMAC

} // namespace cripts::Crypto

// Formatters for {fmt}
namespace fmt
{
template <> struct formatter<cripts::Crypto::SHA256> {
  constexpr auto
  parse(format_parse_context &ctx) -> decltype(ctx.begin())
  {
    return ctx.begin();
  }

  template <typename FormatContext>
  auto
  format(cripts::Crypto::SHA256 &sha, FormatContext &ctx) const -> decltype(ctx.out())
  {
    return fmt::format_to(ctx.out(), "{}", sha.Hex());
  }
};

template <> struct formatter<cripts::Crypto::SHA512> {
  constexpr auto
  parse(format_parse_context &ctx) -> decltype(ctx.begin())
  {
    return ctx.begin();
  }

  template <typename FormatContext>
  auto
  format(cripts::Crypto::SHA512 &sha, FormatContext &ctx) const -> decltype(ctx.out())
  {
    return fmt::format_to(ctx.out(), "{}", sha.Hex());
  }
};

template <> struct formatter<cripts::Crypto::AES256> {
  constexpr auto
  parse(format_parse_context &ctx) -> decltype(ctx.begin())
  {
    return ctx.begin();
  }

  template <typename FormatContext>
  auto
  format(cripts::Crypto::AES256 &sha, FormatContext &ctx) const -> decltype(ctx.out())
  {
    return fmt::format_to(ctx.out(), "{}", cripts::Crypto::Base64::Encode(sha.Message()));
  }
};

} // namespace fmt
