#pragma once
#include "common.hpp"
#include <fstream>
#include <iostream>
#include <sodium.h>

enum class CryptoDomain : uint8_t {
	DEFAULT = 0, NETWORK = 1, STORAGE = 2, TELEMETRY = 3,
	FIRMWARE = 4, SCRIPT = 5, SESSION = 6, PRODUCT = 7
};

class CryptoEngine {
	static constexpr char KDF_CONTEXT[crypto_kdf_CONTEXTBYTES + 1] = "cleanpii";
	SecureBuffer<32> master_key_;
	bool ready_ = false;
	std::unordered_map<uint8_t, std::vector<uint8_t>> key_cache_;
	std::mutex cache_mutex_;

	std::vector<uint8_t> domain_key(uint8_t domain_id) {
		std::lock_guard<std::mutex> l(cache_mutex_);
		auto it = key_cache_.find(domain_id);
		if (it != key_cache_.end()) return it->second;
		std::vector<uint8_t> key(KEY_SIZE);
		if (crypto_kdf_derive_from_key(key.data(), KEY_SIZE, domain_id,
			reinterpret_cast<const char*>(KDF_CONTEXT), master_key_.data()) != 0) {
			return {};
		}
		key_cache_[domain_id] = key;
		return key;
	}

public:
	bool is_ready() const { return ready_; }

	bool load_key(const std::string& path) {
		std::ifstream f(path);
		if (!f) return false;
		std::string line;
		while (std::getline(f, line)) {
			if (line.empty()) continue;
			if (line.find("pii_key=") == 0) {
				std::string hex = line.substr(8);
				auto raw = hex_decode(hex);
				if (raw.size() == KEY_SIZE) {
					std::memcpy(master_key_.data(), raw.data(), KEY_SIZE);
					ready_ = true; return true;
				}
				return false;
			}
			auto raw = hex_decode(line);
			if (raw.size() == KEY_SIZE) {
				std::memcpy(master_key_.data(), raw.data(), KEY_SIZE);
				ready_ = true; return true;
			}
		}
		return false;
	}

	std::vector<uint8_t> encrypt(const uint8_t* data, size_t len, CryptoDomain domain) {
		if (!ready_ || !data) return {};
		auto key = domain_key(static_cast<uint8_t>(domain));
		if (key.empty()) return {};
		std::vector<uint8_t> nonce(NONCE_SIZE);
		randombytes_buf(nonce.data(), NONCE_SIZE);
		std::vector<uint8_t> ciphertext(crypto_secretbox_MACBYTES + len);
		if (crypto_secretbox_easy(ciphertext.data(), data, len, nonce.data(), key.data()) != 0) {
			secure_zero(key.data(), key.size());
			return {};
		}
		secure_zero(key.data(), key.size());
		std::vector<uint8_t> out;
		out.reserve(1 + NONCE_SIZE + ciphertext.size());
		out.push_back(static_cast<uint8_t>(domain));
		out.insert(out.end(), nonce.begin(), nonce.end());
		out.insert(out.end(), ciphertext.begin(), ciphertext.end());
		return out;
	}

	std::vector<uint8_t> decrypt(const uint8_t* ciphertext, size_t len, CryptoDomain domain) {
		if (!ready_ || !ciphertext || len < 1 + NONCE_SIZE + crypto_secretbox_MACBYTES) return {};
		uint8_t pkt_domain = ciphertext[0];
		if (pkt_domain != static_cast<uint8_t>(domain)) {
			if (pkt_domain <= static_cast<uint8_t>(CryptoDomain::PRODUCT)) {
				domain = static_cast<CryptoDomain>(pkt_domain);
			} else {
				return {};
			}
		}
		auto key = domain_key(static_cast<uint8_t>(domain));
		if (key.empty()) return {};
		const uint8_t* nonce = ciphertext + 1;
		const uint8_t* ctext = ciphertext + 1 + NONCE_SIZE;
		size_t ctext_len = len - 1 - NONCE_SIZE;
		std::vector<uint8_t> plaintext(ctext_len - crypto_secretbox_MACBYTES);
		if (crypto_secretbox_open_easy(plaintext.data(), ctext, ctext_len, nonce, key.data()) != 0) {
			if (len >= NONCE_SIZE + crypto_secretbox_MACBYTES) {
				const uint8_t* legacy_nonce = ciphertext;
				const uint8_t* legacy_ctext = ciphertext + NONCE_SIZE;
				size_t legacy_ctext_len = len - NONCE_SIZE;
				if (legacy_ctext_len >= crypto_secretbox_MACBYTES) {
					std::vector<uint8_t> legacy_plain(legacy_ctext_len - crypto_secretbox_MACBYTES);
					if (crypto_secretbox_open_easy(legacy_plain.data(), legacy_ctext, legacy_ctext_len,
						legacy_nonce, key.data()) == 0) {
						secure_zero(key.data(), key.size());
						return legacy_plain;
					}
				}
			}
			secure_zero(key.data(), key.size());
			return {};
		}
		secure_zero(key.data(), key.size());
		return plaintext;
	}

	static std::vector<uint8_t> hmac_sha256(const uint8_t* data, size_t len, const uint8_t* key) {
		std::vector<uint8_t> mac(HMAC_LEN);
		crypto_auth_hmacsha256(mac.data(), data, len, key);
		return mac;
	}
	static std::vector<uint8_t> hmac_sha256(const std::string& data, const uint8_t* key) {
		return hmac_sha256(reinterpret_cast<const uint8_t*>(data.data()), data.size(), key);
	}

	static bool verify_hmac(const uint8_t* data, size_t len, const uint8_t* key, const uint8_t* mac) {
		return crypto_auth_hmacsha256_verify(mac, data, len, key) == 0;
	}

	static std::vector<uint8_t> secure_random(size_t len) {
		std::vector<uint8_t> out(len);
		randombytes_buf(out.data(), len);
		return out;
	}
};
