from pathlib import Path

import typer
from typing import Optional

from ..entities.agent import AgentRepository
from ..entities.agent_environment import AgentEnvironmentRepository
from ..entities.agent_phone_number_assignment import AgentPhoneNumberAssignment, AgentPhoneNumberAssignmentRepository
from ..entities.agent_secret import AgentSecret, AgentSecretRepository
from ..entities.organization_phone_number import OrganizationPhoneNumber, OrganizationPhoneNumberRepository
from ..entities.organization_secret import OrganizationSecret, OrganizationSecretRepository
from ..entities.organization_telephony import (
    OrganizationTelephonyRepository,
    PROVIDER_TYPES,
    PROVIDER_CREDENTIAL_FIELDS,
)
from ..entities.agent_template import AgentTemplate, AgentTemplateRepository
from ..entities.user import get_current_user, get_effective_organization_id
from ..utils.vrignore import load_vrignore_patterns, matches_vrignore
from ..utils.config import (
    TITLE_STYLE,
    INFO_STYLE,
    ERROR_STYLE,
    SUCCESS_STYLE,
)
from ..utils.utils import console, print_warning, prompt

app = typer.Typer(help="Create resources.")


@app.command("secret")
@app.command("secrets", hidden=True)
def create_secret(
    name: str = typer.Argument(help="Secret name"),
    value: str = typer.Argument(help="Secret value"),
    agent: Optional[str] = typer.Option(None, "--agent", "-a", help="Agent name or ID (omit for organization secret)"),
):
    """Create a secret. Use --agent for agent secret, or omit for organization secret."""
    if agent:
        # Create agent secret
        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 = AgentSecretRepository(agent_entity.id)
        secret = AgentSecret(name=name, value=value)
        created_secret = repo.create(secret)

        if not created_secret:
            console.print(f"[{ERROR_STYLE}]Failed to create secret '{name}'.[/{ERROR_STYLE}]")
            raise typer.Exit(1)

        console.print(f"[{SUCCESS_STYLE}]Secret '{name}' created successfully for agent '{agent_entity.name}'.[/{SUCCESS_STYLE}]")
        console.print(f"  [{INFO_STYLE}]ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{created_secret.id}[/{TITLE_STYLE}]")
    else:
        # Create organization 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)

        repo = OrganizationSecretRepository()
        secret = OrganizationSecret(name=name, value=value)
        created_secret = repo.create(secret)

        if not created_secret:
            console.print(f"[{ERROR_STYLE}]Failed to create organization secret '{name}'.[/{ERROR_STYLE}]")
            raise typer.Exit(1)

        console.print(f"[{SUCCESS_STYLE}]Organization secret '{name}' created successfully.[/{SUCCESS_STYLE}]")
        console.print(f"  [{INFO_STYLE}]ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{created_secret.id}[/{TITLE_STYLE}]")


@app.command("assignment")
@app.command("assignments", hidden=True)
def create_assignment(
    agent: str = typer.Argument(help="Agent name or ID"),
    environment: str = typer.Argument(help="Environment name or ID"),
    phone_number_id: str = typer.Argument(help="Organization phone number ID"),
    configure: bool = typer.Option(False, "--configure", help="Configure the phone number with the telephony provider after assignment"),
):
    """Assign a phone number to an agent environment."""
    # 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)

    # Resolve environment
    env_repo = AgentEnvironmentRepository(agent_entity.id)
    env_entity = env_repo.get_by_name_or_id(environment)

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

    repo = AgentPhoneNumberAssignmentRepository(agent_entity.id)
    assignment = AgentPhoneNumberAssignment(
        agent_environment_id=env_entity.id,
        organization_phone_number_id=phone_number_id,
    )
    created = repo.create(assignment)

    if not created:
        console.print(f"[{ERROR_STYLE}]Failed to create assignment.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print(f"[{SUCCESS_STYLE}]Assignment created successfully.[/{SUCCESS_STYLE}]")
    console.print(f"  [{INFO_STYLE}]ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{created.id}[/{TITLE_STYLE}]")
    console.print(f"  [{INFO_STYLE}]Phone Number ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{created.organization_phone_number_id}[/{TITLE_STYLE}]")
    console.print(f"  [{INFO_STYLE}]Environment ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{created.agent_environment_id}[/{TITLE_STYLE}]")

    if configure:
        configured = repo.configure(created.id)
        if not configured:
            console.print(f"[{ERROR_STYLE}]Failed to configure phone number.[/{ERROR_STYLE}]")
            raise typer.Exit(1)
        console.print(f"[{SUCCESS_STYLE}]Phone number configured successfully.[/{SUCCESS_STYLE}]")


@app.command("phonenumber")
@app.command("phonenumbers", hidden=True)
def create_phone_number(
    telephony_id: str = typer.Argument("voicerun", help="Telephony provider ID"),
    purchase: bool = typer.Option(False, "--purchase", help="Purchase a new number from the telephony provider"),
    area_code: Optional[str] = typer.Option(None, "--area-code", "-a", help="Area code for the phone number"),
    country_code: Optional[str] = typer.Option(None, "--country-code", "-c", help="Country code (defaults to US)"),
    friendly_name: Optional[str] = typer.Option(None, "--friendly-name", "-n", help="Friendly name for the phone number"),
    phone_number_value: Optional[str] = typer.Option(None, "--phone-number", "-p", help="Phone number to register (without --purchase)"),
):
    """Create or purchase a phone number for the organization."""
    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()

    if purchase:
        phone_number = repo.purchase(
            telephony_id=telephony_id,
            area_code=area_code,
            country_code=country_code,
            friendly_name=friendly_name,
        )

        if not phone_number:
            console.print(f"[{ERROR_STYLE}]Failed to purchase phone number.[/{ERROR_STYLE}]")
            raise typer.Exit(1)

        console.print(f"[{SUCCESS_STYLE}]Phone number purchased successfully.[/{SUCCESS_STYLE}]")
    else:
        entity = OrganizationPhoneNumber(
            telephony_id=telephony_id,
            phone_number=phone_number_value,
            area_code=area_code,
            country_code=country_code,
            friendly_name=friendly_name,
        )
        phone_number = repo.create(entity)

        if not phone_number:
            console.print(f"[{ERROR_STYLE}]Failed to create phone number.[/{ERROR_STYLE}]")
            raise typer.Exit(1)

        console.print(f"[{SUCCESS_STYLE}]Phone number created successfully.[/{SUCCESS_STYLE}]")

    console.print(f"  [{INFO_STYLE}]Phone Number:[/{INFO_STYLE}] [{TITLE_STYLE}]{phone_number.phone_number}[/{TITLE_STYLE}]")
    console.print(f"  [{INFO_STYLE}]ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{phone_number.id}[/{TITLE_STYLE}]")
    if phone_number.friendly_name:
        console.print(f"  [{INFO_STYLE}]Friendly Name:[/{INFO_STYLE}] [{TITLE_STYLE}]{phone_number.friendly_name}[/{TITLE_STYLE}]")


@app.command("telephony")
def create_telephony(
    name: Optional[str] = typer.Option(None, "--name", "-n", help="Name for the telephony provider"),
    provider_type: Optional[str] = typer.Option(None, "--provider-type", "-p", help=f"Provider type ({', '.join(PROVIDER_TYPES)})"),
    # Twilio credentials
    account_sid: Optional[str] = typer.Option(None, "--account-sid", help="Twilio Account SID"),
    api_key_sid: Optional[str] = typer.Option(None, "--api-key-sid", help="Twilio API Key SID"),
    api_key_secret: Optional[str] = typer.Option(None, "--api-key-secret", help="Twilio API Key Secret"),
    # Telnyx credentials
    api_key: Optional[str] = typer.Option(None, "--api-key", help="Telnyx API Key"),
):
    """Create a telephony provider for the organization."""
    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)

    # Prompt for missing values interactively
    if not name:
        name = prompt("Name")

    if not provider_type:
        def _validate_provider_type(value):
            if value not in PROVIDER_TYPES:
                raise typer.BadParameter(f"Must be one of: {', '.join(PROVIDER_TYPES)}")
            return value

        provider_type = prompt(
            f"Provider type ({', '.join(PROVIDER_TYPES)})",
            validation=_validate_provider_type,
        )

    if provider_type not in PROVIDER_TYPES:
        console.print(f"[{ERROR_STYLE}]Invalid provider type '{provider_type}'. Must be one of: {', '.join(PROVIDER_TYPES)}[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    # Build credentials from flags or interactive prompts
    flag_values = {
        "account_sid": account_sid,
        "api_key_sid": api_key_sid,
        "api_key_secret": api_key_secret,
        "api_key": api_key,
    }

    credentials = {}
    for field_key, field_label in PROVIDER_CREDENTIAL_FIELDS[provider_type]:
        # Convert snake_case field key to camelCase for API
        camel_key = "".join(
            word.capitalize() if i > 0 else word
            for i, word in enumerate(field_key.split("_"))
        )
        value = flag_values.get(field_key)
        if not value:
            value = prompt(field_label)
        credentials[camel_key] = value

    repo = OrganizationTelephonyRepository()
    telephony = repo.create_with_credentials(name, provider_type, credentials)

    if not telephony:
        console.print(f"[{ERROR_STYLE}]Failed to create telephony provider.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print(f"[{SUCCESS_STYLE}]Telephony provider '{name}' created successfully.[/{SUCCESS_STYLE}]")
    console.print(f"  [{INFO_STYLE}]ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{telephony.id}[/{TITLE_STYLE}]")
    console.print(f"  [{INFO_STYLE}]Provider:[/{INFO_STYLE}] [{TITLE_STYLE}]{telephony.provider_type}[/{TITLE_STYLE}]")


@app.command("template")
@app.command("templates", hidden=True)
def create_template(
    name: str = typer.Argument(help="Template name"),
    description: Optional[str] = typer.Option(None, "--description", "-d", help="Template description"),
    category: Optional[str] = typer.Option(None, "--category", "-c", help="Template category"),
    public: bool = typer.Option(False, "--public/--private", help="Make template public (admin only) or private to org"),
):
    """Create a template from the current project files."""
    # Only admins can create public templates
    user = get_current_user()
    if not user:
        console.print(f"[{ERROR_STYLE}]Unable to fetch user session. Please sign in with `vr signin`.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    if public and not user.is_admin:
        print_warning("You cannot create public templates. Creating as private.")
        public = False

    project_dir = Path.cwd()
    vrignore_patterns = load_vrignore_patterns(project_dir)

    # Collect all project files (excluding those matched by .vrignore)
    files = {}
    for file_path in sorted(project_dir.rglob("*")):
        if file_path.is_dir():
            continue
        rel_path = str(file_path.relative_to(project_dir))
        if matches_vrignore(rel_path, vrignore_patterns):
            continue
        try:
            with open(file_path) as f:
                files[rel_path] = f.read()
        except UnicodeDecodeError:
            # Skip binary files
            continue

    if not files:
        console.print(f"[{ERROR_STYLE}]No files found in current directory.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    repo = AgentTemplateRepository()
    template_entity = AgentTemplate(
        name=name,
        description=description,
        category=category,
        is_public=public,
        files=files,
    )
    created = repo.create(template_entity)

    if not created:
        console.print(f"[{ERROR_STYLE}]Failed to create template.[/{ERROR_STYLE}]")
        raise typer.Exit(1)

    console.print(f"[{SUCCESS_STYLE}]Template '{name}' created successfully.[/{SUCCESS_STYLE}]")
    console.print(f"  [{INFO_STYLE}]ID:[/{INFO_STYLE}] [{TITLE_STYLE}]{created.id}[/{TITLE_STYLE}]")
    console.print(f"  [{INFO_STYLE}]Files:[/{INFO_STYLE}]")
    for f in files:
        console.print(f"    [{TITLE_STYLE}]{f}[/{TITLE_STYLE}]")


if __name__ == "__main__":
    app()
