import json

from osx_backup.context import Context
from osx_backup.modules.base import NullProgress, get_module
from osx_backup.modules.brew import BrewModule, Service, ensure_rsync_in_brewfile, parse_services
from osx_backup.runner import RecordingRunner

SERVICES_JSON = json.dumps(
    [
        {"name": "postgresql@18", "status": "started", "user": "me"},
        {"name": "redis", "status": "none", "user": None},
    ]
)


def _runner() -> RecordingRunner:
    r = RecordingRunner()
    r.add(("brew", "list", "--formula"), stdout="git\nrsync\n")
    r.add(("brew", "list", "--cask"), stdout="ghostty\n")
    r.add(("brew", "tap"), stdout="homebrew/core\nshopify/shopify\n")
    r.add(("brew", "services", "list", "--json"), stdout=SERVICES_JSON)
    return r


def test_registered() -> None:
    assert isinstance(get_module("brew"), BrewModule)


def test_parse_services() -> None:
    assert parse_services(SERVICES_JSON) == [
        Service("postgresql@18", "started"),
        Service("redis", "none"),
    ]


def test_ensure_rsync_in_brewfile() -> None:
    assert ensure_rsync_in_brewfile('brew "git"\n') == 'brew "git"\nbrew "rsync"\n'
    assert ensure_rsync_in_brewfile('brew "rsync"\n') == 'brew "rsync"\n'
    assert ensure_rsync_in_brewfile('brew "git"') == 'brew "git"\nbrew "rsync"\n'


def test_inventory(ctx: Context) -> None:
    ctx.runner = _runner()
    inv = BrewModule().inventory(ctx)
    assert inv.available and inv.items == 5
    assert inv.details == {"formulae": 2, "casks": 1, "taps": 2, "services_started": 1}


def test_inventory_without_brew(ctx: Context) -> None:
    ctx.runner = RecordingRunner(which_map={})
    inv = BrewModule().inventory(ctx)
    assert not inv.available and "Homebrew" in inv.reason


class DumpingRunner(RecordingRunner):
    """Simule l'écriture du Brewfile par `brew bundle dump`."""

    def __init__(self, brewfile) -> None:
        super().__init__()
        self.brewfile = brewfile

    def run(self, cmd, **kw):
        if list(cmd[:3]) == ["brew", "bundle", "dump"]:
            self.brewfile.parent.mkdir(parents=True, exist_ok=True)
            self.brewfile.write_text('brew "git"\n')
        return super().run(cmd, **kw)


def test_backup_dumps_and_adds_rsync(ctx: Context) -> None:
    brewfile = ctx.dest / "brew" / "Brewfile"
    r = DumpingRunner(brewfile)
    r.add(("brew", "services", "list", "--json"), stdout=SERVICES_JSON)
    ctx.runner = r
    res = BrewModule().backup(ctx, NullProgress())
    assert res.status == "ok"
    assert r.has_call("brew", "bundle", "dump", "--file", str(brewfile), "--force", "--describe")
    assert brewfile.read_text() == 'brew "git"\nbrew "rsync"\n'
    services = json.loads((ctx.dest / "brew" / "services.json").read_text())
    assert services == [
        {"name": "postgresql@18", "status": "started"},
        {"name": "redis", "status": "none"},
    ]


def test_backup_dump_failure(ctx: Context) -> None:
    r = RecordingRunner()
    r.add(("brew", "bundle", "dump"), returncode=1, stderr="Error: ligne 1\nError: pas de place\n")
    ctx.runner = r
    res = BrewModule().backup(ctx, NullProgress())
    assert res.status == "failed"
    assert "Error: pas de place" in res.message
    assert not (ctx.dest / "brew" / "services.json").exists()


def test_restore_installs_and_starts_services(ctx: Context) -> None:
    (ctx.dest / "brew").mkdir()
    (ctx.dest / "brew" / "Brewfile").write_text('brew "git"\n')
    (ctx.dest / "brew" / "services.json").write_text(
        json.dumps(
            [{"name": "postgresql@18", "status": "started"}, {"name": "redis", "status": "none"}]
        )
    )
    r = RecordingRunner()
    ctx.runner = r
    res = BrewModule().restore(ctx, NullProgress())
    assert res.status == "ok"
    assert r.mutations == [
        ["brew", "bundle", "install", "--file", str(ctx.dest / "brew" / "Brewfile")],
        ["brew", "services", "start", "postgresql@18"],
    ]


def test_restore_warns_on_service_failure(ctx: Context) -> None:
    (ctx.dest / "brew").mkdir()
    (ctx.dest / "brew" / "Brewfile").write_text('brew "git"\n')
    (ctx.dest / "brew" / "services.json").write_text(
        json.dumps([{"name": "postgresql@18", "status": "started"}])
    )
    r = RecordingRunner()
    r.add(("brew", "services", "start"), returncode=1, stderr="boom")
    ctx.runner = r
    res = BrewModule().restore(ctx, NullProgress())
    assert res.status == "ok"
    assert any("postgresql@18" in w for w in res.warnings)


def test_restore_without_brewfile_skipped(ctx: Context) -> None:
    assert BrewModule().restore(ctx, NullProgress()).status == "skipped"
