"""Tests for the manifest HMAC seal module."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from certisigma_census.manifest import Manifest, ManifestEntry
from certisigma_census.seal import create_seal, seal_path_for, verify_seal


VALID_KEY = "aa" * 32


def _make_manifest(tmp_path: Path) -> Path:
    m = Manifest(root_dir=str(tmp_path))
    m.add(ManifestEntry(hash_hex="bb" * 32, path="file.txt", size=100, mtime=1.0))
    mpath = tmp_path / "test.db"
    m.save(mpath)
    m.close()
    return mpath


class TestCreateSeal:
    def test_creates_seal_file(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        sp = create_seal(mpath, VALID_KEY)
        assert sp.exists()
        assert sp == seal_path_for(mpath.resolve())

    def test_seal_file_contains_valid_json(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        sp = create_seal(mpath, VALID_KEY)
        data = json.loads(sp.read_text())
        assert data["algorithm"] == "HMAC-SHA256"
        assert "hmac" in data
        assert len(data["hmac"]) == 64
        assert "created_at" in data
        assert "census_version" in data

    def test_rejects_short_key(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        with pytest.raises(ValueError, match="64 hex"):
            create_seal(mpath, "aa" * 16)

    def test_rejects_non_hex_key(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        with pytest.raises(ValueError, match="64 hex"):
            create_seal(mpath, "zz" * 32)

    def test_rejects_missing_manifest(self, tmp_path):
        with pytest.raises(FileNotFoundError):
            create_seal(tmp_path / "nope.db", VALID_KEY)


class TestVerifySeal:
    def test_valid_seal(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        create_seal(mpath, VALID_KEY)
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is True
        assert result.error is None

    def test_tampered_manifest(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        create_seal(mpath, VALID_KEY)

        m = Manifest.load(mpath.resolve())
        m.add(ManifestEntry(hash_hex="cc" * 32, path="new.txt", size=50, mtime=2.0))
        m.save(mpath)
        m.close()

        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "mismatch" in result.error.lower()

    def test_wrong_key(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        create_seal(mpath, VALID_KEY)
        result = verify_seal(mpath, "cc" * 32)
        assert result.valid is False
        assert "mismatch" in result.error.lower()

    def test_missing_seal_file(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "not found" in result.error.lower()

    def test_missing_manifest(self, tmp_path):
        result = verify_seal(tmp_path / "nope.db", VALID_KEY)
        assert result.valid is False
        assert "not found" in result.error.lower()

    def test_corrupt_seal_file(self, tmp_path):
        mpath = _make_manifest(tmp_path)
        sp = seal_path_for(mpath.resolve())
        sp.write_text("not json", encoding="utf-8")
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "cannot read" in result.error.lower()

    def test_seal_path_for(self, tmp_path):
        p = tmp_path / "manifest.db"
        assert seal_path_for(p) == tmp_path / "manifest.db.seal"

    def test_empty_manifest_seal_roundtrip(self, tmp_path):
        """Sealing a 0-entry manifest (empty DB) must succeed and verify."""
        m = Manifest(root_dir=str(tmp_path))
        mpath = tmp_path / "empty.db"
        m.save(mpath)
        m.close()

        create_seal(mpath, VALID_KEY)
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is True

    def test_seal_missing_hmac_field(self, tmp_path):
        """Seal JSON with no 'hmac' key must fail as mismatch, not crash."""
        mpath = _make_manifest(tmp_path)
        sp = seal_path_for(mpath.resolve())
        sp.write_text(json.dumps({"algorithm": "HMAC-SHA256"}), encoding="utf-8")
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "mismatch" in result.error.lower()

    def test_seal_non_string_hmac_field(self, tmp_path):
        """Non-string 'hmac' field must fail gracefully, not TypeError."""
        mpath = _make_manifest(tmp_path)
        sp = seal_path_for(mpath.resolve())
        sp.write_text(json.dumps({"algorithm": "HMAC-SHA256", "hmac": 12345}), encoding="utf-8")
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "not a string" in result.error.lower()

    def test_rejects_whitespace_padded_key(self, tmp_path):
        """Keys with whitespace must be rejected even if len==64."""
        mpath = _make_manifest(tmp_path)
        key_with_spaces = "a" * 62 + "  "
        assert len(key_with_spaces) == 64
        with pytest.raises(ValueError, match="64 hex"):
            create_seal(mpath, key_with_spaces)

    def test_seal_non_dict_json_rejected(self, tmp_path):
        """Seal file containing valid JSON but not a dict must fail gracefully."""
        mpath = _make_manifest(tmp_path)
        sp = seal_path_for(mpath.resolve())
        sp.write_text(json.dumps([1, 2, 3]), encoding="utf-8")
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "not a json object" in result.error.lower()

    def test_seal_json_string_rejected(self, tmp_path):
        """Seal file containing a JSON string must fail gracefully."""
        mpath = _make_manifest(tmp_path)
        sp = seal_path_for(mpath.resolve())
        sp.write_text(json.dumps("just a string"), encoding="utf-8")
        result = verify_seal(mpath, VALID_KEY)
        assert result.valid is False
        assert "not a json object" in result.error.lower()
