import io
import json

from rich.console import Console

from machine_backup.cli import cmd_list, cmd_verify, resolve_snapshot, run_backup
from machine_backup.config import Config
from machine_backup.console import Reporter

from conftest import FakeModule, needs_bins


def _reporter():
    buf = io.StringIO()
    return Reporter(mode="linear", console=Console(file=buf, width=140)), buf


def _cfg(tmp_path, recipient, identity):
    return Config(
        destination=tmp_path / "backups",
        age_recipient=recipient,
        age_identity=identity,
    )


@needs_bins
def test_verify_ok_then_corrupted(tmp_path, age_keys):
    recipient, identity = age_keys
    cfg = _cfg(tmp_path, recipient, identity)
    reporter, _ = _reporter()
    run_backup([FakeModule()], cfg, reporter)

    assert cmd_verify("latest", cfg, _reporter()[0]) == 0

    snap = resolve_snapshot(cfg.destination, "latest")
    archive = snap / "snapshot.tar.zst.age"
    data = bytearray(archive.read_bytes())
    data[-1] ^= 0xFF
    archive.write_bytes(bytes(data))

    reporter, buf = _reporter()
    assert cmd_verify("latest", cfg, reporter) == 1
    assert "archive" in buf.getvalue().lower()


@needs_bins
def test_verify_detects_tampered_artifact_hash(tmp_path, age_keys):
    recipient, identity = age_keys
    cfg = _cfg(tmp_path, recipient, identity)
    run_backup([FakeModule()], cfg, _reporter()[0])

    snap = resolve_snapshot(cfg.destination, "latest")
    manifest_path = snap / "manifest.json"
    manifest = json.loads(manifest_path.read_text())
    # On ment sur le hash attendu : verify doit s'en apercevoir.
    manifest["modules"]["fake"]["artifacts"][0]["sha256"] = "0" * 64
    manifest_path.write_text(json.dumps(manifest, indent=2))

    reporter, buf = _reporter()
    assert cmd_verify("latest", cfg, reporter) == 1
    assert "fake/data.txt" in buf.getvalue()


@needs_bins
def test_verify_reports_missing_member(tmp_path, age_keys):
    recipient, identity = age_keys
    cfg = _cfg(tmp_path, recipient, identity)
    run_backup([FakeModule()], cfg, _reporter()[0])

    snap = resolve_snapshot(cfg.destination, "latest")
    manifest_path = snap / "manifest.json"
    manifest = json.loads(manifest_path.read_text())
    manifest["modules"]["fake"]["artifacts"].append(
        {"path": "fake/fantome.txt", "sha256": "0" * 64, "size": 1, "sensitive": False}
    )
    manifest_path.write_text(json.dumps(manifest, indent=2))

    reporter, buf = _reporter()
    assert cmd_verify("latest", cfg, reporter) == 1
    assert "fantome.txt" in buf.getvalue()


def test_verify_without_identity_exits_2(tmp_path):
    cfg = Config(destination=tmp_path / "backups", age_recipient="age1abc")
    assert cmd_verify("latest", cfg, _reporter()[0]) == 2


def test_verify_unknown_snapshot_exits_2(tmp_path):
    cfg = Config(
        destination=tmp_path / "backups",
        age_recipient="age1abc",
        age_identity=tmp_path / "id.txt",
    )
    (tmp_path / "id.txt").write_text("x")
    assert cmd_verify("2020-01-01T00-00-00", cfg, _reporter()[0]) == 2


@needs_bins
def test_list_shows_snapshots(tmp_path, age_keys):
    recipient, identity = age_keys
    cfg = _cfg(tmp_path, recipient, identity)
    run_backup([FakeModule()], cfg, _reporter()[0])

    reporter, buf = _reporter()
    assert cmd_list(cfg, reporter) == 0
    out = buf.getvalue()
    snap = resolve_snapshot(cfg.destination, "latest")
    assert snap.name in out
    assert "complete" in out
    assert "fake" in out


def test_list_empty_destination_exits_0(tmp_path):
    cfg = Config(destination=tmp_path / "vide")
    reporter, buf = _reporter()
    assert cmd_list(cfg, reporter) == 0
    assert "aucun snapshot" in buf.getvalue().lower()


@needs_bins
def test_list_marks_corrupted_manifest_and_continues(tmp_path, age_keys):
    recipient, identity = age_keys
    cfg = _cfg(tmp_path, recipient, identity)
    run_backup([FakeModule()], cfg, _reporter()[0])

    broken = cfg.destination / "2026-01-01T00-00-00"
    broken.mkdir(parents=True)
    (broken / "manifest.json").write_text("pas du json")

    reporter, buf = _reporter()
    assert cmd_list(cfg, reporter) == 0
    out = buf.getvalue()
    assert "corrompu" in out.lower()
    assert "complete" in out  # le snapshot sain reste liste
