"""
Module: test_init
Description: Tests for pack initialization

Implements:
    - docs/cli/reference.md#huitzo-init
"""

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

import httpx
import pytest
from typer.testing import CliRunner

from huitzo_cli.commands.init import (
    _clone_claude_env,
    _prompt_claude_setup,
    check_namespace_available,
    validate_name,
    validate_namespace,
)
from huitzo_cli.main import app


def test_validate_name_valid() -> None:
    """Test valid pack names."""
    assert validate_name("my-pack")
    assert validate_name("hello-world")
    assert validate_name("pack123")
    assert validate_name("a")


def test_validate_name_invalid() -> None:
    """Test invalid pack names."""
    assert not validate_name("MyPack")  # uppercase
    assert not validate_name("my_pack")  # underscore
    assert not validate_name("-my-pack")  # starts with hyphen
    assert not validate_name("my-pack-")  # ends with hyphen
    assert not validate_name("")  # empty


def test_validate_namespace_valid() -> None:
    """Test valid namespaces."""
    assert validate_namespace("my-namespace")
    assert validate_namespace("hello")
    assert validate_namespace("ns123")


def test_validate_namespace_invalid() -> None:
    """Test invalid namespaces."""
    assert not validate_namespace("MyNamespace")  # uppercase
    assert not validate_namespace("my_namespace")  # underscore


def test_init_command_with_args(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test init command (now under `pack new`) with all arguments provided."""
    from huitzo_cli.config import ConfigManager

    runner = CliRunner()

    # Mock config dir to isolate quota file
    config_dir = temp_dir / "config"
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: config_dir))

    import os

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)

        result = runner.invoke(
            app,
            [
                "pack",
                "new",
                "test-pack",
                "--namespace",
                "test",
                "--author",
                "Test Author",
                "--email",
                "test@example.com",
                "--description",
                "Test Pack",
            ],
            input="n\n",  # Decline Claude Code setup
        )

        assert result.exit_code == 0
        assert "Pack 'test-pack' initialized successfully!" in result.stdout

        # Verify structure
        pack_dir = temp_dir / "test-pack"
        assert (pack_dir / "pyproject.toml").exists()
        assert (pack_dir / "huitzo.yaml").exists()
        assert (pack_dir / "README.md").exists()
        assert (pack_dir / ".gitignore").exists()
        assert (pack_dir / "src" / "test_pack" / "__init__.py").exists()
        assert (pack_dir / "src" / "test_pack" / "commands" / "hello.py").exists()
        assert (pack_dir / "tests" / "test_hello.py").exists()

        # Verify docs structure
        assert (pack_dir / "docs" / "README.md").exists()
        assert (pack_dir / "docs" / "commands" / "README.md").exists()
        assert (pack_dir / "docs" / "commands" / "hello.md").exists()
        assert (pack_dir / "docs" / "guides" / "README.md").exists()
        assert (pack_dir / "docs" / "guides" / "getting-started.md").exists()

    finally:
        os.chdir(old_cwd)


def test_init_invalid_name(temp_dir: Path) -> None:
    """Test init with invalid pack name."""
    runner = CliRunner()

    import os

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)

        result = runner.invoke(app, ["pack", "new", "InvalidPack", "--author", "Test"])
        assert result.exit_code == 1
        assert "Invalid pack name" in result.stdout

    finally:
        os.chdir(old_cwd)


def test_init_directory_exists(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test init when directory already exists."""
    from huitzo_cli.config import ConfigManager

    runner = CliRunner()

    # Mock config dir to isolate quota file
    config_dir = temp_dir / "config"
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: config_dir))

    # Create pack directory first
    pack_dir = temp_dir / "existing-pack"
    pack_dir.mkdir(parents=True)

    import os

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)

        result = runner.invoke(
            app,
            [
                "pack",
                "new",
                "existing-pack",
                "--namespace",
                "test",
                "--author",
                "Test",
                "--email",
                "test@example.com",
                "--description",
                "Test Pack",
            ],
        )

        assert result.exit_code == 1
        assert "already exists" in result.stdout

    finally:
        os.chdir(old_cwd)


# --- Backend namespace validation tests ---


def test_check_namespace_available_not_authenticated() -> None:
    """Test namespace check when user is not authenticated."""
    mock_auth = MagicMock()
    mock_auth.get_token.return_value = None

    available, error = check_namespace_available("test-namespace", mock_auth)

    assert available is True
    assert error is None


def test_check_namespace_available_success() -> None:
    """Test namespace check when namespace is available."""
    mock_auth = MagicMock()
    mock_auth.get_token.return_value = "fake-token"
    mock_auth.get_api_url.return_value = "http://localhost:8000"

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"data": {"available": True, "error": None}}

    with patch("httpx.get", return_value=mock_response):
        available, error = check_namespace_available("test-namespace", mock_auth)

    assert available is True
    assert error is None


def test_check_namespace_available_reserved() -> None:
    """Test namespace check for reserved namespace."""
    mock_auth = MagicMock()
    mock_auth.get_token.return_value = "fake-token"
    mock_auth.get_api_url.return_value = "http://localhost:8000"

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {
        "data": {"available": False, "error": "Namespace 'huitzo' is reserved"}
    }

    with patch("httpx.get", return_value=mock_response):
        available, error = check_namespace_available("huitzo", mock_auth)

    assert available is False
    assert error == "Namespace 'huitzo' is reserved"


def test_check_namespace_available_taken() -> None:
    """Test namespace check for already taken namespace."""
    mock_auth = MagicMock()
    mock_auth.get_token.return_value = "fake-token"
    mock_auth.get_api_url.return_value = "http://localhost:8000"

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {
        "data": {"available": False, "error": "Namespace 'existing' is already taken"}
    }

    with patch("httpx.get", return_value=mock_response):
        available, error = check_namespace_available("existing", mock_auth)

    assert available is False
    assert error == "Namespace 'existing' is already taken"


def test_check_namespace_available_network_error() -> None:
    """Test namespace check gracefully handles network errors."""
    mock_auth = MagicMock()
    mock_auth.get_token.return_value = "fake-token"
    mock_auth.get_api_url.return_value = "http://localhost:8000"

    with patch("httpx.get", side_effect=httpx.ConnectError("Connection failed")):
        available, error = check_namespace_available("test-namespace", mock_auth)

    # Should allow creation on network error (backend will validate again)
    assert available is True
    assert error is None


# --- Rate limiting tests ---


def test_namespace_quota_first_creation(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test first namespace creation is allowed."""
    from huitzo_cli.config import ConfigManager

    # Temporarily disable test mode to test actual rate limiting
    monkeypatch.delenv("HUITZO_TEST_MODE", raising=False)

    config = ConfigManager(project_root=temp_dir)

    # Mock get_config_dir to use temp_dir
    with patch.object(ConfigManager, "get_config_dir", return_value=temp_dir):
        allowed, error = config.check_namespace_quota()

    assert allowed is True
    assert error is None


def test_namespace_quota_within_limit(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test multiple creations within quota limit."""
    from huitzo_cli.config import ConfigManager

    # Temporarily disable test mode to test actual rate limiting
    monkeypatch.delenv("HUITZO_TEST_MODE", raising=False)

    config = ConfigManager(project_root=temp_dir)

    # Mock get_config_dir to use temp_dir
    with patch.object(ConfigManager, "get_config_dir", return_value=temp_dir):
        # Create 4 namespaces (under limit of 5)
        for _ in range(4):
            allowed, error = config.check_namespace_quota()
            assert allowed is True
            assert error is None


def test_namespace_quota_exceeded(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test rate limit after exceeding quota."""
    from huitzo_cli.config import ConfigManager

    # Temporarily disable test mode to test actual rate limiting
    monkeypatch.delenv("HUITZO_TEST_MODE", raising=False)

    config = ConfigManager(project_root=temp_dir)

    # Mock get_config_dir to use temp_dir
    with patch.object(ConfigManager, "get_config_dir", return_value=temp_dir):
        # Create 5 namespaces (at limit)
        for _ in range(5):
            allowed, error = config.check_namespace_quota()
            assert allowed is True

        # 6th attempt should fail
        allowed, error = config.check_namespace_quota()
        assert allowed is False
        assert error == "Rate limit exceeded: You can create up to 5 namespaces per hour"


def test_namespace_quota_expires(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test quota resets after time window expires."""
    import json
    import time

    from huitzo_cli.config import ConfigManager

    # Temporarily disable test mode to test actual rate limiting
    monkeypatch.delenv("HUITZO_TEST_MODE", raising=False)

    config = ConfigManager(project_root=temp_dir)

    # Create quota file with old attempts (> 1 hour ago)
    quota_file = temp_dir / "namespace_quota.json"
    quota_file.parent.mkdir(parents=True, exist_ok=True)

    old_time = time.time() - 3700  # 1 hour and 100 seconds ago
    quota_file.write_text(json.dumps({"attempts": [old_time] * 5}))

    # Mock get_config_dir to use temp_dir
    with patch.object(ConfigManager, "get_config_dir", return_value=temp_dir):
        # Should be allowed since old attempts expired
        allowed, error = config.check_namespace_quota()
        assert allowed is True
        assert error is None


def test_namespace_quota_corrupted_file(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test graceful handling of corrupted quota file."""
    from huitzo_cli.config import ConfigManager

    # Temporarily disable test mode to test actual rate limiting
    monkeypatch.delenv("HUITZO_TEST_MODE", raising=False)

    config = ConfigManager(project_root=temp_dir)

    # Create corrupted quota file
    quota_file = temp_dir / "namespace_quota.json"
    quota_file.parent.mkdir(parents=True, exist_ok=True)
    quota_file.write_text("invalid json{{{")

    # Mock get_config_dir to use temp_dir
    with patch.object(ConfigManager, "get_config_dir", return_value=temp_dir):
        # Should reset quota and allow creation
        allowed, error = config.check_namespace_quota()
        assert allowed is True
        assert error is None


# --- Claude Code environment setup tests ---


def test_prompt_claude_setup_decline(temp_dir: Path) -> None:
    """Test declining Claude Code setup."""
    with patch("huitzo_cli.commands.init.typer.confirm", return_value=False):
        _prompt_claude_setup(temp_dir)

    # No .claude directory should be created
    assert not (temp_dir / ".claude").exists()
    assert not (temp_dir / "CONSTITUTION.md").exists()


def test_prompt_claude_setup_no_git(temp_dir: Path) -> None:
    """Test Claude Code setup when git is not available."""
    with (
        patch("huitzo_cli.commands.init.typer.confirm", return_value=True),
        patch("huitzo_cli.commands.init.shutil.which", return_value=None),
    ):
        _prompt_claude_setup(temp_dir)

    # No .claude directory should be created
    assert not (temp_dir / ".claude").exists()


def test_clone_claude_env_success(temp_dir: Path) -> None:
    """Test successful clone and copy of Claude Code environment."""

    # Create a fake clone target with the expected structure
    def fake_run(cmd, **kwargs):
        if cmd[1] == "clone":
            clone_dir = Path(cmd[-1])
            # Simulate the cloned repo structure
            claude_dir = clone_dir / "claude"
            claude_dir.mkdir(parents=True, exist_ok=True)
            (claude_dir / "CLAUDE.md").write_text("# Pack Development")
            rules_dir = claude_dir / "rules"
            rules_dir.mkdir(parents=True, exist_ok=True)
            (rules_dir / "sdk-patterns.md").write_text("# SDK Patterns")
            skills_dir = claude_dir / "skills" / "add-command"
            skills_dir.mkdir(parents=True, exist_ok=True)
            (skills_dir / "SKILL.md").write_text("# /add-command")
            agents_dir = claude_dir / "agents"
            agents_dir.mkdir(parents=True, exist_ok=True)
            (agents_dir / "pack-developer.md").write_text("# Pack Developer")
            (clone_dir / "CONSTITUTION.md").write_text("# Constitution")
            result = MagicMock()
            result.returncode = 0
            return result
        return MagicMock(returncode=0)

    with patch("huitzo_cli.commands.init.subprocess.run", side_effect=fake_run):
        _clone_claude_env(temp_dir)

    # Verify files were copied
    assert (temp_dir / ".claude" / "CLAUDE.md").exists()
    assert (temp_dir / ".claude" / "rules" / "sdk-patterns.md").exists()
    assert (temp_dir / ".claude" / "skills" / "add-command" / "SKILL.md").exists()
    assert (temp_dir / ".claude" / "agents" / "pack-developer.md").exists()
    assert (temp_dir / "CONSTITUTION.md").exists()

    # Verify content
    assert "Pack Development" in (temp_dir / ".claude" / "CLAUDE.md").read_text()
    assert "Constitution" in (temp_dir / "CONSTITUTION.md").read_text()


def test_clone_claude_env_clone_failure(temp_dir: Path) -> None:
    """Test graceful handling when git clone fails (e.g., no internet)."""
    mock_result = MagicMock()
    mock_result.returncode = 128

    with patch("huitzo_cli.commands.init.subprocess.run", return_value=mock_result):
        _clone_claude_env(temp_dir)

    # No files should be created
    assert not (temp_dir / ".claude").exists()
    assert not (temp_dir / "CONSTITUTION.md").exists()


def test_clone_claude_env_timeout(temp_dir: Path) -> None:
    """Test graceful handling when git clone times out."""
    with patch(
        "huitzo_cli.commands.init.subprocess.run",
        side_effect=subprocess.TimeoutExpired(cmd="git", timeout=30),
    ):
        _clone_claude_env(temp_dir)

    # No files should be created
    assert not (temp_dir / ".claude").exists()


def test_clone_claude_env_unexpected_error(temp_dir: Path) -> None:
    """Test graceful handling of unexpected errors."""
    with patch(
        "huitzo_cli.commands.init.subprocess.run",
        side_effect=OSError("Unexpected error"),
    ):
        _clone_claude_env(temp_dir)

    # No files should be created
    assert not (temp_dir / ".claude").exists()


def test_init_with_claude_setup_decline(temp_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test full init flow where user declines Claude Code setup."""
    from huitzo_cli.config import ConfigManager

    runner = CliRunner()

    # Mock config dir to isolate quota file
    config_dir = temp_dir / "config"
    monkeypatch.setattr(ConfigManager, "get_config_dir", staticmethod(lambda: config_dir))

    import os

    old_cwd = os.getcwd()
    try:
        os.chdir(temp_dir)

        result = runner.invoke(
            app,
            [
                "pack",
                "new",
                "claude-test-pack",
                "--namespace",
                "test",
                "--author",
                "Test Author",
                "--email",
                "test@example.com",
                "--description",
                "Test Pack",
            ],
            input="n\n",  # Decline Claude Code setup
        )

        assert result.exit_code == 0
        assert "Pack 'claude-test-pack' initialized successfully!" in result.stdout

        pack_dir = temp_dir / "claude-test-pack"
        assert (pack_dir / "pyproject.toml").exists()
        # .claude should NOT exist
        assert not (pack_dir / ".claude").exists()

    finally:
        os.chdir(old_cwd)
