"""
Module: test_auth
Description: Tests for authentication

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

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

import httpx
import pytest
from typer.testing import CliRunner

from huitzo_cli.auth import AuthClient, TokenStore
from huitzo_cli.commands.auth import auth_app
from huitzo_cli.config import ConfigManager


@pytest.fixture
def temp_token_file(temp_dir: Path) -> Generator[Path, None, None]:
    """Provide a temporary token file."""
    token_file = temp_dir / "token"
    with patch.object(TokenStore, "_get_token_path", return_value=token_file):
        yield token_file


def test_token_store_save(temp_token_file: Path) -> None:
    """Test saving token."""
    store = TokenStore()
    store.save("test-token")

    assert temp_token_file.exists()
    assert temp_token_file.read_text().strip() == "test-token"


def test_token_store_load(temp_token_file: Path) -> None:
    """Test loading token."""
    temp_token_file.parent.mkdir(parents=True, exist_ok=True)
    temp_token_file.write_text("test-token")

    store = TokenStore()
    assert store.load() == "test-token"


def test_token_store_load_missing() -> None:
    """Test loading when token doesn't exist."""
    store = TokenStore()
    # Mock the token path to a non-existent location
    with patch.object(TokenStore, "_get_token_path", return_value=Path("/nonexistent/token")):
        store = TokenStore()
        assert store.load() is None


def test_token_store_delete(temp_token_file: Path) -> None:
    """Test deleting token."""
    temp_token_file.parent.mkdir(parents=True, exist_ok=True)
    temp_token_file.write_text("test-token")

    store = TokenStore()
    store.delete()

    assert not temp_token_file.exists()


def test_auth_client_login(temp_dir: Path, temp_token_file: Path) -> None:
    """Test auth client login with token."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    auth_client.login(email="test@example.com", token="test-token")

    assert auth_client.is_authenticated()
    assert auth_client.get_token() == "test-token"


def test_auth_client_logout(temp_dir: Path, temp_token_file: Path) -> None:
    """Test auth client logout."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    auth_client.login(email="test@example.com", token="test-token")
    assert auth_client.is_authenticated()

    auth_client.logout()
    assert not auth_client.is_authenticated()


def test_auth_client_get_token(temp_dir: Path, temp_token_file: Path) -> None:
    """Test getting token from client."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    auth_client.login(email="test@example.com", token="test-token")
    assert auth_client.get_token() == "test-token"


def test_auth_command_login_with_token(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test login command with token."""
    # Mock config directory
    config_dir = temp_dir / "config"
    monkeypatch.setattr(ConfigManager, "_get_config_dir", staticmethod(lambda: config_dir))

    # Mock token store
    token_file = temp_dir / "token"
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))

    result = cli_runner.invoke(auth_app, ["login", "--token", "test-token"])

    assert result.exit_code == 0
    assert "Logged in with token" in result.stdout


def test_auth_command_login_prompts_for_credentials(cli_runner: CliRunner) -> None:
    """Test login command prompts for email and password when not provided."""
    # Without --token, it prompts for email. Providing input via stdin.
    result = cli_runner.invoke(auth_app, ["login"], input="test@example.com\npassword\n")

    # Will fail because no backend is running, but should not show "not yet available"
    assert result.exit_code == 1
    assert "Login failed" in result.stdout


def test_auth_command_logout(
    cli_runner: CliRunner, temp_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Test logout command."""
    # Mock config directory
    config_dir = temp_dir / "config"
    monkeypatch.setattr(ConfigManager, "_get_config_dir", staticmethod(lambda: config_dir))

    # Mock token store
    token_file = temp_dir / "token"
    token_file.parent.mkdir(parents=True, exist_ok=True)
    token_file.write_text("test-token")
    monkeypatch.setattr(TokenStore, "_get_token_path", staticmethod(lambda: token_file))

    result = cli_runner.invoke(auth_app, ["logout"])

    assert result.exit_code == 0
    assert "Logged out successfully" in result.stdout


# --- HTTP login flow tests ---


def _mock_response(status_code: int, json_data: dict | None = None, text: str = "") -> MagicMock:
    """Create a mock httpx.Response."""
    resp = MagicMock(spec=httpx.Response)
    resp.status_code = status_code
    resp.text = text
    if json_data is not None:
        resp.json.return_value = json_data
    else:
        resp.json.side_effect = ValueError("No JSON")
    return resp


def test_auth_client_http_login_success(temp_dir: Path, temp_token_file: Path) -> None:
    """Test successful HTTP-based login stores token."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    mock_resp = _mock_response(
        200,
        {
            "data": {
                "tokens": {"accessToken": "abc123", "refreshToken": "r1", "expiresAt": 9999},
                "user": {"email": "user@test.com"},
            }
        },
    )

    with patch("huitzo_cli.auth.httpx.post", return_value=mock_resp):
        data = auth_client.login(
            email="user@test.com", password="secret", url="http://localhost:8000"
        )

    assert data["tokens"]["accessToken"] == "abc123"
    assert auth_client.get_token() == "abc123"


def test_auth_client_http_login_failure(temp_dir: Path, temp_token_file: Path) -> None:
    """Test HTTP login with bad credentials raises RuntimeError."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    mock_resp = _mock_response(
        401,
        {"error": {"message": "Invalid credentials"}},
    )

    with patch("huitzo_cli.auth.httpx.post", return_value=mock_resp):
        with pytest.raises(RuntimeError, match="Invalid credentials"):
            auth_client.login(email="user@test.com", password="wrong", url="http://localhost:8000")


def test_auth_client_http_login_network_error(temp_dir: Path, temp_token_file: Path) -> None:
    """Test HTTP login with network error raises RuntimeError."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    with patch(
        "huitzo_cli.auth.httpx.post",
        side_effect=httpx.ConnectError("Connection refused"),
    ):
        with pytest.raises(RuntimeError, match="Could not connect to backend"):
            auth_client.login(email="user@test.com", password="secret", url="http://localhost:8000")


def test_auth_client_http_login_malformed_response(temp_dir: Path, temp_token_file: Path) -> None:
    """Test HTTP login with non-JSON error response raises RuntimeError."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    mock_resp = _mock_response(500, text="Internal Server Error")

    with patch("huitzo_cli.auth.httpx.post", return_value=mock_resp):
        with pytest.raises(RuntimeError, match="Internal Server Error"):
            auth_client.login(email="user@test.com", password="secret", url="http://localhost:8000")


def test_auth_client_login_requires_password_without_token(
    temp_dir: Path, temp_token_file: Path
) -> None:
    """Test that login without token and without password raises."""
    config = ConfigManager(project_root=temp_dir)
    auth_client = AuthClient(config)

    with pytest.raises(RuntimeError, match="Password is required"):
        auth_client.login(email="user@test.com", url="http://localhost:8000")
