"""
Unit tests for auth CLI commands.

Tests verify the public API of lattice.shell.cli auth commands using
CliRunner and file isolation via tmp_path and monkeypatch fixtures.

Per R7 (Boundary Isolation): Unit tests mock the filesystem via monkeypatch.
"""

import pytest
from typer.testing import CliRunner

from lattice.shell.cli import auth_app

runner = CliRunner()


class TestAuthLogin:
    """Test auth login command."""

    def test_auth_login_saves_key(self, tmp_path, monkeypatch):
        """auth login saves key to auth file."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = runner.invoke(auth_app, ["login", "openai", "--key", "sk-test"])

        assert result.exit_code == 0
        assert "Saved" in result.output or "saved" in result.output.lower()

    def test_auth_login_updates_existing_key(self, tmp_path, monkeypatch):
        """auth login updates key if provider already exists."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Save initial key
        runner.invoke(auth_app, ["login", "openai", "--key", "sk-old"])

        # Update with new key
        result = runner.invoke(auth_app, ["login", "openai", "--key", "sk-new"])

        assert result.exit_code == 0
        # Verify file was updated
        import json

        data = json.loads(auth_file.read_text())
        assert data["openai"]["api_key"] == "sk-new"


class TestAuthLogout:
    """Test auth logout command."""

    def test_auth_logout_removes_key(self, tmp_path, monkeypatch):
        """auth logout removes key from auth file."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # First save a key
        runner.invoke(auth_app, ["login", "openai", "--key", "sk-test"])

        # Then logout
        result = runner.invoke(auth_app, ["logout", "openai"])

        assert result.exit_code == 0
        assert "Removed" in result.output or "removed" in result.output.lower()

    def test_auth_logout_missing_provider(self, tmp_path, monkeypatch):
        """auth logout handles missing provider gracefully."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

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

        assert result.exit_code == 0
        assert (
            "No API key" in result.output
            or "not found" in result.output.lower()
            or "no key" in result.output.lower()
        )


class TestAuthList:
    """Test auth list command."""

    def test_auth_list_shows_providers(self, tmp_path, monkeypatch):
        """auth list shows saved providers."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Save a key
        runner.invoke(auth_app, ["login", "openai", "--key", "sk-test"])

        # List
        result = runner.invoke(auth_app, ["list"])

        assert result.exit_code == 0
        assert "openai" in result.output
        # Keys should be redacted
        assert "sk-test" not in result.output

    def test_auth_list_empty(self, tmp_path, monkeypatch):
        """auth list shows message when no keys saved."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = runner.invoke(auth_app, ["list"])

        assert result.exit_code == 0
        assert (
            "No API keys" in result.output
            or "no keys" in result.output.lower()
            or "no api" in result.output.lower()
        )

    def test_auth_list_multiple_providers(self, tmp_path, monkeypatch):
        """auth list shows all saved providers."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Save multiple keys
        runner.invoke(auth_app, ["login", "openai", "--key", "sk-openai-test"])
        runner.invoke(auth_app, ["login", "anthropic", "--key", "sk-anthropic-test"])

        # List
        result = runner.invoke(auth_app, ["list"])

        assert result.exit_code == 0
        assert "openai" in result.output
        assert "anthropic" in result.output
        # Keys should be redacted
        assert "sk-openai-test" not in result.output
        assert "sk-anthropic-test" not in result.output


class TestAuthTest:
    """Test auth test command."""

    def test_auth_test_valid_key(self, tmp_path, monkeypatch):
        """auth test validates key with API call."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Save a key
        runner.invoke(auth_app, ["login", "openai", "--key", "sk-test"])

        # Test (will fail without real key, but we test the command structure)
        result = runner.invoke(auth_app, ["test", "openai"])
        # Just check the command runs without crashing
        assert result.exit_code in [0, 1]  # May fail due to invalid key

    def test_auth_test_missing_provider(self, tmp_path, monkeypatch):
        """auth test handles missing provider gracefully."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = runner.invoke(auth_app, ["test", "nonexistent"])

        # Should fail with error message
        assert result.exit_code != 0 or (
            "not found" in result.output.lower()
            or "no api key" in result.output.lower()
            or "error" in result.output.lower()
        )


class TestAuthIntegration:
    """Integration tests for full CLI auth workflow."""

    def test_full_auth_workflow(self, tmp_path, monkeypatch):
        """Test complete login, list, logout workflow."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Login
        result = runner.invoke(auth_app, ["login", "openai", "--key", "sk-test"])
        assert result.exit_code == 0

        # List - should show provider
        result = runner.invoke(auth_app, ["list"])
        assert result.exit_code == 0
        assert "openai" in result.output

        # Logout
        result = runner.invoke(auth_app, ["logout", "openai"])
        assert result.exit_code == 0

        # List - should be empty
        result = runner.invoke(auth_app, ["list"])
        assert result.exit_code == 0
        assert "openai" not in result.output.lower() or "no" in result.output.lower()
