"""Contrat des modules : inventaire, backup, restore, progression, registre."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import asdict, dataclass, field
from typing import Any, ClassVar, Literal

from osx_backup.context import Context
from osx_backup.runner import CommandResult

Status = Literal["ok", "failed", "skipped"]


def stderr_detail(ctx: Context, label: str, res: CommandResult) -> str:
    """Journalise stderr en entier et renvoie sa dernière ligne pour le message court.

    Les messages affichés doivent tenir sur une ligne ; le détail complet d'une commande
    qui échoue reste consultable dans le log.
    """
    if res.stderr.strip():
        ctx.log(f"{label} stderr :\n{res.stderr.rstrip()}")
    lines = [line.strip() for line in res.stderr.splitlines() if line.strip()]
    return f" : {lines[-1]}" if lines else ""


@dataclass
class Inventory:
    available: bool = True
    reason: str = ""
    items: int = 0
    size_bytes: int = 0
    warnings: list[str] = field(default_factory=list)
    details: dict[str, Any] = field(default_factory=dict)


@dataclass
class ModuleResult:
    status: Status
    message: str = ""
    size_bytes: int = 0
    duration_s: float = 0.0
    items: int = 0
    warnings: list[str] = field(default_factory=list)

    @classmethod
    def ok(cls, message: str = "", **kw: Any) -> ModuleResult:
        return cls("ok", message, **kw)

    @classmethod
    def failed(cls, message: str, **kw: Any) -> ModuleResult:
        return cls("failed", message, **kw)

    @classmethod
    def skipped(cls, message: str = "") -> ModuleResult:
        return cls("skipped", message)

    def to_dict(self) -> dict[str, Any]:
        return asdict(self)


class TaskHandle(ABC):
    @abstractmethod
    def advance(self, n: float = 1) -> None: ...

    @abstractmethod
    def set_total(self, total: float) -> None: ...

    @abstractmethod
    def set_completed(self, completed: float) -> None: ...

    @abstractmethod
    def finish(self) -> None: ...


class ProgressReporter(ABC):
    @abstractmethod
    def task(self, description: str, total: float | None = None) -> TaskHandle: ...


class NullTask(TaskHandle):
    def __init__(self, description: str, total: float | None) -> None:
        self.description = description
        self.total = total
        self.completed = 0.0
        self.finished = False

    def advance(self, n: float = 1) -> None:
        self.completed += n

    def set_total(self, total: float) -> None:
        self.total = total

    def set_completed(self, completed: float) -> None:
        self.completed = completed

    def finish(self) -> None:
        self.finished = True


class NullProgress(ProgressReporter):
    def __init__(self) -> None:
        self.tasks: list[NullTask] = []

    def task(self, description: str, total: float | None = None) -> TaskHandle:
        t = NullTask(description, total)
        self.tasks.append(t)
        return t


class Module(ABC):
    name: ClassVar[str]
    description: ClassVar[str]
    order: ClassVar[int]

    @abstractmethod
    def inventory(self, ctx: Context) -> Inventory: ...

    @abstractmethod
    def backup(self, ctx: Context, progress: ProgressReporter) -> ModuleResult: ...

    @abstractmethod
    def restore(self, ctx: Context, progress: ProgressReporter) -> ModuleResult: ...


_REGISTRY: dict[str, type[Module]] = {}


def register(cls: type[Module]) -> type[Module]:
    _REGISTRY[cls.name] = cls
    return cls


def clear_registry() -> None:
    _REGISTRY.clear()


def get_modules() -> list[Module]:
    return [cls() for cls in sorted(_REGISTRY.values(), key=lambda c: c.order)]


def get_module(name: str) -> Module:
    return _REGISTRY[name]()
