"""Tests for the authentication CLI."""

import sys
from datetime import datetime, timedelta
from io import StringIO
from unittest.mock import MagicMock, patch

import pytest

from platform_2step_mcp.auth import TokenData, TokenStorage
from platform_2step_mcp.cli import cmd_login, cmd_logout, cmd_status, main


# =============================================================================
# Fixtures
# =============================================================================


@pytest.fixture
def mock_args():
    """Create a mock args namespace."""

    class Args:
        force = False

    return Args()


@pytest.fixture
def valid_tokens() -> TokenData:
    """Create valid token data."""
    return TokenData(
        access_token="test-access-token",
        refresh_token="test-refresh-token",
        expires_at=datetime.utcnow() + timedelta(hours=1),
        scope="read create_pending",
    )


@pytest.fixture
def expiring_soon_tokens() -> TokenData:
    """Create token data expiring soon (within 30 minutes)."""
    return TokenData(
        access_token="test-access-token",
        refresh_token="test-refresh-token",
        expires_at=datetime.utcnow() + timedelta(minutes=15),
        scope="read create_pending",
    )


@pytest.fixture
def expired_tokens() -> TokenData:
    """Create expired token data."""
    return TokenData(
        access_token="expired-access-token",
        refresh_token="test-refresh-token",
        expires_at=datetime.utcnow() - timedelta(hours=1),
        scope="read create_pending",
    )


# =============================================================================
# Status Command Tests
# =============================================================================


class TestStatusCommand:
    """Tests for the status command."""

    def test_status_no_tokens(self, tmp_path):
        """Test status command when no tokens exist."""
        with patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_storage = MagicMock()
            mock_storage.load.return_value = None
            mock_storage_class.return_value = mock_storage

            args = MagicMock()
            result = cmd_status(args)

            assert result == 1
            mock_storage.load.assert_called_once()

    def test_status_valid_tokens(self, tmp_path, valid_tokens):
        """Test status command with valid tokens."""
        with patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_storage = MagicMock()
            mock_storage.load.return_value = valid_tokens
            mock_storage_class.return_value = mock_storage

            args = MagicMock()
            result = cmd_status(args)

            assert result == 0

    def test_status_expired_tokens(self, tmp_path, expired_tokens):
        """Test status command with expired tokens."""
        with patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_storage = MagicMock()
            mock_storage.load.return_value = expired_tokens
            mock_storage_class.return_value = mock_storage

            args = MagicMock()
            result = cmd_status(args)

            assert result == 1


# =============================================================================
# Logout Command Tests
# =============================================================================


class TestLogoutCommand:
    """Tests for the logout command."""

    def test_logout_no_tokens(self, tmp_path):
        """Test logout command when no tokens exist."""
        with patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_storage = MagicMock()
            mock_storage.exists.return_value = False
            mock_storage_class.return_value = mock_storage

            args = MagicMock()
            result = cmd_logout(args)

            assert result == 0
            mock_storage.clear.assert_not_called()

    def test_logout_with_tokens(self, tmp_path, valid_tokens):
        """Test logout command with existing tokens."""
        with patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_storage = MagicMock()
            mock_storage.exists.return_value = True
            mock_storage.load.return_value = valid_tokens
            mock_storage_class.return_value = mock_storage

            args = MagicMock()
            result = cmd_logout(args)

            assert result == 0
            mock_storage.clear.assert_called_once()


# =============================================================================
# Login Command Tests
# =============================================================================


class TestLoginCommand:
    """Tests for the login command."""

    def test_login_already_authenticated(self, tmp_path, valid_tokens, mock_args):
        """Test login command when already authenticated."""
        with patch("platform_2step_mcp.cli._get_bff_url") as mock_get_url, patch(
            "platform_2step_mcp.cli.AuthClient"
        ) as mock_auth_class, patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_get_url.return_value = "https://test.agendapro.com"

            mock_auth = MagicMock()
            mock_auth_class.return_value = mock_auth

            mock_storage = MagicMock()
            mock_storage.load.return_value = valid_tokens
            mock_storage_class.return_value = mock_storage

            result = cmd_login(mock_args)

            assert result == 0
            # Should not call authenticate since we have valid tokens
            mock_auth.authenticate.assert_not_called()

    def test_login_force_reauthenticate(self, tmp_path, valid_tokens):
        """Test login command with --force flag."""

        class Args:
            force = True

        args = Args()

        with patch("platform_2step_mcp.cli._get_bff_url") as mock_get_url, patch(
            "platform_2step_mcp.cli.AuthClient"
        ) as mock_auth_class, patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_get_url.return_value = "https://test.agendapro.com"

            mock_auth = MagicMock()
            mock_auth.authenticate.return_value = valid_tokens
            mock_auth_class.return_value = mock_auth

            mock_storage = MagicMock()
            mock_storage.load.return_value = valid_tokens
            mock_storage_class.return_value = mock_storage

            result = cmd_login(args)

            assert result == 0
            # Should call authenticate because of --force
            mock_auth.authenticate.assert_called_once()

    def test_login_no_existing_tokens(self, tmp_path, valid_tokens, mock_args):
        """Test login command with no existing tokens."""
        with patch("platform_2step_mcp.cli._get_bff_url") as mock_get_url, patch(
            "platform_2step_mcp.cli.AuthClient"
        ) as mock_auth_class, patch(
            "platform_2step_mcp.cli.TokenStorage"
        ) as mock_storage_class:
            mock_get_url.return_value = "https://test.agendapro.com"

            mock_auth = MagicMock()
            mock_auth.authenticate.return_value = valid_tokens
            mock_auth_class.return_value = mock_auth

            mock_storage = MagicMock()
            mock_storage.load.return_value = None
            mock_storage_class.return_value = mock_storage

            result = cmd_login(mock_args)

            assert result == 0
            mock_auth.authenticate.assert_called_once()


# =============================================================================
# Main Entry Point Tests
# =============================================================================


class TestMain:
    """Tests for the main entry point."""

    def test_main_no_command(self):
        """Test main with no command shows help."""
        with patch.object(sys, "argv", ["platform-mcp-auth"]):
            with pytest.raises(SystemExit) as exc_info:
                main()
            assert exc_info.value.code == 0

    def test_main_help(self):
        """Test main with --help flag."""
        with patch.object(sys, "argv", ["platform-mcp-auth", "--help"]):
            with pytest.raises(SystemExit) as exc_info:
                main()
            assert exc_info.value.code == 0

    def test_main_status_command(self):
        """Test main with status command."""
        with patch.object(sys, "argv", ["platform-mcp-auth", "status"]), patch(
            "platform_2step_mcp.cli.cmd_status"
        ) as mock_cmd:
            mock_cmd.return_value = 0
            with pytest.raises(SystemExit) as exc_info:
                main()
            assert exc_info.value.code == 0
            mock_cmd.assert_called_once()

    def test_main_logout_command(self):
        """Test main with logout command."""
        with patch.object(sys, "argv", ["platform-mcp-auth", "logout"]), patch(
            "platform_2step_mcp.cli.cmd_logout"
        ) as mock_cmd:
            mock_cmd.return_value = 0
            with pytest.raises(SystemExit) as exc_info:
                main()
            assert exc_info.value.code == 0
            mock_cmd.assert_called_once()

    def test_main_login_command(self):
        """Test main with login command."""
        with patch.object(sys, "argv", ["platform-mcp-auth", "login"]), patch(
            "platform_2step_mcp.cli.cmd_login"
        ) as mock_cmd:
            mock_cmd.return_value = 0
            with pytest.raises(SystemExit) as exc_info:
                main()
            assert exc_info.value.code == 0
            mock_cmd.assert_called_once()

    def test_main_login_with_force(self):
        """Test main with login --force command."""
        with patch.object(
            sys, "argv", ["platform-mcp-auth", "login", "--force"]
        ), patch("platform_2step_mcp.cli.cmd_login") as mock_cmd:
            mock_cmd.return_value = 0
            with pytest.raises(SystemExit) as exc_info:
                main()
            assert exc_info.value.code == 0
            mock_cmd.assert_called_once()
            args = mock_cmd.call_args[0][0]
            assert args.force is True


# =============================================================================
# Integration Tests with TokenStorage
# =============================================================================


class TestTokenStorageIntegration:
    """Integration tests using actual TokenStorage."""

    def test_status_with_real_storage(self, tmp_path, valid_tokens):
        """Test status command with real storage."""
        storage_path = str(tmp_path / "tokens.json")
        storage = TokenStorage(storage_path)
        storage.save(valid_tokens)

        with patch(
            "platform_2step_mcp.cli.TokenStorage",
            return_value=TokenStorage(storage_path),
        ):
            args = MagicMock()
            result = cmd_status(args)
            assert result == 0

    def test_logout_with_real_storage(self, tmp_path, valid_tokens):
        """Test logout command with real storage."""
        storage_path = str(tmp_path / "tokens.json")
        storage = TokenStorage(storage_path)
        storage.save(valid_tokens)

        # Verify file exists
        assert storage.exists()

        with patch(
            "platform_2step_mcp.cli.TokenStorage",
            return_value=TokenStorage(storage_path),
        ):
            args = MagicMock()
            result = cmd_logout(args)
            assert result == 0

        # Verify file is cleared
        storage2 = TokenStorage(storage_path)
        assert not storage2.exists()
