"""Tests for manifest creation, serialization, and round-trip."""

from pathlib import Path

import pytest

from certisigma_census.manifest import Manifest, ManifestEntry


def test_add_and_get():
    m = Manifest(root_dir="/tmp/test")
    entry = ManifestEntry(hash_hex="ab" * 32, path="file.txt", size=1024, mtime=1.0)
    m.add(entry)
    assert m.total == 1
    assert m.get("ab" * 32) is entry
    assert m.get("00" * 32) is None


def test_mark_attested():
    m = Manifest(root_dir="/tmp/test")
    entry = ManifestEntry(hash_hex="ab" * 32, path="file.txt", size=1024, mtime=1.0)
    m.add(entry)
    assert m.attested_count == 0
    m.mark_attested("ab" * 32, "att_42", "2026-01-01T00:00:00Z")
    assert m.attested_count == 1
    assert entry.attested is True
    assert entry.attestation_id == "att_42"


def test_save_and_load(tmp_path: Path):
    m = Manifest(root_dir="/data/sensitive")
    for i in range(5):
        h = f"{i:064x}"
        m.add(ManifestEntry(hash_hex=h, path=f"file_{i}.dat", size=i * 100, mtime=float(i)))
    m.mark_attested(f"{0:064x}", "att_1", "2026-01-01T00:00:00Z")

    fpath = tmp_path / "manifest.json"
    m.save(fpath)

    loaded = Manifest.load(fpath)
    assert loaded.total == 5
    assert loaded.attested_count == 1
    assert loaded.root_dir == "/data/sensitive"
    assert loaded.get(f"{0:064x}").attested is True


def test_duplicate_hash_overwrites():
    m = Manifest(root_dir="/tmp")
    e1 = ManifestEntry(hash_hex="ab" * 32, path="a.txt", size=100, mtime=1.0)
    e2 = ManifestEntry(hash_hex="ab" * 32, path="b.txt", size=200, mtime=2.0)
    m.add(e1)
    m.add(e2)
    assert m.total == 1
    assert m.get("ab" * 32).path == "b.txt"


# ─── has_path ──────────────────────────────────────────────────────────────

def test_has_path_found():
    m = Manifest(root_dir="/tmp")
    m.add(ManifestEntry(hash_hex="aa" * 32, path="docs/report.pdf", size=500, mtime=1.0))
    m.add(ManifestEntry(hash_hex="bb" * 32, path="data.csv", size=200, mtime=2.0))

    found = m.has_path("docs/report.pdf")
    assert found is not None
    assert found.hash_hex == "aa" * 32


def test_has_path_not_found():
    m = Manifest(root_dir="/tmp")
    m.add(ManifestEntry(hash_hex="aa" * 32, path="file.txt", size=100, mtime=1.0))
    assert m.has_path("other.txt") is None


# ─── merge ─────────────────────────────────────────────────────────────────

def test_merge_adds_new_entries():
    base = Manifest(root_dir="/tmp")
    base.add(ManifestEntry(hash_hex="aa" * 32, path="a.txt", size=100, mtime=1.0))
    base.mark_attested("aa" * 32, "att_1", "2026-01-01T00:00:00Z")

    incoming = Manifest(root_dir="/tmp")
    incoming.add(ManifestEntry(hash_hex="bb" * 32, path="b.txt", size=200, mtime=2.0))

    changed = base.merge(incoming)
    assert changed == 1
    assert base.total == 2
    assert base.get("aa" * 32).attested is True
    assert base.get("bb" * 32).attested is False


def test_merge_preserves_attestation_on_unchanged():
    base = Manifest(root_dir="/tmp")
    base.add(ManifestEntry(hash_hex="aa" * 32, path="a.txt", size=100, mtime=1.0))
    base.mark_attested("aa" * 32, "att_1", "2026-01-01T00:00:00Z")

    incoming = Manifest(root_dir="/tmp")
    incoming.add(ManifestEntry(hash_hex="aa" * 32, path="a.txt", size=100, mtime=1.0))

    changed = base.merge(incoming)
    assert changed == 0
    assert base.get("aa" * 32).attested is True
    assert base.get("aa" * 32).attestation_id == "att_1"


def test_merge_updates_changed_file():
    """Same hash but different path/size is treated as an update."""
    base = Manifest(root_dir="/tmp")
    base.add(ManifestEntry(hash_hex="aa" * 32, path="a.txt", size=100, mtime=1.0))
    base.mark_attested("aa" * 32, "att_1", "2026-01-01T00:00:00Z")

    incoming = Manifest(root_dir="/tmp")
    incoming.add(ManifestEntry(hash_hex="aa" * 32, path="a_renamed.txt", size=100, mtime=3.0))

    changed = base.merge(incoming)
    assert changed == 1
    entry = base.get("aa" * 32)
    assert entry.path == "a_renamed.txt"
    assert entry.attested is True


def test_save_atomic(tmp_path: Path):
    """Save uses atomic write (.tmp then rename) to prevent corruption."""
    m = Manifest(root_dir="/tmp")
    m.add(ManifestEntry(hash_hex="aa" * 32, path="f.txt", size=100, mtime=1.0))

    fpath = tmp_path / "manifest.json"
    m.save(fpath)
    assert fpath.exists()
    assert not fpath.with_suffix(".tmp").exists()
