"""Tests for daemon integration in CLI commands (init, up, down, doctor)."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

import yaml
from click.testing import CliRunner

from loom.cli import cli


class TestInitDaemonConfig:
    """Verify loom init generates daemon config section."""

    def test_init_generates_daemon_config(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        runner = CliRunner()

        with patch("loom.cli._find_available_port", side_effect=[5432, 6379, 8765]):
            result = runner.invoke(cli, ["init", "--name", "test-project"])

        assert result.exit_code == 0
        config_path = tmp_path / ".loom" / "config.yaml"
        assert config_path.exists()

        with open(config_path) as f:
            config = yaml.safe_load(f)

        assert "daemon" in config
        assert config["daemon"]["enabled"] is False
        assert config["daemon"]["tick_interval_seconds"] == 30
        assert config["daemon"]["pid_file_path"] == ".loom/daemon.pid"
        assert config["daemon"]["log_file_path"] == ".loom/logs/daemon.log"


class TestUpDaemonStart:
    """Verify loom up starts daemon when enabled."""

    def test_up_starts_daemon_when_enabled(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path, daemon_enabled=True)
        cfg = self._make_config(daemon_enabled=True)

        runner = CliRunner()
        with (
            patch("loom.cli.subprocess.run"),
            patch("loom.cli.asyncio.run"),
            patch("loom.cli._write_mcp_json"),
            patch("loom.cli._protect_venv"),
            patch("loom.config.load_config", return_value=cfg),
            patch("loom.daemon.process.start_daemon") as mock_start,
        ):
            result = runner.invoke(cli, ["up"])

        assert result.exit_code == 0, result.output
        assert "Starting daemon" in result.output
        mock_start.assert_called_once_with(cfg, tmp_path)

    def test_up_skips_daemon_when_disabled(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path, daemon_enabled=False)
        cfg = self._make_config(daemon_enabled=False)

        runner = CliRunner()
        with (
            patch("loom.cli.subprocess.run"),
            patch("loom.cli.asyncio.run"),
            patch("loom.cli._write_mcp_json"),
            patch("loom.cli._protect_venv"),
            patch("loom.config.load_config", return_value=cfg),
            patch("loom.daemon.process.start_daemon") as mock_start,
        ):
            result = runner.invoke(cli, ["up"])

        assert result.exit_code == 0
        assert "Starting daemon" not in result.output
        mock_start.assert_not_called()

    def test_up_handles_daemon_start_failure(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path, daemon_enabled=True)

        runner = CliRunner()
        with (
            patch("loom.cli.subprocess.run"),
            patch("loom.cli.asyncio.run"),
            patch("loom.cli._write_mcp_json"),
            patch("loom.cli._protect_venv"),
            patch("loom.config.load_config", return_value=self._make_config(daemon_enabled=True)),
            patch("loom.daemon.process.start_daemon", side_effect=RuntimeError("fork failed")),
        ):
            result = runner.invoke(cli, ["up"])

        # Should not fail — daemon start error is non-fatal
        assert result.exit_code == 0
        assert "Loom is running" in result.output

    def _setup_project(self, path: Path, daemon_enabled: bool = False):
        loom_dir = path / ".loom"
        loom_dir.mkdir(exist_ok=True)
        config = {
            "loom": {"project_name": "test", "project_id": "00000000-0000-0000-0000-000000000001"},
            "database": {"url": "postgresql://loom:loom@localhost:5432/loom"},
            "redis": {"url": "redis://localhost:6379"},
            "daemon": {"enabled": daemon_enabled},
        }
        with open(loom_dir / "config.yaml", "w") as f:
            yaml.dump(config, f)
        compose = {"services": {"postgres": {"ports": []}, "redis": {"ports": []}}}
        with open(path / "docker-compose.loom.yml", "w") as f:
            yaml.dump(compose, f)

    def _make_config(self, daemon_enabled: bool = False):
        cfg = MagicMock()
        cfg.daemon.enabled = daemon_enabled
        cfg.daemon.pid_file_path = ".loom/daemon.pid"
        cfg.daemon.log_file_path = ".loom/logs/daemon.log"
        cfg.database.url = "postgresql://loom:loom@localhost:5432/loom"
        cfg.database.pool_min_size = 1
        cfg.database.pool_max_size = 5
        cfg.redis.url = "redis://localhost:6379"
        cfg.project_id = "00000000-0000-0000-0000-000000000001"
        return cfg


class TestDownDaemonStop:
    """Verify loom down stops daemon before docker compose down."""

    def test_down_stops_daemon_before_compose(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path)

        call_order = []
        runner = CliRunner()
        with (
            patch("loom.config.load_config", return_value=self._make_config()),
            patch(
                "loom.daemon.process.is_daemon_running",
                return_value=1234,
            ),
            patch(
                "loom.daemon.process.stop_daemon",
                side_effect=lambda *a, **kw: call_order.append("stop_daemon") or True,
            ),
            patch(
                "loom.cli.subprocess.run",
                side_effect=lambda *a, **kw: call_order.append("compose_down"),
            ),
        ):
            result = runner.invoke(cli, ["down"])

        assert result.exit_code == 0
        assert "Stopping daemon" in result.output
        assert "Daemon stopped" in result.output
        assert call_order.index("stop_daemon") < call_order.index("compose_down")

    def test_down_handles_daemon_not_running(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path)

        runner = CliRunner()
        with (
            patch("loom.config.load_config", return_value=self._make_config()),
            patch("loom.daemon.process.is_daemon_running", return_value=None),
            patch("loom.daemon.process.stop_daemon") as mock_stop,
            patch("loom.cli.subprocess.run"),
        ):
            result = runner.invoke(cli, ["down"])

        assert result.exit_code == 0
        mock_stop.assert_not_called()

    def test_down_handles_daemon_stop_failure(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path)

        runner = CliRunner()
        with (
            patch("loom.config.load_config", return_value=self._make_config()),
            patch("loom.daemon.process.is_daemon_running", return_value=1234),
            patch("loom.daemon.process.stop_daemon", side_effect=RuntimeError("permission denied")),
            patch("loom.cli.subprocess.run") as mock_compose,
        ):
            result = runner.invoke(cli, ["down"])

        assert result.exit_code == 0
        mock_compose.assert_called_once()  # compose down still runs

    def _setup_project(self, path: Path):
        loom_dir = path / ".loom"
        loom_dir.mkdir(exist_ok=True)
        config = {
            "loom": {"project_name": "test", "project_id": "00000000-0000-0000-0000-000000000001"},
            "database": {"url": "postgresql://loom:loom@localhost:5432/loom"},
            "redis": {"url": "redis://localhost:6379"},
            "daemon": {"enabled": True},
        }
        with open(loom_dir / "config.yaml", "w") as f:
            yaml.dump(config, f)
        compose = {"services": {"postgres": {"ports": []}, "redis": {"ports": []}}}
        with open(path / "docker-compose.loom.yml", "w") as f:
            yaml.dump(compose, f)

    def _make_config(self):
        cfg = MagicMock()
        cfg.daemon.enabled = True
        cfg.daemon.pid_file_path = ".loom/daemon.pid"
        return cfg


class TestDoctorDaemonCheck:
    """Verify loom doctor reports daemon status."""

    def test_doctor_shows_daemon_running(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path, daemon_enabled=True)

        runner = CliRunner()
        with (
            patch("loom.config.load_config", return_value=self._make_config(daemon_enabled=True)),
            patch("loom.daemon.process.is_daemon_running", return_value=4242),
            patch("loom.cli.subprocess.run", return_value=MagicMock(returncode=0)),
            patch("loom.cli.asyncio.run", return_value=True),
        ):
            result = runner.invoke(cli, ["doctor"])

        assert "4242" in result.output
        assert "running" in result.output.lower()

    def test_doctor_shows_daemon_not_running(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path, daemon_enabled=True)

        runner = CliRunner()
        with (
            patch("loom.config.load_config", return_value=self._make_config(daemon_enabled=True)),
            patch("loom.daemon.process.is_daemon_running", return_value=None),
            patch("loom.cli.subprocess.run", return_value=MagicMock(returncode=0)),
            patch("loom.cli.asyncio.run", return_value=True),
        ):
            result = runner.invoke(cli, ["doctor"])

        assert "not running" in result.output.lower()

    def test_doctor_shows_daemon_disabled(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        self._setup_project(tmp_path, daemon_enabled=False)

        runner = CliRunner()
        with (
            patch("loom.config.load_config", return_value=self._make_config(daemon_enabled=False)),
            patch("loom.cli.subprocess.run", return_value=MagicMock(returncode=0)),
            patch("loom.cli.asyncio.run", return_value=True),
        ):
            result = runner.invoke(cli, ["doctor"])

        assert "disabled" in result.output.lower()

    def _setup_project(self, path: Path, daemon_enabled: bool = False):
        loom_dir = path / ".loom"
        loom_dir.mkdir(exist_ok=True)
        config = {
            "loom": {"project_name": "test", "project_id": "00000000-0000-0000-0000-000000000001"},
            "database": {"url": "postgresql://loom:loom@localhost:5432/loom"},
            "redis": {"url": "redis://localhost:6379"},
            "daemon": {"enabled": daemon_enabled},
        }
        with open(loom_dir / "config.yaml", "w") as f:
            yaml.dump(config, f)

    def _make_config(self, daemon_enabled: bool = False):
        cfg = MagicMock()
        cfg.daemon.enabled = daemon_enabled
        cfg.daemon.pid_file_path = ".loom/daemon.pid"
        cfg.daemon.log_file_path = ".loom/logs/daemon.log"
        cfg.database.url = "postgresql://loom:loom@localhost:5432/loom"
        cfg.redis.url = "redis://localhost:6379"
        cfg.project_id = "00000000-0000-0000-0000-000000000001"
        return cfg
