import io
import tempfile

from rich.console import Console

from machine_backup.cli import resolve_snapshot, run_backup, run_restore
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,
    )


def _make_snapshot(tmp_path, age_keys, module=None):
    recipient, identity = age_keys
    cfg = _cfg(tmp_path, recipient, identity)
    run_backup([module or FakeModule()], cfg, _reporter()[0])
    return cfg, resolve_snapshot(cfg.destination, "latest")


@needs_bins
def test_dry_run_executes_nothing(tmp_path, age_keys):
    cfg, snapshot = _make_snapshot(tmp_path, age_keys)
    mod = FakeModule()
    reporter, buf = _reporter()
    code = run_restore(snapshot, [mod], cfg, reporter, apply=False, force=set())
    assert code == 0
    assert mod.restored == []
    # Le plan est bien affiche, lui.
    assert "additive" in buf.getvalue()
    assert "destructive" in buf.getvalue()


@needs_bins
def test_apply_runs_additive_only(tmp_path, age_keys):
    cfg, snapshot = _make_snapshot(tmp_path, age_keys)
    mod = FakeModule()
    code = run_restore(snapshot, [mod], cfg, _reporter()[0], apply=True, force=set())
    assert code == 0
    assert "recreer data.txt" in mod.restored
    assert "ecraser existing.txt" not in mod.restored


@needs_bins
def test_force_module_runs_destructive(tmp_path, age_keys):
    cfg, snapshot = _make_snapshot(tmp_path, age_keys)
    mod = FakeModule()
    code = run_restore(
        snapshot, [mod], cfg, _reporter()[0], apply=True, force={"fake"}
    )
    assert code == 0
    assert "recreer data.txt" in mod.restored
    assert "ecraser existing.txt" in mod.restored


@needs_bins
def test_module_absent_from_snapshot_is_reported_not_fatal(tmp_path, age_keys):
    cfg, snapshot = _make_snapshot(tmp_path, age_keys)
    autre = FakeModule(name="autre")
    reporter, buf = _reporter()
    code = run_restore(snapshot, [autre], cfg, reporter, apply=True, force=set())
    assert code == 0
    assert autre.restored == []
    assert "absent du snapshot" in buf.getvalue()


@needs_bins
def test_workdir_cleaned_even_on_module_error(tmp_path, age_keys, monkeypatch):
    cfg, snapshot = _make_snapshot(tmp_path, age_keys)
    created: list[str] = []
    real_mkdtemp = tempfile.mkdtemp

    def spy(*args, **kwargs):
        path = real_mkdtemp(*args, **kwargs)
        created.append(path)
        return path

    monkeypatch.setattr(tempfile, "mkdtemp", spy)

    mod = FakeModule(fail_restore=True)
    code = run_restore(snapshot, [mod], cfg, _reporter()[0], apply=True, force=set())
    assert code == 1
    assert created, "le repertoire de travail temporaire n'a pas ete cree"
    for path in created:
        import os

        assert not os.path.exists(path), "du clair dechiffre a survecu au restore"


@needs_bins
def test_force_unknown_module_exits_2(tmp_path, age_keys):
    cfg, snapshot = _make_snapshot(tmp_path, age_keys)
    code = run_restore(
        snapshot, [FakeModule()], cfg, _reporter()[0], apply=True, force={"inconnu"}
    )
    assert code == 2


def test_restore_without_identity_exits_2(tmp_path):
    cfg = Config(destination=tmp_path / "backups", age_recipient="age1abc")
    code = run_restore(
        tmp_path / "snap", [FakeModule()], cfg, _reporter()[0], apply=False, force=set()
    )
    assert code == 2
