#include "core/package/bank_package.h"

#include <cstring>

#include "core/version/app_version.h"
#include "core/wire/bytes.h"

// Byte offsets (format 1, see package_format.h's ladder): magic at 0, u32
// formatVersion at 4, u32 minReaderVersion at 8, u32 semver length W at 12,
// semver at 16, u32 manifest length M at 16+W, manifest at 20+W, payloads at
// 20+W+M. The region through the semver is the FROZEN refusal surface.

namespace reasampler::package {

namespace {

constexpr std::size_t kMagicBytes = 4;
constexpr std::size_t kSemverLenAt = 12;
constexpr std::size_t kSemverAt = 16;

bool magicMatches(const std::vector<std::uint8_t>& bytes) {
    return bytes.size() >= kMagicBytes &&
           std::memcmp(bytes.data(), kPackageMagic, kMagicBytes) == 0;
}

std::uint32_t u32At(const std::vector<std::uint8_t>& bytes, std::size_t at) {
    std::uint32_t v = 0;
    for (std::size_t b = 0; b < 4; ++b)
        v |= static_cast<std::uint32_t>(bytes[at + b]) << (b * 8);
    return v;
}

// Appends the payload spans for `entries` starting at `firstOffset`. False on
// u64 overflow (a forged length field summing past 2^64 must not wrap into a
// plausible layout).
bool appendSpans(const std::vector<PackageEntry>& entries, std::uint64_t firstOffset,
                 std::vector<PackageEntrySpan>& out, std::uint64_t& end) {
    std::uint64_t offset = firstOffset;
    for (const auto& e : entries) {
        out.push_back({e.fileName, offset, e.byteLength});
        if (offset + e.byteLength < offset) return false;
        offset += e.byteLength;
    }
    end = offset;
    return true;
}

}  // namespace

std::optional<EncodedPackage> encodePackage(const PackageManifest& m) {
    auto manifestJson = serializeManifest(m);
    if (!manifestJson) return std::nullopt;
    if (manifestJson->size() > kMaxManifestBytes) return std::nullopt;

    const std::string& writer = version::stampVersion();
    if (writer.size() > kMaxWriterVersionBytes) return std::nullopt;

    EncodedPackage enc;
    auto& out = enc.prefix;
    out.insert(out.end(), kPackageMagic, kPackageMagic + kMagicBytes);
    wire::putLE(out, kPackageFormatVersion);
    wire::putLE(out, kPackageMinReaderVersion);
    wire::putLE(out, static_cast<std::uint32_t>(writer.size()));
    out.insert(out.end(), writer.begin(), writer.end());
    wire::putLE(out, static_cast<std::uint32_t>(manifestJson->size()));
    out.insert(out.end(), manifestJson->begin(), manifestJson->end());

    if (!appendSpans(m.entries, out.size(), enc.layout, enc.totalSize))
        return std::nullopt;
    return enc;
}

DecodedPackage decodePackage(const std::vector<std::uint8_t>& prefix,
                             std::uint64_t totalFileSize) {
    DecodedPackage dec;  // status starts Malformed; every early return means it

    wire::ByteReader r(prefix);
    if (r.str(kMagicBytes) != std::string(kPackageMagic, kMagicBytes)) return dec;

    const std::uint32_t formatVersion = r.u32();
    const std::uint32_t minReader = r.u32();
    if (!r.ok) return dec;
    const PackageReadability verdict = classifyPackageVersion(formatVersion, minReader);
    if (verdict == PackageReadability::Malformed) return dec;

    const std::uint32_t semverLen = r.u32();
    if (!r.ok || semverLen > kMaxWriterVersionBytes) return dec;
    std::string writer = r.str(semverLen);
    if (!r.ok) return dec;

    dec.header = PackageHeader{formatVersion, minReader, std::move(writer)};
    if (verdict == PackageReadability::TooNew) {
        // Refuse whole: the header names the writer for the message; nothing
        // past the frozen region is read, and no manifest is produced.
        dec.status = PackageReadability::TooNew;
        return dec;
    }

    const std::uint32_t manifestLen = r.u32();
    if (!r.ok || manifestLen > kMaxManifestBytes) return dec;
    const std::string manifestJson = r.str(manifestLen);
    if (!r.ok) return dec;

    auto manifest = deserializeManifest(manifestJson);
    if (!manifest) {
        // A newer additive format's parse failure reports TooNew, not the
        // unactionable Malformed — the header (with the writer semver) is
        // already valid here. See this directory's CLAUDE.md for the tradeoff.
        if (formatVersion > kPackageFormatVersion) dec.status = PackageReadability::TooNew;
        return dec;
    }

    std::uint64_t end = 0;
    std::vector<PackageEntrySpan> layout;
    if (!appendSpans(manifest->entries, r.pos, layout, end)) return dec;
    // Exact-size proof: a byte missing (truncation) or a byte extra (trailing
    // garbage) both fail, even though no payload is read here.
    if (end != totalFileSize) return dec;

    dec.status = PackageReadability::Readable;
    dec.manifest = std::move(*manifest);
    dec.layout = std::move(layout);
    dec.prefixSize = r.pos;
    return dec;
}

std::optional<std::uint64_t> requiredPrefixSize(const std::vector<std::uint8_t>& bytes) {
    if (bytes.size() < kSemverAt) return kSemverAt;
    if (!magicMatches(bytes)) return std::nullopt;

    const auto verdict = classifyPackageVersion(u32At(bytes, 4), u32At(bytes, 8));
    if (verdict == PackageReadability::Malformed) return std::nullopt;

    const std::uint32_t semverLen = u32At(bytes, kSemverLenAt);
    if (semverLen > kMaxWriterVersionBytes) return std::nullopt;
    const std::uint64_t throughSemver = kSemverAt + semverLen;
    if (verdict == PackageReadability::TooNew) return throughSemver;

    if (bytes.size() < throughSemver + 4) return throughSemver + 4;
    const std::uint32_t manifestLen = u32At(bytes, static_cast<std::size_t>(throughSemver));
    if (manifestLen > kMaxManifestBytes) return std::nullopt;
    return throughSemver + 4 + manifestLen;
}

}  // namespace reasampler::package
