#include <cstring>
#include <cstdint>
#include <vector>
#include <string>
#include <fstream>
#include <array>
#include <algorithm>
#include <openssl/evp.h>
#include "native_logger.hpp"

namespace netcore{

    // OTA AEAD format: MAGIC(7) | nonce(12) | ciphertext(N) | tag(16)
    constexpr const char* kOtaMagic = "AROTAE1";
    constexpr size_t kOtaMagicLen = 7;
    constexpr size_t kGcmNonceLen = 12;
    constexpr size_t kGcmTagLen = 16;
    constexpr size_t kHeaderLen = kOtaMagicLen + kGcmNonceLen;
    // 分块解密，避免整包读入导致 RAM 峰值约为「文件大小×2」（小内存设备易 OOM）
    constexpr size_t kDecryptChunk = 65536;

    static std::array<uint8_t, 32> ota_key_bytes() {
        static const std::array<uint8_t, 32> a = {
            0x92,0x99,0x4d,0x06,0x6f,0xb6,0xa6,0x3d,0x85,0x08,0xbe,0x73,0x5e,0x73,0x4d,0x8a,
            0x53,0x88,0xe6,0x99,0xfc,0x10,0x29,0xb9,0x16,0x9b,0xe7,0x0c,0x65,0x21,0x1c,0xce
        };
        static const std::array<uint8_t, 32> b = {
            0xcf,0x60,0xa2,0xc2,0x32,0x7a,0x61,0xb0,0x4c,0x8e,0x8a,0x62,0x31,0xc7,0x82,0xff,
            0xec,0xac,0xa1,0x04,0x2a,0x4d,0xaa,0xf2,0xb0,0x5b,0x39,0x2b,0xf4,0xb3,0xad,0xad
        };
        std::array<uint8_t, 32> k{};
        for (size_t i = 0; i < k.size(); i++) k[i] = static_cast<uint8_t>(a[i] ^ b[i]);
        return k;
    }

    bool decrypt_ota_file_impl(const std::string& input_path, const std::string& output_zip_path) {
        std::ifstream ifs(input_path, std::ios::binary);
        if (!ifs) {
            netcore::log_error(std::string("decrypt_ota_file: open in failed: ") + input_path);
            return false;
        }
        ifs.seekg(0, std::ios::end);
        const std::streampos szp = ifs.tellg();
        if (szp <= 0) {
            netcore::log_error("decrypt_ota_file: empty input");
            return false;
        }
        const uint64_t file_size = static_cast<uint64_t>(szp);
        const size_t min_len = kHeaderLen + kGcmTagLen + 1;
        if (file_size < min_len) {
            netcore::log_error("decrypt_ota_file: too short");
            return false;
        }
        const uint64_t ciphertext_len = file_size - kHeaderLen - kGcmTagLen;

        ifs.seekg(0, std::ios::beg);
        std::array<uint8_t, kHeaderLen> header{};
        ifs.read(reinterpret_cast<char*>(header.data()), static_cast<std::streamsize>(kHeaderLen));
        if (ifs.gcount() != static_cast<std::streamsize>(kHeaderLen)) {
            netcore::log_error("decrypt_ota_file: read header failed");
            return false;
        }
        if (!std::equal(header.begin(), header.begin() + kOtaMagicLen,
                         reinterpret_cast<const uint8_t*>(kOtaMagic))) {
            netcore::log_error("decrypt_ota_file: bad magic");
            return false;
        }
        const uint8_t* nonce = header.data() + kOtaMagicLen;

        std::ofstream ofs(output_zip_path, std::ios::binary | std::ios::trunc);
        if (!ofs) {
            netcore::log_error(std::string("decrypt_ota_file: open out failed: ") + output_zip_path);
            return false;
        }

        EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
        if (!ctx) {
            netcore::log_error("decrypt_ota_file: EVP_CIPHER_CTX_new failed");
            return false;
        }

        bool ok = false;
        auto key = ota_key_bytes();
        std::vector<uint8_t> chunk_in(kDecryptChunk);
        std::vector<uint8_t> chunk_out(kDecryptChunk + EVP_MAX_BLOCK_LENGTH);

        do {
            if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr)) {
                netcore::log_error("decrypt_ota_file: DecryptInit failed");
                break;
            }
            if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(kGcmNonceLen), nullptr)) {
                netcore::log_error("decrypt_ota_file: set ivlen failed");
                break;
            }
            if (1 != EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), nonce)) {
                netcore::log_error("decrypt_ota_file: set key/iv failed");
                break;
            }

            uint64_t remaining = ciphertext_len;
            while (remaining > 0) {
                const size_t n = static_cast<size_t>(std::min<uint64_t>(remaining, kDecryptChunk));
                ifs.read(reinterpret_cast<char*>(chunk_in.data()), static_cast<std::streamsize>(n));
                if (ifs.gcount() != static_cast<std::streamsize>(n)) {
                    netcore::log_error("decrypt_ota_file: read ciphertext chunk failed");
                    goto cleanup_ctx;
                }
                int outl = 0;
                if (1 != EVP_DecryptUpdate(ctx, chunk_out.data(), &outl,
                                          chunk_in.data(), static_cast<int>(n))) {
                    netcore::log_error("decrypt_ota_file: update failed");
                    goto cleanup_ctx;
                }
                if (outl > 0) {
                    ofs.write(reinterpret_cast<const char*>(chunk_out.data()), outl);
                    if (!ofs) {
                        netcore::log_error("decrypt_ota_file: write plaintext failed");
                        goto cleanup_ctx;
                    }
                }
                remaining -= n;
            }

            std::array<uint8_t, kGcmTagLen> tag{};
            ifs.read(reinterpret_cast<char*>(tag.data()), static_cast<std::streamsize>(kGcmTagLen));
            if (ifs.gcount() != static_cast<std::streamsize>(kGcmTagLen)) {
                netcore::log_error("decrypt_ota_file: read tag failed");
                break;
            }
            if (1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, static_cast<int>(kGcmTagLen), tag.data())) {
                netcore::log_error("decrypt_ota_file: set tag failed");
                break;
            }

            int outl2 = 0;
            if (1 != EVP_DecryptFinal_ex(ctx, chunk_out.data(), &outl2)) {
                netcore::log_error("decrypt_ota_file: final failed (auth tag mismatch?)");
                break;
            }
            if (outl2 > 0) {
                ofs.write(reinterpret_cast<const char*>(chunk_out.data()), outl2);
                if (!ofs) {
                    netcore::log_error("decrypt_ota_file: write final failed");
                    break;
                }
            }
            ok = true;
        } while (false);

cleanup_ctx:
        EVP_CIPHER_CTX_free(ctx);
        return ok;
    }
}  // namespace netcore
