"""User management commands for Teams Phone CLI.

This module provides CLI commands for listing, showing, and searching
Teams users with their voice configurations.
"""

from __future__ import annotations

import asyncio
from enum import Enum
from typing import TYPE_CHECKING, Any, Optional

import typer

from teams_phone.cli.context import get_context
from teams_phone.exceptions import TeamsPhoneError
from teams_phone.infrastructure import CacheManager, GraphClient
from teams_phone.models import AccountType, UserConfiguration
from teams_phone.services import AuthService
from teams_phone.services.user_service import (
    UserService,
)  # Direct import to avoid circular import

if TYPE_CHECKING:
    from teams_phone.infrastructure import OutputFormatter

users_app = typer.Typer(
    name="users",
    help="Manage Teams users.",
    no_args_is_help=True,
)


class SearchField(str, Enum):
    """Search field options for user search."""

    NAME = "name"
    UPN = "upn"
    PHONE = "phone"


def _run_async(
    ctx: typer.Context,
    coro_factory: Any,
) -> tuple[Any, "OutputFormatter"]:
    """Run an async operation with UserService.

    Creates the required infrastructure (GraphClient, UserService) and runs
    the async operation using asyncio.run().

    Args:
        ctx: Typer context with CLIContext.
        coro_factory: A callable that takes UserService and returns a coroutine.

    Returns:
        Tuple of (result, OutputFormatter) where result is the coroutine's return value.
    """
    cli_ctx = get_context(ctx)
    formatter = cli_ctx.get_output_formatter()
    config_manager = cli_ctx.get_config_manager()
    cache_manager = CacheManager()
    auth_service = AuthService(config_manager, cache_manager)

    async def _execute() -> Any:
        async with GraphClient(auth_service) as graph_client:
            service = UserService(graph_client)
            return await coro_factory(service)

    result = asyncio.run(_execute())
    return result, formatter


def _format_phone_number(user: UserConfiguration) -> str:
    """Format phone number for display, or return dash if none assigned."""
    if user.telephone_numbers:
        return user.telephone_numbers[0].telephone_number
    return "—"


def _format_license_status(user: UserConfiguration) -> str:
    """Format license status for display."""
    return "Licensed" if user.is_enterprise_voice_enabled else "Unlicensed"


def _format_voice_enabled(user: UserConfiguration) -> str:
    """Format voice enabled status for display."""
    return "Yes" if user.is_enterprise_voice_enabled else "No"


def _format_account_type(account_type: AccountType) -> str:
    """Format account type for display."""
    display_names = {
        AccountType.USER: "User",
        AccountType.RESOURCE_ACCOUNT: "Resource Account",
        AccountType.GUEST: "Guest",
        AccountType.SFB_ON_PREM_USER: "SfB On-Prem",
        AccountType.UNKNOWN: "Unknown",
    }
    return display_names.get(account_type, account_type.value)


def _user_to_list_dict(user: UserConfiguration) -> dict[str, Any]:
    """Convert UserConfiguration to dict for list/search table display."""
    return {
        "display_name": user.display_name or "—",
        "upn": user.user_principal_name,
        "account_type": _format_account_type(user.account_type),
        "phone_number": _format_phone_number(user),
        "license_status": _format_license_status(user),
        "voice_enabled": _format_voice_enabled(user),
    }


def _user_to_json_dict(user: UserConfiguration) -> dict[str, Any]:
    """Convert UserConfiguration to dict for JSON output."""
    return {
        "id": user.id,
        "display_name": user.display_name,
        "user_principal_name": user.user_principal_name,
        "account_type": user.account_type.value,
        "is_enterprise_voice_enabled": user.is_enterprise_voice_enabled,
        "telephone_numbers": [
            {
                "telephone_number": t.telephone_number,
                "assignment_category": t.assignment_category,
            }
            for t in user.telephone_numbers
        ],
        "feature_types": user.feature_types,
        "effective_policy_assignments": [
            {
                "policy_type": p.policy_type,
                "policy_assignment": {
                    "display_name": p.policy_assignment.display_name,
                    "assignment_type": p.policy_assignment.assignment_type,
                    "policy_id": p.policy_assignment.policy_id,
                    "group_id": p.policy_assignment.group_id,
                },
            }
            for p in user.effective_policy_assignments
        ],
    }


def _get_policy_value(user: UserConfiguration, policy_type: str) -> str:
    """Get the display name for a specific policy type, or 'Global (Default)' if not set."""
    for assignment in user.effective_policy_assignments:
        if assignment.policy_type == policy_type:
            return assignment.policy_assignment.display_name or "Global (Default)"
    return "Global (Default)"


@users_app.command("list")
def list_users(
    ctx: typer.Context,
    licensed: Optional[bool] = typer.Option(
        None,
        "--licensed/--unlicensed",
        help="Filter by license status (--licensed or --unlicensed).",
    ),
    assigned: Optional[bool] = typer.Option(
        None,
        "--assigned/--unassigned",
        help="Filter by phone number assignment (--assigned or --unassigned).",
    ),
    account_type: Optional[AccountType] = typer.Option(
        None,
        "--account-type",
        "-a",
        help="Filter by account type (user, resourceAccount, guest).",
    ),
    limit: Optional[int] = typer.Option(
        None,
        "--limit",
        "-l",
        help="Limit results to n users.",
    ),
    all_users: bool = typer.Option(
        False,
        "--all",
        help="Fetch all users (may be slow for large tenants).",
    ),
) -> None:
    """List users with voice configuration."""
    cli_ctx = get_context(ctx)
    formatter = cli_ctx.get_output_formatter()

    # Validate mutually exclusive options
    if limit is not None and all_users:
        formatter.error(
            "Cannot specify both --limit and --all",
            remediation="Use either --limit <n> to limit results or --all to fetch all users.",
        )
        raise typer.Exit(5)  # ValidationError exit code

    # Determine max_items: None for --all, provided limit, or default 50
    if all_users:
        max_items = None
    elif limit is not None:
        max_items = limit
    else:
        max_items = 50  # Default limit

    try:
        users, _ = _run_async(
            ctx,
            lambda service: service.list_users(
                licensed=licensed,
                assigned=assigned,
                account_type=account_type,
                max_items=max_items,
            ),
        )

        if cli_ctx.json_output:
            formatter.json([_user_to_json_dict(u) for u in users])
        else:
            if not users:
                formatter.info("No users found matching the specified criteria")
            else:
                formatter.table(
                    data=[_user_to_list_dict(u) for u in users],
                    columns=[
                        "display_name",
                        "upn",
                        "account_type",
                        "phone_number",
                        "license_status",
                        "voice_enabled",
                    ],
                    title="Users",
                )
    except TeamsPhoneError as e:
        formatter.error(e.message, remediation=e.remediation)
        raise typer.Exit(e.exit_code)


@users_app.command("show")
def show_user(
    ctx: typer.Context,
    user: str = typer.Argument(
        ...,
        help="User identifier: UPN (email), display name, or object ID.",
    ),
) -> None:
    """Show detailed voice configuration for a specific user."""
    cli_ctx = get_context(ctx)
    formatter = cli_ctx.get_output_formatter()

    try:
        user_config, _ = _run_async(
            ctx,
            lambda service: service.resolve_user(user),
        )

        if cli_ctx.json_output:
            formatter.json(_user_to_json_dict(user_config))
        else:
            # Identity section
            formatter.table(
                data=[
                    {"field": "Display Name", "value": user_config.display_name or "—"},
                    {"field": "UPN", "value": user_config.user_principal_name},
                    {"field": "Object ID", "value": user_config.id},
                    {
                        "field": "Account Type",
                        "value": _format_account_type(user_config.account_type),
                    },
                ],
                columns=["field", "value"],
                title="Identity",
            )

            # Phone Number section
            if user_config.telephone_numbers:
                phone_data = [
                    {"number": num.telephone_number, "type": num.assignment_category}
                    for num in user_config.telephone_numbers
                ]
            else:
                phone_data = [{"number": "No phone number assigned", "type": "—"}]
            formatter.table(
                data=phone_data,
                columns=["number", "type"],
                title="Phone Numbers",
            )

            # Licensing section
            licensing_data = [
                {
                    "field": "Enterprise Voice",
                    "value": _format_voice_enabled(user_config),
                },
                {"field": "Status", "value": _format_license_status(user_config)},
            ]
            if user_config.feature_types:
                licensing_data.append(
                    {"field": "Features", "value": ", ".join(user_config.feature_types)}
                )
            formatter.table(
                data=licensing_data,
                columns=["field", "value"],
                title="Licensing",
            )

            # Voice Policies section
            formatter.table(
                data=[
                    {
                        "policy": "Calling",
                        "value": _get_policy_value(user_config, "TeamsCallingPolicy"),
                    },
                    {
                        "policy": "Voicemail",
                        "value": _get_policy_value(user_config, "TeamsCallParkPolicy"),
                    },
                    {
                        "policy": "Emergency Calling",
                        "value": _get_policy_value(
                            user_config, "TeamsEmergencyCallingPolicy"
                        ),
                    },
                    {
                        "policy": "Emergency Call Routing",
                        "value": _get_policy_value(
                            user_config, "TeamsEmergencyCallRoutingPolicy"
                        ),
                    },
                    {
                        "policy": "Caller ID",
                        "value": _get_policy_value(user_config, "TeamsCallerIdPolicy"),
                    },
                    {
                        "policy": "Dial Plan",
                        "value": _get_policy_value(user_config, "TenantDialPlan"),
                    },
                    {
                        "policy": "Voice Routing",
                        "value": _get_policy_value(
                            user_config, "TeamsVoiceRoutingPolicy"
                        ),
                    },
                ],
                columns=["policy", "value"],
                title="Voice Policies",
            )

    except TeamsPhoneError as e:
        formatter.error(e.message, remediation=e.remediation)
        raise typer.Exit(e.exit_code)


@users_app.command("search")
def search_users(
    ctx: typer.Context,
    query: str = typer.Argument(
        ...,
        help="Search term (partial match supported).",
    ),
    field: Optional[SearchField] = typer.Option(
        None,
        "--field",
        "-f",
        help="Limit search to specific field: name, upn, phone.",
    ),
    limit: int = typer.Option(
        25,
        "--limit",
        "-l",
        help="Maximum results (default: 25).",
    ),
) -> None:
    """Search for users by name, UPN, or phone number."""
    cli_ctx = get_context(ctx)
    formatter = cli_ctx.get_output_formatter()

    # Modify query based on field option to force specific search behavior
    search_query = query
    if field == SearchField.PHONE:
        # Ensure phone number format for search
        if not query.startswith("+") and not query.isdigit():
            # Try to use as-is, the service will handle it
            search_query = query
        elif query.isdigit():
            search_query = f"+{query}"
        else:
            search_query = query
    elif field == SearchField.UPN:
        # UPN search expects @ - if not present, it will do display name search
        # User should provide full UPN for exact match
        search_query = query
    elif field == SearchField.NAME:
        # Name search is the default for non-phone, non-email queries
        # Force display name search by ensuring no @ or +
        if "@" not in query and not query.startswith("+"):
            search_query = query
        else:
            # User provided email or phone format but wants name search
            # This is an unusual case; proceed with the query as-is
            search_query = query

    try:
        users, _ = _run_async(
            ctx,
            lambda service: service.search_users(search_query, max_results=limit),
        )

        if cli_ctx.json_output:
            formatter.json([_user_to_json_dict(u) for u in users])
        else:
            if not users:
                formatter.info(f"No users found matching '{query}'")
            else:
                formatter.table(
                    data=[_user_to_list_dict(u) for u in users],
                    columns=[
                        "display_name",
                        "upn",
                        "account_type",
                        "phone_number",
                        "license_status",
                        "voice_enabled",
                    ],
                    title=f"Search Results for '{query}'",
                )
    except TeamsPhoneError as e:
        formatter.error(e.message, remediation=e.remediation)
        raise typer.Exit(e.exit_code)
