"""Integration tests for CLI users commands.

This module tests users command workflows including list/show/search
operations, filter options, 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 NotFoundError
from teams_phone.models import AccountType

from tests.integration.cli.conftest import make_user_config


class TestUsersListIntegration:
    """Integration tests for users list command."""

    @pytest.mark.integration
    def test_list_all_users_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """List without filters returns all users in table format."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(upn="jdoe@co.com", display_name="JohnDoe"),
            make_user_config(upn="jsmith@co.com", display_name="JaneSmith"),
        ]
        mocks["user_service"].list_users.return_value = users

        result = runner.invoke(app, ["users", "list"])
        assert result.exit_code == 0
        # Check for short unique names that won't be truncated
        assert "JohnDoe" in result.stdout
        assert "JaneSmith" in result.stdout
        assert "jdoe@" in result.stdout
        assert "jsmith@" in result.stdout

    @pytest.mark.integration
    def test_list_users_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """List users with --json produces valid JSON array."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(
                upn="john.doe@contoso.com",
                display_name="John Doe",
                phone_number="+14255551234",
            ),
        ]
        mocks["user_service"].list_users.return_value = users

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

        data = json.loads(result.stdout)
        assert isinstance(data, list)
        assert len(data) == 1
        assert data[0]["user_principal_name"] == "john.doe@contoso.com"
        assert data[0]["display_name"] == "John Doe"
        assert data[0]["telephone_numbers"][0]["telephone_number"] == "+14255551234"

    @pytest.mark.integration
    def test_list_users_licensed_filter(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--licensed filter passes correct parameter to service."""
        mocks = mock_cli_services_users
        licensed_users = [
            make_user_config(
                upn="licensed@contoso.com",
                display_name="Licensed User",
                is_enterprise_voice_enabled=True,
            ),
        ]
        mocks["user_service"].list_users.return_value = licensed_users

        result = runner.invoke(app, ["users", "list", "--licensed"])
        assert result.exit_code == 0
        # Check for partial name (table may truncate)
        assert "Licensed" in result.stdout

        # Verify the service was called with licensed=True
        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["licensed"] is True

    @pytest.mark.integration
    def test_list_users_unlicensed_filter(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--unlicensed filter passes licensed=False to service."""
        mocks = mock_cli_services_users
        unlicensed_users = [
            make_user_config(
                upn="unlicensed@contoso.com",
                display_name="NoLicense",
                is_enterprise_voice_enabled=False,
            ),
        ]
        mocks["user_service"].list_users.return_value = unlicensed_users

        result = runner.invoke(app, ["users", "list", "--unlicensed"])
        assert result.exit_code == 0
        assert "NoLicense" in result.stdout

        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["licensed"] is False

    @pytest.mark.integration
    def test_list_users_assigned_filter(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--assigned filter passes correct parameter to service."""
        mocks = mock_cli_services_users
        assigned_users = [
            make_user_config(
                upn="assigned@contoso.com",
                display_name="HasPhone",
                phone_number="+14255551234",
            ),
        ]
        mocks["user_service"].list_users.return_value = assigned_users

        result = runner.invoke(app, ["users", "list", "--assigned"])
        assert result.exit_code == 0
        assert "HasPhone" in result.stdout

        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["assigned"] is True

    @pytest.mark.integration
    def test_list_users_unassigned_filter(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--unassigned filter passes assigned=False to service."""
        mocks = mock_cli_services_users
        unassigned_users = [
            make_user_config(
                upn="unassigned@contoso.com",
                display_name="NoPhone",
                phone_number=None,
            ),
        ]
        mocks["user_service"].list_users.return_value = unassigned_users

        result = runner.invoke(app, ["users", "list", "--unassigned"])
        assert result.exit_code == 0
        assert "NoPhone" in result.stdout

        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["assigned"] is False

    @pytest.mark.integration
    def test_list_users_account_type_filter(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--account-type filter passes correct parameter to service."""
        mocks = mock_cli_services_users
        resource_accounts = [
            make_user_config(
                upn="resource@contoso.com",
                display_name="ResAcct",
                account_type=AccountType.RESOURCE_ACCOUNT,
            ),
        ]
        mocks["user_service"].list_users.return_value = resource_accounts

        result = runner.invoke(
            app, ["users", "list", "--account-type", "resourceAccount"]
        )
        assert result.exit_code == 0
        assert "ResAcct" in result.stdout

        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["account_type"] == AccountType.RESOURCE_ACCOUNT

    @pytest.mark.integration
    def test_list_users_limit_option(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--limit option passes correct max_items to service."""
        mocks = mock_cli_services_users
        users = [make_user_config(upn="user@contoso.com", display_name="Test User")]
        mocks["user_service"].list_users.return_value = users

        result = runner.invoke(app, ["--no-color", "users", "list", "--limit", "10"])
        assert result.exit_code == 0

        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["max_items"] == 10

    @pytest.mark.integration
    def test_list_users_all_option(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--all option passes max_items=None to service."""
        mocks = mock_cli_services_users
        users = [make_user_config(upn="user@contoso.com", display_name="Test User")]
        mocks["user_service"].list_users.return_value = users

        result = runner.invoke(app, ["--no-color", "users", "list", "--all"])
        assert result.exit_code == 0

        call_kwargs = mocks["user_service"].list_users.call_args[1]
        assert call_kwargs["max_items"] is None

    @pytest.mark.integration
    def test_list_users_limit_and_all_mutually_exclusive(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--limit and --all together produces ValidationError exit code 5."""
        result = runner.invoke(
            app, ["--no-color", "users", "list", "--limit", "10", "--all"]
        )
        assert result.exit_code == 5
        assert "Cannot specify both" in result.stdout

    @pytest.mark.integration
    def test_list_users_empty_result(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """Empty result shows info message in table mode."""
        mocks = mock_cli_services_users
        mocks["user_service"].list_users.return_value = []

        result = runner.invoke(app, ["--no-color", "users", "list"])
        assert result.exit_code == 0
        assert "No users found" in result.stdout


class TestUsersShowIntegration:
    """Integration tests for users show command."""

    @pytest.mark.integration
    def test_show_user_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """Show user displays detailed information in table format."""
        mocks = mock_cli_services_users
        user = make_user_config(
            upn="john.doe@contoso.com",
            display_name="John Doe",
            phone_number="+14255551234",
            policies=[
                ("TeamsCallingPolicy", "AllowCalling"),
                ("TenantDialPlan", "US-DialPlan"),
            ],
        )
        mocks["user_service"].resolve_user.return_value = user

        result = runner.invoke(
            app, ["--no-color", "users", "show", "john.doe@contoso.com"]
        )
        assert result.exit_code == 0
        assert "John Doe" in result.stdout
        assert "john.doe@contoso.com" in result.stdout
        assert "+14255551234" in result.stdout
        assert "AllowCalling" in result.stdout

    @pytest.mark.integration
    def test_show_user_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """Show user with --json produces valid JSON object."""
        mocks = mock_cli_services_users
        user = make_user_config(
            user_id="user-12345",
            upn="john.doe@contoso.com",
            display_name="John Doe",
            phone_number="+14255551234",
        )
        mocks["user_service"].resolve_user.return_value = user

        result = runner.invoke(app, ["--json", "users", "show", "john.doe@contoso.com"])
        assert result.exit_code == 0

        data = json.loads(result.stdout)
        assert data["id"] == "user-12345"
        assert data["user_principal_name"] == "john.doe@contoso.com"
        assert data["display_name"] == "John Doe"
        assert data["telephone_numbers"][0]["telephone_number"] == "+14255551234"
        assert "effective_policy_assignments" in data

    @pytest.mark.integration
    def test_show_user_not_found(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """NotFoundError results in exit code 4."""
        mocks = mock_cli_services_users
        mocks["user_service"].resolve_user.side_effect = NotFoundError(
            "User 'nonexistent@contoso.com' not found",
            remediation="Verify the user principal name is correct.",
        )

        result = runner.invoke(app, ["users", "show", "nonexistent@contoso.com"])
        assert result.exit_code == 4
        assert "not found" in result.stdout.lower()


class TestUsersSearchIntegration:
    """Integration tests for users search command."""

    @pytest.mark.integration
    def test_search_users_table_output(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """Search returns matching users in table format."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(upn="john.doe@contoso.com", display_name="JohnDoe"),
            make_user_config(upn="johnny.smith@contoso.com", display_name="JohnnyS"),
        ]
        mocks["user_service"].search_users.return_value = users

        result = runner.invoke(app, ["users", "search", "john"])
        assert result.exit_code == 0
        # Check for partial names (table may truncate)
        assert "JohnDoe" in result.stdout
        assert "JohnnyS" in result.stdout
        assert "Search Results" in result.stdout

    @pytest.mark.integration
    def test_search_users_json_output(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """Search with --json produces valid JSON array."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(upn="john.doe@contoso.com", display_name="John Doe"),
        ]
        mocks["user_service"].search_users.return_value = users

        result = runner.invoke(app, ["--json", "users", "search", "john"])
        assert result.exit_code == 0

        data = json.loads(result.stdout)
        assert isinstance(data, list)
        assert len(data) == 1
        assert data[0]["user_principal_name"] == "john.doe@contoso.com"

    @pytest.mark.integration
    def test_search_users_with_field_filter(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--field filter restricts search to specific field."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(upn="john.doe@contoso.com", display_name="John Doe"),
        ]
        mocks["user_service"].search_users.return_value = users

        result = runner.invoke(
            app, ["--no-color", "users", "search", "john", "--field", "name"]
        )
        assert result.exit_code == 0
        assert "John Doe" in result.stdout

    @pytest.mark.integration
    def test_search_users_with_limit(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--limit option passes max_results to service."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(upn="user@contoso.com", display_name="Test User"),
        ]
        mocks["user_service"].search_users.return_value = users

        result = runner.invoke(
            app, ["--no-color", "users", "search", "test", "--limit", "5"]
        )
        assert result.exit_code == 0

        call_kwargs = mocks["user_service"].search_users.call_args[1]
        assert call_kwargs["max_results"] == 5

    @pytest.mark.integration
    def test_search_users_empty_result(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """Empty search result shows info message."""
        mocks = mock_cli_services_users
        mocks["user_service"].search_users.return_value = []

        result = runner.invoke(app, ["--no-color", "users", "search", "nonexistent"])
        assert result.exit_code == 0
        assert "No users found" in result.stdout

    @pytest.mark.integration
    def test_search_users_phone_field(
        self,
        runner: CliRunner,
        mock_cli_services_users: dict[str, MagicMock],
    ) -> None:
        """--field phone searches by phone number."""
        mocks = mock_cli_services_users
        users = [
            make_user_config(
                upn="john.doe@contoso.com",
                display_name="John Doe",
                phone_number="+14255551234",
            ),
        ]
        mocks["user_service"].search_users.return_value = users

        result = runner.invoke(
            app, ["--no-color", "users", "search", "+14255551234", "--field", "phone"]
        )
        assert result.exit_code == 0
        assert "John Doe" in result.stdout
