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

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

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 (
    check_namespace_available,
    derive_username_slug,
    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) -> None:
    """Test init command with all arguments provided."""
    runner = CliRunner()

    # Change to temp directory
    import os

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

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

        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()

    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, ["init", "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) -> None:
    """Test init when directory already exists."""
    runner = CliRunner()

    # 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,
            [
                "init",
                "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)


# --- Phase 1: Username slug derivation tests ---


def test_derive_username_slug_valid_email() -> None:
    """Test username slug derivation from valid email."""
    assert derive_username_slug("john.doe@example.com") == "john-doe"
    assert derive_username_slug("alice_smith@example.com") == "alice-smith"
    assert derive_username_slug("bob123@example.com") == "bob123"
    assert derive_username_slug("developer@huitzo.dev") == "developer"


def test_derive_username_slug_sanitization() -> None:
    """Test slug sanitization removes invalid characters."""
    # Special characters removed
    assert derive_username_slug("user+tag@example.com") == "usertag"
    assert derive_username_slug("first.last@example.com") == "first-last"

    # Leading digits OK, but must start with letter after sanitization
    slug = derive_username_slug("user99@example.com")
    assert slug == "user99"


def test_derive_username_slug_invalid() -> None:
    """Test slug derivation returns None for invalid inputs."""
    # No @ symbol
    assert derive_username_slug("notanemail") is None

    # Empty email
    assert derive_username_slug("") is None

    # Username part too short or starts with number
    assert derive_username_slug("1@example.com") is None

    # Username part results in invalid slug
    assert derive_username_slug("---@example.com") is None


def test_derive_username_slug_edge_cases() -> None:
    """Test edge cases in slug derivation."""
    # Very long username (should be truncated by length check)
    long_email = "a" * 50 + "@example.com"
    assert derive_username_slug(long_email) is None  # > 39 chars

    # Single character (too short)
    assert derive_username_slug("a@example.com") is None  # < 2 chars


# --- Phase 2: 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


# --- Phase 3: 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
