import shutil
import subprocess

import pytest

from machine_backup.crypto import (
    CryptoError,
    decrypt_to_dir,
    encrypt_dir,
    require_binaries,
    stream_member_hashes,
)
from machine_backup.manifest import hash_file

from conftest import needs_bins


@needs_bins
def test_roundtrip(tmp_path, age_keys):
    recipient, identity = age_keys
    src = tmp_path / "src"
    (src / "postgres").mkdir(parents=True)
    (src / "postgres" / "db.dump").write_bytes(b"DUMP")
    (src / "manifest.json").write_text("{}")

    archive = tmp_path / "snapshot.tar.zst.age"
    encrypt_dir(src, archive, recipient)
    assert archive.exists()
    assert b"DUMP" not in archive.read_bytes()

    out = tmp_path / "out"
    decrypt_to_dir(archive, identity, out)
    assert (out / "postgres" / "db.dump").read_bytes() == b"DUMP"
    assert (out / "manifest.json").read_text() == "{}"


@needs_bins
def test_stream_member_hashes_matches_disk(tmp_path, age_keys):
    recipient, identity = age_keys
    src = tmp_path / "src"
    (src / "shell").mkdir(parents=True)
    (src / "shell" / "config.fish").write_bytes(b"aaa")
    archive = tmp_path / "s.age"
    encrypt_dir(src, archive, recipient)

    hashes = stream_member_hashes(archive, identity)
    assert hashes["shell/config.fish"] == (hash_file(src / "shell" / "config.fish"), 3)


@needs_bins
def test_stream_member_hashes_writes_nothing_to_disk(tmp_path, age_keys):
    recipient, identity = age_keys
    src = tmp_path / "src"
    src.mkdir()
    (src / "a.txt").write_bytes(b"x")
    archive = tmp_path / "s.age"
    encrypt_dir(src, archive, recipient)

    before = sorted(p.name for p in tmp_path.iterdir())
    stream_member_hashes(archive, identity)
    assert sorted(p.name for p in tmp_path.iterdir()) == before


@needs_bins
def test_wrong_identity_raises(tmp_path, age_keys):
    recipient, _ = age_keys
    src = tmp_path / "src"
    src.mkdir()
    (src / "x").write_text("x")
    archive = tmp_path / "s.age"
    encrypt_dir(src, archive, recipient)

    other = tmp_path / "other.txt"
    subprocess.run(["age-keygen", "-o", str(other)], capture_output=True)
    with pytest.raises(CryptoError):
        stream_member_hashes(archive, other)


@needs_bins
def test_corrupted_archive_raises(tmp_path, age_keys):
    recipient, identity = age_keys
    src = tmp_path / "src"
    src.mkdir()
    (src / "x").write_text("x")
    archive = tmp_path / "s.age"
    encrypt_dir(src, archive, recipient)

    data = bytearray(archive.read_bytes())
    data[len(data) // 2] ^= 0xFF
    archive.write_bytes(bytes(data))
    with pytest.raises(CryptoError):
        stream_member_hashes(archive, identity)


@needs_bins
def test_encrypt_with_bad_recipient_raises(tmp_path):
    src = tmp_path / "src"
    src.mkdir()
    (src / "x").write_text("x")
    with pytest.raises(CryptoError):
        encrypt_dir(src, tmp_path / "s.age", "pas-une-cle")


def test_require_binaries_reports_missing(monkeypatch):
    monkeypatch.setattr(shutil, "which", lambda name: None)
    assert set(require_binaries()) == {"tar", "zstd", "age"}


def test_require_binaries_empty_when_present(monkeypatch):
    monkeypatch.setattr(shutil, "which", lambda name: f"/usr/bin/{name}")
    assert require_binaries() == []
