import typer
from pathlib import Path
from typing import Optional

from rich.panel import Panel
from rich.table import Table

from ..entities.agent import AgentRepository
from ..entities.agent_function import AgentFunctionRepository
from ..entities.agent_environment import AgentEnvironmentRepository
from ..entities.agent_phone_number_assignment import AgentPhoneNumberAssignmentRepository
from ..entities.agent_secret import AgentSecretRepository
from ..entities.organization_phone_number import OrganizationPhoneNumberRepository
from ..entities.organization_secret import OrganizationSecretRepository
from ..entities.organization_telephony import OrganizationTelephonyRepository
from ..entities.user import get_effective_organization_id
from ..utils.agent_lock import load_agent_lock
from ..utils.config import (
    TITLE_STYLE,
    INFO_STYLE,
    ERROR_STYLE,
    ID_STYLE,
    SUCCESS_STYLE,
    WARNING_STYLE,
    NOT_AVAILABLE,
)
from ..utils.utils import console

app = typer.Typer(help="Show detailed information about a resource.")


def format_value(value, true_style=SUCCESS_STYLE, false_style=WARNING_STYLE):
    """Format a value for display."""
    if value is None:
        return f"[{INFO_STYLE}]{NOT_AVAILABLE}[/{INFO_STYLE}]"
    if isinstance(value, bool):
        return f"[{true_style}]Yes[/{true_style}]" if value else f"[{false_style}]No[/{false_style}]"
    return str(value)


def print_section(title: str, items: list[tuple[str, str]]):
    """Print a section with key-value pairs."""
    table = Table(show_header=False, box=None, padding=(0, 2))
    table.add_column("Key", style=INFO_STYLE, width=35)
    table.add_column("Value")

    for key, value in items:
        table.add_row(key, value)

    console.print(Panel(table, title=f"[{TITLE_STYLE}]{title}[/{TITLE_STYLE}]", border_style="dim"))


@app.command("assignment")
def describe_assignment(
    agent: str = typer.Argument(help="Agent name or ID"),
    assignment_id: str = typer.Argument(help="Assignment ID"),
):
    """Show detailed information about a phone number assignment."""
    # Resolve agent
    agent_repo = AgentRepository()
    agent_entity = agent_repo.get_by_name_or_id(agent)

    if not agent_entity:
        console.print(f"[{ERROR_STYLE}]Agent not found: {agent}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    repo = AgentPhoneNumberAssignmentRepository(agent_entity.id)
    assignment = repo.get_by_id(assignment_id)

    if not assignment:
        console.print(f"[{ERROR_STYLE}]Assignment not found: {assignment_id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print()
    console.print(f"[{TITLE_STYLE}]Assignment: {assignment.id}[/{TITLE_STYLE}]")
    console.print(f"[{INFO_STYLE}]Agent: {agent_entity.name}[/{INFO_STYLE}]")
    console.print()

    print_section("Assignment Details", [
        ("ID", f"[{ID_STYLE}]{assignment.id}[/{ID_STYLE}]"),
        ("Agent ID", assignment.agent_id or NOT_AVAILABLE),
        ("Environment ID", assignment.agent_environment_id or NOT_AVAILABLE),
        ("Phone Number ID", assignment.organization_phone_number_id or NOT_AVAILABLE),
    ])

    print_section("Timestamps", [
        ("Created At", assignment.created_at or NOT_AVAILABLE),
        ("Updated At", assignment.updated_at or NOT_AVAILABLE),
    ])


@app.command("agent")
def describe_agent(
    name_or_id: str = typer.Argument(help="Agent name or ID")
):
    """Show detailed information about an agent."""
    repo = AgentRepository()
    agent = repo.get_by_name_or_id(name_or_id)

    if not agent:
        console.print(f"[{ERROR_STYLE}]Agent not found: {name_or_id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print()
    console.print(f"[{TITLE_STYLE}]Agent: {agent.name or NOT_AVAILABLE}[/{TITLE_STYLE}]")
    console.print()

    # Basic Info
    print_section("Basic Information", [
        ("ID", f"[{ID_STYLE}]{agent.id}[/{ID_STYLE}]"),
        ("Name", agent.name or NOT_AVAILABLE),
        ("Description", agent.description or NOT_AVAILABLE),
    ])

    # Voice & Transport
    print_section("Voice & Transport", [
        ("Default Voice ID", agent.default_voice_id or NOT_AVAILABLE),
        ("Default Voice Name", agent.default_voice_name or NOT_AVAILABLE),
        ("Transport", agent.transport or NOT_AVAILABLE),
        ("Telephony ID", agent.telephony_id or NOT_AVAILABLE),
    ])

    # Organization
    print_section("Organization", [
        ("User ID", agent.user_id or NOT_AVAILABLE),
        ("Organization ID", agent.organization_id or NOT_AVAILABLE),
    ])

    # Debug & Tracing
    print_section("Debug & Tracing", [
        ("Debug Environment ID", agent.debug_environment_id or NOT_AVAILABLE),
        ("Tracing Enabled", format_value(agent.tracing_enabled)),
    ])

    # Timestamps
    print_section("Timestamps", [
        ("Created At", agent.created_at or NOT_AVAILABLE),
        ("Updated At", agent.updated_at or NOT_AVAILABLE),
        ("Last Activity At", agent.last_activity_at or NOT_AVAILABLE),
    ])


@app.command("function")
def describe_function(
    agent: str = typer.Argument(help="Agent name or ID"),
    name_or_id: str = typer.Argument(help="Function name, display name, or ID")
):
    """Show detailed information about a function."""
    # First resolve the agent
    agent_repo = AgentRepository()
    agent_entity = agent_repo.get_by_name_or_id(agent)

    if not agent_entity:
        console.print(f"[{ERROR_STYLE}]Agent not found: {agent}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    repo = AgentFunctionRepository(agent_entity.id)
    func = repo.get_by_name_or_id(name_or_id)

    if not func:
        console.print(f"[{ERROR_STYLE}]Function not found: {name_or_id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print()
    console.print(f"[{TITLE_STYLE}]Function: {func.get_display_name()}[/{TITLE_STYLE}]")
    console.print(f"[{INFO_STYLE}]Agent: {agent_entity.name}[/{INFO_STYLE}]")
    console.print()

    # Basic Info
    print_section("Basic Information", [
        ("ID", f"[{ID_STYLE}]{func.id}[/{ID_STYLE}]"),
        ("Name", func.name or NOT_AVAILABLE),
        ("Display Name", func.display_name or NOT_AVAILABLE),
        ("Agent ID", func.agent_id or NOT_AVAILABLE),
    ])

    # Code Configuration
    print_section("Code Configuration", [
        ("Language", func.language or NOT_AVAILABLE),
        ("Strategy", func.strategy or NOT_AVAILABLE),
        ("Code Path", func.code_path or NOT_AVAILABLE),
        ("Is Multifile", format_value(func.is_multifile)),
    ])

    # Code Preview (if available and not too long)
    if func.code:
        code_preview = func.code[:500] + "..." if len(func.code) > 500 else func.code
        console.print(Panel(code_preview, title=f"[{TITLE_STYLE}]Code Preview[/{TITLE_STYLE}]", border_style="dim"))

    # Test Data (if available)
    if func.test_data:
        import json
        test_data_str = json.dumps(func.test_data, indent=2)
        console.print(Panel(test_data_str, title=f"[{TITLE_STYLE}]Test Data[/{TITLE_STYLE}]", border_style="dim"))

    # Timestamps
    print_section("Timestamps", [
        ("Created At", func.created_at or NOT_AVAILABLE),
        ("Updated At", func.updated_at or NOT_AVAILABLE),
    ])


@app.command("environment")
def describe_environment(
    agent: str = typer.Argument(help="Agent name or ID"),
    name_or_id: str = typer.Argument(help="Environment name or ID")
):
    """Show detailed information about an environment."""
    # First resolve the agent
    agent_repo = AgentRepository()
    agent_entity = agent_repo.get_by_name_or_id(agent)

    if not agent_entity:
        console.print(f"[{ERROR_STYLE}]Agent not found: {agent}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    repo = AgentEnvironmentRepository(agent_entity.id)
    env = repo.get_by_name_or_id(name_or_id)

    if not env:
        console.print(f"[{ERROR_STYLE}]Environment not found: {name_or_id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print()
    console.print(f"[{TITLE_STYLE}]Environment: {env.name or NOT_AVAILABLE}[/{TITLE_STYLE}]")
    console.print(f"[{INFO_STYLE}]Agent: {agent_entity.name}[/{INFO_STYLE}]")
    console.print()

    # Basic Info
    print_section("Basic Information", [
        ("ID", f"[{ID_STYLE}]{env.id}[/{ID_STYLE}]"),
        ("Name", env.name or NOT_AVAILABLE),
        ("Agent ID", env.agent_id or NOT_AVAILABLE),
        ("Function ID", env.function_id or NOT_AVAILABLE),
        ("Phone Number", env.phone_number or NOT_AVAILABLE),
        ("Is Debug", format_value(env.is_debug)),
    ])

    # STT Configuration
    print_section("Speech-to-Text (STT)", [
        ("Model", env.stt_model or NOT_AVAILABLE),
        ("Language", env.stt_language or NOT_AVAILABLE),
        ("Endpointing (ms)", format_value(env.stt_endpointing)),
        ("Prompt", env.stt_prompt or NOT_AVAILABLE),
        ("Filter", env.stt_filter or NOT_AVAILABLE),
        ("Noise Reduction", env.stt_noise_reduction_type or NOT_AVAILABLE),
        ("Prewarm Model", format_value(env.stt_prewarm_model)),
        ("Auto Switch", format_value(env.stt_auto_switch)),
    ])

    # Recording
    print_section("Recording", [
        ("Recording Enabled", format_value(env.recording_enabled)),
        ("Recording Location", env.recording_location or NOT_AVAILABLE),
        ("Redaction Enabled", format_value(env.redaction_enabled)),
    ])

    # Error Handling
    print_section("Error Handling", [
        ("Fallback Timeout (s)", format_value(env.error_fallback_timeout_seconds)),
        ("Fallback Time Window (s)", format_value(env.error_fallback_time_window_seconds)),
        ("Fallback Occurrence Threshold", format_value(env.error_fallback_occurrence_threshold)),
        ("Fallback Type", env.error_fallback_type or NOT_AVAILABLE),
        ("Fallback Value", env.error_fallback_value or NOT_AVAILABLE),
    ])

    # Audio Processing
    print_section("Audio Processing", [
        ("Audio Input Delay", format_value(env.audio_input_delay)),
        ("EOT Threshold", format_value(env.eot_threshold)),
        ("EOT Timeout (ms)", format_value(env.eot_timeout_ms)),
        ("Eager EOT Threshold", format_value(env.eager_eot_threshold)),
        ("Startup Audio URL", env.startup_audio_url or NOT_AVAILABLE),
        ("Startup Audio Interruptible", format_value(env.startup_audio_interruptible)),
    ])

    # VAD & Advanced
    print_section("VAD & Advanced", [
        ("VAD Mode", env.vad_mode or NOT_AVAILABLE),
        ("VAD Eagerness", env.vad_eagerness or NOT_AVAILABLE),
        ("Module Rule", env.module_rule or NOT_AVAILABLE),
        ("Ringing Enabled", format_value(env.ringing_enabled)),
    ])

    # Timestamps
    print_section("Timestamps", [
        ("Created At", env.created_at or NOT_AVAILABLE),
        ("Updated At", env.updated_at or NOT_AVAILABLE),
    ])


@app.command("secret")
def describe_secret(
    name_or_id: str = typer.Argument(help="Secret name or ID"),
    agent: Optional[str] = typer.Option(None, "--agent", "-a", help="Agent name or ID"),
):
    """Show detailed information about a secret."""
    organization_id = get_effective_organization_id()
    if not organization_id:
        console.print(f"[{ERROR_STYLE}]Unable to determine organization. Please sign in or set organization ID with 'vr context set-org'.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    # Determine agent - from flag or from agent.lock
    agent_entity = None
    agent_id = None

    if agent:
        agent_repo = AgentRepository()
        agent_entity = agent_repo.get_by_name_or_id(agent)
        if not agent_entity:
            console.print(f"[{ERROR_STYLE}]Agent not found: {agent}[/{ERROR_STYLE}]")
            raise typer.Exit(1)
        agent_id = agent_entity.id
    else:
        voicerun_dir = Path.cwd() / ".voicerun"
        if voicerun_dir.exists():
            lock_data = load_agent_lock(voicerun_dir)
            if lock_data and "id" in lock_data:
                agent_id = lock_data["id"]
                agent_repo = AgentRepository()
                agent_entity = agent_repo.get_by_id(agent_id)

    # Try organization secrets first
    org_repo = OrganizationSecretRepository()
    secret = org_repo.get_by_name_or_id(name_or_id)
    source = "Organization"

    # If not found at org level and we have an agent, try agent secrets
    if not secret and agent_id:
        agent_secret_repo = AgentSecretRepository(agent_id)
        secret = agent_secret_repo.get_by_name_or_id(name_or_id)
        source = f"Agent: {agent_entity.name if agent_entity else agent_id}"

    if not secret:
        console.print(f"[{ERROR_STYLE}]Secret not found: {name_or_id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print(f"[{TITLE_STYLE}]Secret: {secret.name or NOT_AVAILABLE}[/{TITLE_STYLE}]")
    console.print(f"  Source:          {source}")
    console.print(f"  ID:              [{ID_STYLE}]{secret.id}[/{ID_STYLE}]")
    console.print(f"  Name:            {secret.name or NOT_AVAILABLE}")
    if hasattr(secret, "organization_id") and secret.organization_id:
        console.print(f"  Organization ID: {secret.organization_id}")
    if hasattr(secret, "agent_id") and secret.agent_id:
        console.print(f"  Agent ID:        {secret.agent_id}")
    console.print(f"  Created At:      {secret.created_at or NOT_AVAILABLE}")
    console.print(f"  Updated At:      {secret.updated_at or NOT_AVAILABLE}")


@app.command("phonenumber")
def describe_phone_number(
    id: str = typer.Argument(help="Phone number ID"),
):
    """Show detailed information about a phone number."""
    organization_id = get_effective_organization_id()
    if not organization_id:
        console.print(f"[{ERROR_STYLE}]Unable to determine organization. Please sign in or set organization ID with 'vr context set-org'.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    repo = OrganizationPhoneNumberRepository()
    pn = repo.get_by_id(id)

    if not pn:
        console.print(f"[{ERROR_STYLE}]Phone number not found: {id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print(f"[{TITLE_STYLE}]Phone Number: {pn.phone_number or NOT_AVAILABLE}[/{TITLE_STYLE}]")
    console.print(f"  ID:            [{ID_STYLE}]{pn.id}[/{ID_STYLE}]")
    console.print(f"  Phone Number:  {pn.phone_number or NOT_AVAILABLE}")
    console.print(f"  Friendly Name: {pn.friendly_name or NOT_AVAILABLE}")
    console.print(f"  Area Code:     {pn.area_code or NOT_AVAILABLE}")
    console.print(f"  Country Code:  {pn.country_code or NOT_AVAILABLE}")
    console.print(f"  Telephony ID:  {pn.telephony_id or NOT_AVAILABLE}")
    console.print(f"  Created At:    {pn.created_at or NOT_AVAILABLE}")
    console.print(f"  Updated At:    {pn.updated_at or NOT_AVAILABLE}")


@app.command("telephony")
def describe_telephony(
    name_or_id: str = typer.Argument(help="Telephony provider name or ID"),
):
    """Show detailed information about a telephony provider."""
    organization_id = get_effective_organization_id()
    if not organization_id:
        console.print(f"[{ERROR_STYLE}]Unable to determine organization. Please sign in or set organization ID with 'vr context set-org'.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    repo = OrganizationTelephonyRepository()
    provider = repo.get_by_name_or_id(name_or_id)

    if not provider:
        console.print(f"[{ERROR_STYLE}]Telephony provider not found: {name_or_id}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print(f"[{TITLE_STYLE}]Telephony: {provider.name or NOT_AVAILABLE}[/{TITLE_STYLE}]")
    console.print(f"  ID:              [{ID_STYLE}]{provider.id}[/{ID_STYLE}]")
    console.print(f"  Name:            {provider.name or NOT_AVAILABLE}")
    console.print(f"  Provider Type:   {provider.provider_type or NOT_AVAILABLE}")
    console.print(f"  Organization ID: {provider.organization_id or NOT_AVAILABLE}")
    console.print(f"  Created At:      {provider.created_at or NOT_AVAILABLE}")
    console.print(f"  Updated At:      {provider.updated_at or NOT_AVAILABLE}")


if __name__ == "__main__":
    app()
