"""Tests for djb init command."""

from __future__ import annotations

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

import pytest
from click.testing import CliRunner

from djb.cli.djb import djb_cli


@pytest.fixture
def runner():
    """Click CLI test runner."""
    return CliRunner()


@pytest.fixture
def mock_subprocess_run():
    """Mock subprocess.run to avoid actual command execution."""
    with patch("djb.cli.init.subprocess.run") as mock:
        # Default: commands succeed
        mock.return_value = Mock(returncode=0, stdout=b"", stderr=b"")
        yield mock


class TestDjbInit:
    """Tests for djb init command."""

    def test_init_help(self, runner):
        """Test that init --help works."""
        result = runner.invoke(djb_cli, ["init", "--help"])
        assert result.exit_code == 0
        assert "Initialize djb development environment" in result.output
        assert "--skip-brew" in result.output
        assert "--skip-python" in result.output
        assert "--skip-frontend" in result.output
        assert "--skip-secrets" in result.output

    def test_init_skip_all(self, runner, mock_subprocess_run):
        """Test init with all skips - should complete without errors."""
        result = runner.invoke(
            djb_cli,
            ["init", "--skip-brew", "--skip-python", "--skip-frontend", "--skip-secrets"],
        )
        assert result.exit_code == 0
        # With all skips, no subprocess calls should be made
        assert mock_subprocess_run.call_count == 0

    def test_init_homebrew_check_on_macos(self, runner, mock_subprocess_run):
        """Test that init checks for Homebrew on macOS."""
        with patch("djb.cli.init.sys.platform", "darwin"):
            # Mock brew not found
            def run_side_effect(cmd, *args, **kwargs):
                if cmd == ["which", "brew"]:
                    return Mock(returncode=1)
                return Mock(returncode=0, stdout=b"", stderr=b"")

            mock_subprocess_run.side_effect = run_side_effect

            result = runner.invoke(
                djb_cli,
                ["init", "--skip-python", "--skip-frontend", "--skip-secrets"],
            )

            assert result.exit_code == 1

    def test_init_skips_homebrew_on_non_macos(self, runner, mock_subprocess_run):
        """Test that init skips Homebrew on non-macOS platforms."""
        with patch("djb.cli.init.sys.platform", "linux"):
            result = runner.invoke(
                djb_cli,
                ["init", "--skip-python", "--skip-frontend", "--skip-secrets"],
            )

            assert result.exit_code == 0
            # On non-macOS, no brew commands should be called
            brew_calls = [c for c in mock_subprocess_run.call_args_list if "brew" in str(c)]
            assert len(brew_calls) == 0

    def test_init_installs_age_when_missing(self, runner, mock_subprocess_run):
        """Test that init installs age when not present."""
        with patch("djb.cli.init.sys.platform", "darwin"):

            def run_side_effect(cmd, *args, **kwargs):
                # brew exists, age doesn't
                if cmd == ["which", "brew"]:
                    return Mock(returncode=0)
                if cmd == ["brew", "list", "age"]:
                    return Mock(returncode=1)  # Not installed
                return Mock(returncode=0, stdout=b"", stderr=b"")

            mock_subprocess_run.side_effect = run_side_effect

            result = runner.invoke(
                djb_cli,
                ["init", "--skip-python", "--skip-frontend", "--skip-secrets"],
            )

            assert result.exit_code == 0
            # Check that brew install age was called
            install_calls = [
                c
                for c in mock_subprocess_run.call_args_list
                if "brew" in str(c) and "install" in str(c) and "age" in str(c)
            ]
            assert len(install_calls) > 0

    def test_init_skips_age_when_present(self, runner, mock_subprocess_run):
        """Test that init skips installing age when already present."""
        with patch("djb.cli.init.sys.platform", "darwin"):

            def run_side_effect(cmd, *args, **kwargs):
                # brew and age both exist
                if cmd == ["which", "brew"]:
                    return Mock(returncode=0)
                if cmd == ["brew", "list", "age"]:
                    return Mock(returncode=0)  # Already installed
                return Mock(returncode=0, stdout=b"", stderr=b"")

            mock_subprocess_run.side_effect = run_side_effect

            result = runner.invoke(
                djb_cli,
                ["init", "--skip-python", "--skip-frontend", "--skip-secrets"],
            )

            assert result.exit_code == 0
            # Verify that brew install age was NOT called (since it's already installed)
            install_age_calls = [
                c
                for c in mock_subprocess_run.call_args_list
                if "brew" in str(c) and "install" in str(c) and "age" in str(c)
            ]
            assert len(install_age_calls) == 0

    def test_init_python_dependencies(self, runner, mock_subprocess_run, tmp_path):
        """Test that init runs uv sync for Python dependencies."""
        result = runner.invoke(
            djb_cli,
            [
                "init",
                "--skip-brew",
                "--skip-frontend",
                "--skip-secrets",
                "--project-root",
                str(tmp_path),
            ],
        )

        assert result.exit_code == 0
        # Check that uv sync was called
        uv_calls = [
            c for c in mock_subprocess_run.call_args_list if "uv" in str(c) and "sync" in str(c)
        ]
        assert len(uv_calls) > 0

    def test_init_frontend_dependencies(self, runner, mock_subprocess_run, tmp_path):
        """Test that init runs bun install for frontend dependencies."""
        # Create frontend directory
        frontend_dir = tmp_path / "frontend"
        frontend_dir.mkdir()

        result = runner.invoke(
            djb_cli,
            [
                "init",
                "--skip-brew",
                "--skip-python",
                "--skip-secrets",
                "--project-root",
                str(tmp_path),
            ],
        )

        assert result.exit_code == 0
        # Check that bun install was called
        bun_calls = [
            c for c in mock_subprocess_run.call_args_list if "bun" in str(c) and "install" in str(c)
        ]
        assert len(bun_calls) > 0

    def test_init_skips_frontend_when_directory_missing(
        self, runner, mock_subprocess_run, tmp_path
    ):
        """Test that init skips frontend when directory doesn't exist."""
        result = runner.invoke(
            djb_cli,
            [
                "init",
                "--skip-brew",
                "--skip-python",
                "--skip-secrets",
                "--project-root",
                str(tmp_path),
            ],
        )

        assert result.exit_code == 0
        # Verify that bun install was NOT called (since frontend dir doesn't exist)
        bun_calls = [
            c for c in mock_subprocess_run.call_args_list if "bun" in str(c) and "install" in str(c)
        ]
        assert len(bun_calls) == 0

    def test_init_secrets_already_initialized(self, runner, mock_subprocess_run, tmp_path):
        """Test that init detects when secrets are already initialized."""
        # Create age key
        age_dir = Path.home() / ".age"
        age_dir.mkdir(exist_ok=True)
        key_file = age_dir / "keys.txt"
        key_existed = key_file.exists()
        if not key_existed:
            key_file.write_text("fake-key")

        # Create secrets directory
        secrets_dir = tmp_path / "secrets"
        secrets_dir.mkdir()
        (secrets_dir / "dev.yaml").write_text("fake: secret")

        try:
            result = runner.invoke(
                djb_cli,
                [
                    "init",
                    "--skip-brew",
                    "--skip-python",
                    "--skip-frontend",
                    "--project-root",
                    str(tmp_path),
                ],
            )

            assert result.exit_code == 0
            # Verify that djb secrets init was NOT called (since secrets already exist)
            secrets_init_calls = [
                c for c in mock_subprocess_run.call_args_list if "secrets" in str(c) and "init" in str(c)
            ]
            assert len(secrets_init_calls) == 0
        finally:
            # Cleanup
            if not key_existed and key_file.exists():
                key_file.unlink()

    def test_init_with_project_root(self, runner, mock_subprocess_run, tmp_path):
        """Test that init respects --project-root option."""
        result = runner.invoke(
            djb_cli,
            [
                "init",
                "--skip-brew",
                "--skip-frontend",
                "--skip-secrets",
                "--project-root",
                str(tmp_path),
            ],
        )

        assert result.exit_code == 0
        # Verify uv sync was called with correct cwd
        uv_calls = [
            c
            for c in mock_subprocess_run.call_args_list
            if len(c.args) > 0 and "uv" in str(c.args[0])
        ]
        assert any(c.kwargs.get("cwd") == tmp_path for c in uv_calls)

    def test_init_idempotent(self, runner, mock_subprocess_run):
        """Test that running init multiple times is safe (idempotent)."""
        with patch("djb.cli.init.sys.platform", "darwin"):

            def run_side_effect(cmd, *args, **kwargs):
                # Everything already installed
                return Mock(returncode=0, stdout=b"", stderr=b"")

            mock_subprocess_run.side_effect = run_side_effect

            # Run init twice
            result1 = runner.invoke(
                djb_cli,
                ["init", "--skip-python", "--skip-frontend", "--skip-secrets"],
            )

            result2 = runner.invoke(
                djb_cli,
                ["init", "--skip-python", "--skip-frontend", "--skip-secrets"],
            )

            # Both runs should succeed (idempotent)
            assert result1.exit_code == 0
            assert result2.exit_code == 0
