import io

from rich.console import Console

from machine_backup.cli import resolve_snapshot, run_backup
from machine_backup.config import Config
from machine_backup.console import Reporter
from machine_backup.logs import CommandResult
from machine_backup.sync import sync_snapshot

from conftest import FakeModule, make_runner, needs_bins


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


def test_sync_builds_the_expected_rsync_call(tmp_path):
    runner = make_runner()
    snapshot = tmp_path / "2026-08-05T14-30-12"
    snapshot.mkdir()
    assert sync_snapshot(snapshot, "user@host:/backups", runner=runner) is True
    assert runner.calls == [["rsync", "-a", str(snapshot), "user@host:/backups/"]]


def test_sync_does_not_double_the_trailing_slash(tmp_path):
    runner = make_runner()
    snapshot = tmp_path / "snap"
    snapshot.mkdir()
    sync_snapshot(snapshot, "user@host:/backups/", runner=runner)
    assert runner.calls[0][-1] == "user@host:/backups/"


def test_sync_failure_returns_false_without_raising(tmp_path):
    runner = make_runner({"rsync": CommandResult(returncode=23, stderr="no route")})
    snapshot = tmp_path / "snap"
    snapshot.mkdir()
    assert sync_snapshot(snapshot, "user@host:/backups", runner=runner) is False


@needs_bins
def test_backup_skips_sync_when_no_sync(tmp_path, age_keys, monkeypatch):
    recipient, identity = age_keys
    called = []
    monkeypatch.setattr(
        "machine_backup.sync.sync_snapshot",
        lambda *a, **kw: called.append(a) or True,
    )
    cfg = Config(
        destination=tmp_path / "backups",
        age_recipient=recipient,
        age_identity=identity,
        remote_target="user@host:/backups",
    )
    run_backup([FakeModule()], cfg, _reporter()[0], no_sync=True)
    assert called == []


@needs_bins
def test_backup_syncs_when_remote_configured(tmp_path, age_keys, monkeypatch):
    recipient, identity = age_keys
    called = []
    monkeypatch.setattr(
        "machine_backup.sync.sync_snapshot",
        lambda *a, **kw: called.append(a) or True,
    )
    cfg = Config(
        destination=tmp_path / "backups",
        age_recipient=recipient,
        age_identity=identity,
        remote_target="user@host:/backups",
    )
    run_backup([FakeModule()], cfg, _reporter()[0])
    assert len(called) == 1
    assert called[0][0] == resolve_snapshot(cfg.destination, "latest")


@needs_bins
def test_partial_snapshot_is_still_synced(tmp_path, age_keys, monkeypatch):
    """Un snapshot partiel reste utile : il doit partir sur le remote."""
    recipient, identity = age_keys
    called = []
    monkeypatch.setattr(
        "machine_backup.sync.sync_snapshot",
        lambda *a, **kw: called.append(a) or True,
    )
    cfg = Config(
        destination=tmp_path / "backups",
        age_recipient=recipient,
        age_identity=identity,
        remote_target="user@host:/backups",
    )
    code = run_backup(
        [FakeModule(name="boom", fail_collect=True), FakeModule(name="ok")],
        cfg,
        _reporter()[0],
    )
    assert code == 1
    assert len(called) == 1


@needs_bins
def test_sync_failure_does_not_change_backup_exit_code(tmp_path, age_keys, monkeypatch):
    recipient, identity = age_keys
    monkeypatch.setattr("machine_backup.sync.sync_snapshot", lambda *a, **kw: False)
    cfg = Config(
        destination=tmp_path / "backups",
        age_recipient=recipient,
        age_identity=identity,
        remote_target="user@host:/backups",
    )
    reporter, buf = _reporter()
    # Le backup local a reussi : son code de sortie ne doit pas etre degrade.
    assert run_backup([FakeModule()], cfg, reporter) == 0
    assert "snapshot local reste valide" in buf.getvalue()
