from pathlib import Path

import pytest

from machine_backup.modules import base
from machine_backup.modules.base import (
    Module,
    ModuleResult,
    ModuleStatus,
    NullProgress,
    ActionState,
    available_modules,
    create_modules,
    register,
)


def test_action_states_values():
    assert [s.value for s in ActionState] == ["satisfied", "additive", "destructive"]


def test_module_statuses_values():
    assert [s.value for s in ModuleStatus] == ["ok", "skipped", "failed"]


def test_fake_module_satisfies_protocol(fake_module):
    assert isinstance(fake_module, Module)


def test_registry_order_and_creation():
    @register("aaa")
    class A:
        name = "aaa"

        def check(self):
            return []

        def collect(self, dest: Path, progress=None):
            return ModuleResult("aaa", ModuleStatus.OK)

        def plan_restore(self, src: Path):
            return []

        def restore(self, src, actions, force):
            return ModuleResult("aaa", ModuleStatus.OK)

    try:
        assert "aaa" in available_modules()
        (m,) = create_modules(only=["aaa"])
        assert m.name == "aaa"
    finally:
        base._REGISTRY.pop("aaa", None)


def test_create_modules_unknown_name_raises():
    with pytest.raises(ValueError, match="module inconnu"):
        create_modules(only=["nope"])


def test_create_modules_passes_only_accepted_kwargs():
    seen = {}

    @register("with_choice")
    class WithChoice:
        name = "with_choice"

        def __init__(self, shell_choice: str = "both"):
            seen["shell_choice"] = shell_choice

        def check(self):
            return []

        def collect(self, dest: Path, progress=None):
            return ModuleResult(self.name, ModuleStatus.OK)

        def plan_restore(self, src: Path):
            return []

        def restore(self, src, actions, force):
            return ModuleResult(self.name, ModuleStatus.OK)

    @register("without_choice")
    class WithoutChoice:
        name = "without_choice"

        def __init__(self):
            pass

        def check(self):
            return []

        def collect(self, dest: Path, progress=None):
            return ModuleResult(self.name, ModuleStatus.OK)

        def plan_restore(self, src: Path):
            return []

        def restore(self, src, actions, force):
            return ModuleResult(self.name, ModuleStatus.OK)

    try:
        # Le kwarg inconnu du second constructeur ne doit pas le faire exploser.
        mods = create_modules(only=["with_choice", "without_choice"], shell_choice="zsh")
        assert len(mods) == 2
        assert seen["shell_choice"] == "zsh"
    finally:
        base._REGISTRY.pop("with_choice", None)
        base._REGISTRY.pop("without_choice", None)


def test_collect_writes_and_hashes(tmp_path, fake_module):
    result = fake_module.collect(tmp_path / "fake")
    assert result.status is ModuleStatus.OK
    assert (tmp_path / "fake" / "data.txt").read_text() == "payload"
    (art,) = result.artifacts
    assert art.path == "fake/data.txt"
    assert len(art.sha256) == 64
    assert art.size == 7


def test_collect_reports_progress(tmp_path, fake_module, recording_progress):
    fake_module.collect(tmp_path / "fake", progress=recording_progress)
    assert recording_progress.total == 1
    assert recording_progress.steps == ["data.txt"]


def test_null_progress_is_inert():
    p = NullProgress()
    p.set_total(5)
    p.step("rien")  # ne doit rien lever
