"""Integration tests for CLI auth commands.

This module tests auth command workflows including login/logout flows,
token refresh scenarios, and output format validation (table/JSON).
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from unittest.mock import MagicMock

import pytest
from typer.testing import CliRunner

from teams_phone.cli.main import app
from teams_phone.models import AuthStatus, AuthStatusType, CachedToken

from tests.integration.cli.conftest import (
    make_auth_status,
    make_cached_token,
)


class TestAuthWorkflows:
    """Integration tests for auth command workflows."""

    @pytest.mark.integration
    def test_login_then_status(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Login flow followed by status check verifying AUTHENTICATED state."""
        mocks = mock_cli_services_auth
        token = make_cached_token()
        auth_status = make_auth_status(status=AuthStatusType.AUTHENTICATED)

        mocks["auth_service"].login.return_value = token
        mocks["auth_service"].get_status.return_value = auth_status

        # Login
        login_result = runner.invoke(app, ["auth", "login"])
        assert login_result.exit_code == 0
        assert "authenticated" in login_result.stdout.lower()
        mocks["auth_service"].login.assert_called_once_with(None, force=False)

        # Check status
        status_result = runner.invoke(app, ["auth", "status"])
        assert status_result.exit_code == 0
        assert "authenticated" in status_result.stdout.lower()
        mocks["auth_service"].get_status.assert_called_once_with(None)

    @pytest.mark.integration
    def test_logout_then_status(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Logout followed by status showing UNAUTHENTICATED."""
        mocks = mock_cli_services_auth
        unauthenticated_status = make_auth_status(
            status=AuthStatusType.UNAUTHENTICATED,
            tenant_id=None,
        )

        mocks["auth_service"].get_status.return_value = unauthenticated_status

        # Logout
        logout_result = runner.invoke(app, ["auth", "logout"])
        assert logout_result.exit_code == 0
        assert "logged out" in logout_result.stdout.lower()
        mocks["auth_service"].logout.assert_called_once_with(None, all_tenants=False)

        # Check status - should show unauthenticated
        status_result = runner.invoke(app, ["auth", "status"])
        assert status_result.exit_code == 0
        assert "unauthenticated" in status_result.stdout.lower()

    @pytest.mark.integration
    def test_status_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Verify table format includes tenant, status, expiry columns."""
        mocks = mock_cli_services_auth
        expires = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
        auth_status = AuthStatus(
            status=AuthStatusType.AUTHENTICATED,
            tenant_name="contoso",
            tenant_id="tenant-abc-def",
            expires_at=expires,
        )
        mocks["auth_service"].get_status.return_value = auth_status

        result = runner.invoke(app, ["--no-color", "auth", "status"])
        assert result.exit_code == 0

        # Verify key information is present in table output
        assert "authenticated" in result.stdout.lower()
        assert "contoso" in result.stdout
        assert "tenant-abc-def" in result.stdout
        assert "2025" in result.stdout  # Expiry year

    @pytest.mark.integration
    def test_status_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Verify JSON structure with json.loads() validation."""
        mocks = mock_cli_services_auth
        expires = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
        auth_status = AuthStatus(
            status=AuthStatusType.AUTHENTICATED,
            tenant_name="contoso",
            tenant_id="tenant-id-12345",
            expires_at=expires,
        )
        mocks["auth_service"].get_status.return_value = auth_status

        result = runner.invoke(app, ["--json", "auth", "status"])
        assert result.exit_code == 0

        # Parse and validate JSON structure
        data = json.loads(result.stdout)
        assert data["status"] == "authenticated"
        assert data["tenant_name"] == "contoso"
        assert data["tenant_id"] == "tenant-id-12345"
        assert "expires_at" in data
        assert data["expires_at"] is not None

    @pytest.mark.integration
    def test_refresh_expired_token(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Test token refresh scenario with force flag."""
        mocks = mock_cli_services_auth
        new_expires = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
        refreshed_token = CachedToken(
            access_token="new-refreshed-token",
            expires_at=new_expires,
            tenant_id="tenant-id-12345",
            scopes=["https://graph.microsoft.com/.default"],
        )
        mocks["auth_service"].refresh_token.return_value = refreshed_token

        # Use --force to ensure refresh happens regardless of token state
        result = runner.invoke(app, ["auth", "refresh", "--force"])
        assert result.exit_code == 0
        assert "refreshed" in result.stdout.lower()
        mocks["auth_service"].refresh_token.assert_called_once_with(None)

        # Verify JSON output for refresh
        mocks["auth_service"].refresh_token.reset_mock()
        mocks["auth_service"].refresh_token.return_value = refreshed_token

        json_result = runner.invoke(app, ["--json", "auth", "refresh", "--force"])
        assert json_result.exit_code == 0
        data = json.loads(json_result.stdout)
        assert data["status"] == "refreshed"
        assert data["tenant_id"] == "tenant-id-12345"

    @pytest.mark.integration
    def test_login_already_authenticated(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Test --force flag behavior when already logged in."""
        mocks = mock_cli_services_auth
        token = make_cached_token()

        # First login without force
        mocks["auth_service"].login.return_value = token
        result1 = runner.invoke(app, ["auth", "login"])
        assert result1.exit_code == 0
        mocks["auth_service"].login.assert_called_with(None, force=False)

        # Second login with --force flag
        mocks["auth_service"].login.reset_mock()
        mocks["auth_service"].login.return_value = token
        result2 = runner.invoke(app, ["auth", "login", "--force"])
        assert result2.exit_code == 0
        mocks["auth_service"].login.assert_called_with(None, force=True)

        # Verify service was called with force=True
        call_args = mocks["auth_service"].login.call_args
        assert call_args[1]["force"] is True

    @pytest.mark.integration
    def test_logout_all_tenants(
        self,
        runner: CliRunner,
        mock_cli_services_auth: dict[str, MagicMock],
    ) -> None:
        """Test --all flag clears all tenant tokens."""
        mocks = mock_cli_services_auth

        # Logout with --all flag
        result = runner.invoke(app, ["auth", "logout", "--all"])
        assert result.exit_code == 0
        assert "all tenants" in result.stdout.lower()
        mocks["auth_service"].logout.assert_called_once_with(None, all_tenants=True)

        # Verify JSON output for logout --all
        mocks["auth_service"].logout.reset_mock()
        json_result = runner.invoke(app, ["--json", "auth", "logout", "--all"])
        assert json_result.exit_code == 0
        data = json.loads(json_result.stdout)
        assert data["status"] == "logged_out"
        assert data["all_tenants"] is True
