"""Integration tests for CLI tenants commands.

This module tests tenants command workflows including add/remove/switch/list
operations, connection testing, and output format validation (table/JSON).
"""

from __future__ import annotations

import json
from unittest.mock import MagicMock

import pytest
from typer.testing import CliRunner

from teams_phone.cli.main import app
from teams_phone.exceptions import ConfigurationError
from teams_phone.models import ConnectionTestResult

from tests.integration.cli.conftest import make_tenant


class TestTenantsWorkflows:
    """Integration tests for tenants command workflows."""

    @pytest.mark.integration
    def test_add_then_list_tenant(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Add tenant and verify it appears in list."""
        mocks = mock_cli_services_tenants
        new_tenant = make_tenant(name="contoso", display_name="Contoso Ltd")

        # Add tenant
        add_result = runner.invoke(
            app,
            [
                "tenants",
                "add",
                "contoso",
                "11111111-1111-1111-1111-111111111111",
                "22222222-2222-2222-2222-222222222222",
                "--display-name",
                "Contoso Ltd",
            ],
        )
        assert add_result.exit_code == 0
        assert "Added tenant" in add_result.stdout
        mocks["tenant_service"].add_tenant.assert_called_once()

        # Verify list shows the new tenant
        mocks["tenant_service"].list_tenants.return_value = [new_tenant]
        mocks["tenant_service"].get_current_tenant.return_value = new_tenant

        list_result = runner.invoke(app, ["tenants", "list"])
        assert list_result.exit_code == 0
        assert "contoso" in list_result.stdout
        assert "Contoso Ltd" in list_result.stdout

    @pytest.mark.integration
    def test_add_then_show_tenant(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Add tenant and verify show displays all config."""
        mocks = mock_cli_services_tenants
        new_tenant = make_tenant(
            name="fabrikam",
            display_name="Fabrikam Inc",
            tenant_id="33333333-3333-3333-3333-333333333333",
            client_id="44444444-4444-4444-4444-444444444444",
        )

        # Add tenant
        add_result = runner.invoke(
            app,
            [
                "tenants",
                "add",
                "fabrikam",
                "33333333-3333-3333-3333-333333333333",
                "44444444-4444-4444-4444-444444444444",
                "--display-name",
                "Fabrikam Inc",
            ],
        )
        assert add_result.exit_code == 0
        mocks["tenant_service"].add_tenant.assert_called_once()

        # Verify current shows the tenant details
        mocks["tenant_service"].get_current_tenant.return_value = new_tenant

        current_result = runner.invoke(app, ["tenants", "current"])
        assert current_result.exit_code == 0
        assert "Fabrikam Inc" in current_result.stdout
        assert "fabrikam" in current_result.stdout
        assert "33333333-3333-3333-3333-333333333333" in current_result.stdout
        assert "44444444-4444-4444-4444-444444444444" in current_result.stdout
        assert "certificate" in current_result.stdout.lower()

    @pytest.mark.integration
    def test_switch_default_tenant(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Switch tenant and verify it becomes current."""
        mocks = mock_cli_services_tenants
        fabrikam = make_tenant(name="fabrikam", display_name="Fabrikam Inc")

        mocks["tenant_service"].switch_tenant.return_value = fabrikam

        # Switch to fabrikam
        switch_result = runner.invoke(app, ["tenants", "switch", "fabrikam"])
        assert switch_result.exit_code == 0
        assert "Switched to tenant" in switch_result.stdout
        assert "Fabrikam Inc" in switch_result.stdout
        mocks["tenant_service"].switch_tenant.assert_called_once_with(
            "fabrikam", auto_login=True
        )

        # Verify current now shows fabrikam
        mocks["tenant_service"].get_current_tenant.return_value = fabrikam

        current_result = runner.invoke(app, ["tenants", "current"])
        assert current_result.exit_code == 0
        assert "Fabrikam Inc" in current_result.stdout

    @pytest.mark.integration
    def test_remove_tenant(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Remove tenant and verify it's gone from list."""
        mocks = mock_cli_services_tenants
        contoso = make_tenant(name="contoso", display_name="Contoso Ltd")

        # Setup: list shows contoso
        mocks["tenant_service"].list_tenants.return_value = [contoso]
        mocks["tenant_service"].get_current_tenant.return_value = contoso

        list_result = runner.invoke(app, ["tenants", "list"])
        assert "contoso" in list_result.stdout

        # Remove contoso with --force to skip confirmation
        remove_result = runner.invoke(app, ["tenants", "remove", "contoso", "--force"])
        assert remove_result.exit_code == 0
        assert "Removed tenant" in remove_result.stdout
        mocks["tenant_service"].remove_tenant.assert_called_once_with("contoso")

        # Verify list no longer shows contoso
        mocks["tenant_service"].list_tenants.return_value = []
        mocks["tenant_service"].get_current_tenant.side_effect = ConfigurationError(
            "No default tenant"
        )

        empty_list_result = runner.invoke(app, ["tenants", "list"])
        assert empty_list_result.exit_code == 0
        assert "No tenants configured" in empty_list_result.stdout

    @pytest.mark.integration
    def test_test_connection_success(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Mock successful Graph API connection."""
        mocks = mock_cli_services_tenants
        tenant = make_tenant()
        test_result = ConnectionTestResult(
            success=True,
            tenant_name="test-tenant",
            latency_ms=150,
            permissions=["https://graph.microsoft.com/.default"],
        )

        mocks["tenant_service"].get_current_tenant.return_value = tenant
        mocks["tenant_service"].test_connection.return_value = test_result

        # Test connection for current tenant
        result = runner.invoke(app, ["tenants", "test"])
        assert result.exit_code == 0
        assert "successful" in result.stdout.lower()
        assert "150" in result.stdout  # Latency

        # Verify JSON output
        json_result = runner.invoke(app, ["--json", "tenants", "test"])
        assert json_result.exit_code == 0
        data = json.loads(json_result.stdout)
        assert data["success"] is True
        assert data["tenant_name"] == "test-tenant"
        assert data["latency_ms"] == 150
        assert "https://graph.microsoft.com/.default" in data["permissions"]

    @pytest.mark.integration
    def test_test_connection_failure(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Mock connection failure with proper error."""
        mocks = mock_cli_services_tenants
        tenant = make_tenant()
        test_result = ConnectionTestResult(
            success=False,
            tenant_name="test-tenant",
            latency_ms=None,
            error_message="Certificate not found at /path/to/cert.pem",
        )

        mocks["tenant_service"].get_current_tenant.return_value = tenant
        mocks["tenant_service"].test_connection.return_value = test_result

        # Test connection - should fail with exit code 1
        result = runner.invoke(app, ["tenants", "test"])
        assert result.exit_code == 1
        assert "failed" in result.stdout.lower()

        # Verify JSON output for failure
        json_result = runner.invoke(app, ["--json", "tenants", "test"])
        # JSON mode doesn't raise exit code 1 for test failure
        assert json_result.exit_code == 0
        data = json.loads(json_result.stdout)
        assert data["success"] is False
        assert data["error_message"] == "Certificate not found at /path/to/cert.pem"

    @pytest.mark.integration
    def test_list_marks_current_tenant(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """Verify 'Current' indicator in list output."""
        mocks = mock_cli_services_tenants
        contoso = make_tenant(name="contoso", display_name="Contoso Ltd")
        fabrikam = make_tenant(
            name="fabrikam",
            display_name="Fabrikam Inc",
            tenant_id="33333333-3333-3333-3333-333333333333",
        )

        mocks["tenant_service"].list_tenants.return_value = [contoso, fabrikam]
        mocks["tenant_service"].get_current_tenant.return_value = contoso

        result = runner.invoke(app, ["tenants", "list"])
        assert result.exit_code == 0

        # Verify both tenants appear
        assert "contoso" in result.stdout
        assert "fabrikam" in result.stdout

        # Verify "Current" indicator appears (for contoso)
        assert "Current" in result.stdout

        # Verify JSON output includes is_current flag
        json_result = runner.invoke(app, ["--json", "tenants", "list"])
        assert json_result.exit_code == 0
        data = json.loads(json_result.stdout)
        assert len(data) == 2

        contoso_entry = next(t for t in data if t["name"] == "contoso")
        fabrikam_entry = next(t for t in data if t["name"] == "fabrikam")
        assert contoso_entry["is_current"] is True
        assert fabrikam_entry["is_current"] is False

    @pytest.mark.integration
    def test_tenants_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_tenants: dict[str, MagicMock],
    ) -> None:
        """All commands produce valid JSON with --json flag."""
        mocks = mock_cli_services_tenants
        tenant = make_tenant()
        test_result = ConnectionTestResult(
            success=True,
            tenant_name="test-tenant",
            latency_ms=100,
            permissions=["https://graph.microsoft.com/.default"],
        )

        mocks["tenant_service"].list_tenants.return_value = [tenant]
        mocks["tenant_service"].get_current_tenant.return_value = tenant
        mocks["tenant_service"].switch_tenant.return_value = tenant
        mocks["tenant_service"].test_connection.return_value = test_result

        # Test list JSON
        list_result = runner.invoke(app, ["--json", "tenants", "list"])
        assert list_result.exit_code == 0
        list_data = json.loads(list_result.stdout)
        assert isinstance(list_data, list)
        assert len(list_data) == 1
        assert list_data[0]["name"] == "test-tenant"

        # Test current JSON
        current_result = runner.invoke(app, ["--json", "tenants", "current"])
        assert current_result.exit_code == 0
        current_data = json.loads(current_result.stdout)
        assert current_data["name"] == "test-tenant"
        assert "tenant_id" in current_data
        assert "client_id" in current_data

        # Test switch JSON
        switch_result = runner.invoke(
            app, ["--json", "tenants", "switch", "test-tenant"]
        )
        assert switch_result.exit_code == 0
        switch_data = json.loads(switch_result.stdout)
        assert switch_data["status"] == "switched"
        assert switch_data["name"] == "test-tenant"

        # Test test JSON
        test_result_output = runner.invoke(app, ["--json", "tenants", "test"])
        assert test_result_output.exit_code == 0
        test_data = json.loads(test_result_output.stdout)
        assert test_data["success"] is True
        assert "latency_ms" in test_data

        # Test add JSON
        add_result = runner.invoke(
            app,
            [
                "--json",
                "tenants",
                "add",
                "new-tenant",
                "55555555-5555-5555-5555-555555555555",
                "66666666-6666-6666-6666-666666666666",
                "--display-name",
                "New Tenant",
            ],
        )
        assert add_result.exit_code == 0
        add_data = json.loads(add_result.stdout)
        assert add_data["status"] == "added"
        assert add_data["name"] == "new-tenant"

        # Test remove JSON (with --force to skip prompt)
        remove_result = runner.invoke(
            app, ["--json", "tenants", "remove", "test-tenant", "--force"]
        )
        assert remove_result.exit_code == 0
        remove_data = json.loads(remove_result.stdout)
        assert remove_data["status"] == "removed"
        assert remove_data["name"] == "test-tenant"
