"""Agent create command implementation."""

import json
from pathlib import Path
from typing import Annotated, Any, Optional, cast
from uuid import UUID

import typer
from pydantic import ValidationError

from cli.commands.agents.create.presenter import present_agent_create_success
from cli.commands.flag_utils import (
    parse_tags_required,
    require_exclusive,
    validate_provider,
    validate_scope,
)
from cli.infrastructure.command import async_command, authenticated_client
from cli.infrastructure.file_content import FileContentError, resolve_content
from alloy_runtime_types.dtos.agents import (
    AgentModelConfig,
    AgentToolConfig,
    CreateAgentRequest,
)
from alloy_runtime_types.dtos.generation import GenerationParameters
from alloy_runtime_types.enums.ownership_scope import OwnershipScope

REQUEST_HEADERS = Annotated[
    Optional[list[str]],
    typer.Option(
        "-H",
        "--header",
        help="Default request header (key:value). Repeatable. E.g., -H 'anthropic-beta:context-1m-2025-08-07'",
    ),
]


def agents_create_command(
    name: str = typer.Option(
        "...",
        "-n",
        "--name",
        help="Agent name",
    ),
    provider: str = typer.Option(
        "...",
        "-p",
        "--provider",
        help="Provider (openai, anthropic, etc.)",
    ),
    model: str = typer.Option(
        "...",
        "-M",
        "--model",
        help="Model name (gpt-4o, claude-3-sonnet, etc.)",
    ),
    tags: str = typer.Option(
        "...",
        "-t",
        "--tags",
        help="Comma-separated tags",
    ),
    description: Optional[str] = typer.Option(
        None,
        "-d",
        "--description",
        help="Agent description",
    ),
    scope: Optional[str] = typer.Option(
        None,
        "--scope",
        help="Scope: user, organization, global",
    ),
    temperature: Optional[float] = typer.Option(
        None,
        "-T",
        "--temperature",
        help="Temperature (0.0-2.0)",
    ),
    max_tokens: Optional[int] = typer.Option(
        None,
        "--max-tokens",
        help="Max tokens to generate",
    ),
    system_template: Optional[str] = typer.Option(
        None,
        "--system-template",
        help="System template name or UUID",
    ),
    tools: Optional[str] = typer.Option(
        None,
        "--tools",
        help="JSON array of tool configs (or @file.json)",
    ),
    tools_file: Optional[Path] = typer.Option(
        None,
        "--tools-file",
        help="Path to JSON file with tool configs",
        exists=True,
        readable=True,
    ),
    base_agent: Optional[str] = typer.Option(
        None,
        "--base-agent",
        help="Base agent name or UUID to inherit configuration from",
    ),
    system_version: Optional[UUID] = typer.Option(
        None,
        "--system-version",
        help="Specific system instruction version UUID",
    ),
    input_schema: Optional[str] = typer.Option(
        None,
        "--input-schema",
        help="Input schema name or UUID",
    ),
    output_schema: Optional[str] = typer.Option(
        None,
        "--output-schema",
        help="Output schema name or UUID (mutually exclusive with --output-schema-def)",
    ),
    output_schema_def: Optional[str] = typer.Option(
        None,
        "--output-schema-def",
        help="Output schema definition JSON (or @file.json). Mutually exclusive with --output-schema.",
    ),
    headers: REQUEST_HEADERS = None,
    top_p: Optional[float] = typer.Option(
        None,
        "--top-p",
        help="Nucleus sampling parameter (0.0-1.0)",
    ),
    top_k: Optional[int] = typer.Option(
        None,
        "--top-k",
        help="Top-k sampling parameter",
    ),
    frequency_penalty: Optional[float] = typer.Option(
        None,
        "--frequency-penalty",
        help="Frequency penalty (-2.0 to 2.0)",
    ),
    presence_penalty: Optional[float] = typer.Option(
        None,
        "--presence-penalty",
        help="Presence penalty (-2.0 to 2.0)",
    ),
    seed: Optional[int] = typer.Option(
        None,
        "--seed",
        help="Random seed for reproducible outputs",
    ),
    stop: Optional[str] = typer.Option(
        None,
        "--stop",
        help="Stop sequences (string or comma-separated for multiple)",
    ),
    logprobs: Optional[bool] = typer.Option(
        None,
        "--logprobs/--no-logprobs",
        help="Include log probabilities in response",
    ),
    top_logprobs: Optional[int] = typer.Option(
        None,
        "--top-logprobs",
        help="Number of top log probabilities to return (1-20)",
    ),
    user: Optional[str] = typer.Option(
        None,
        "--user",
        help="End-user ID for tracking and abuse monitoring",
    ),
) -> None:
    """Create a new AI agent.

    Examples:
        alloy agents create -n "My Agent" -p openai -M gpt-4o -t "agent.llm"
        alloy agents create -n "Writer" -p anthropic -M claude-3-sonnet -t "agent.creative" -d "Creative writer" -T 0.9
        alloy agents create -n "Org Agent" -p openai -M gpt-4o -t "agent.llm,team.backend" --scope organization
        alloy agents create -n "With Tools" -p openai -M gpt-4o -t "agent" --tools '[{"tool_id": "web_search"}]'
        alloy agents create -n "With Tools" -p openai -M gpt-4o -t "agent" --tools-file tools.json
        alloy agents create -n "With Headers" -p openai -M gpt-4o -t "agent" -H 'anthropic-beta:context-1m-2025-08-07'
    """
    require_exclusive(("--tools", tools), ("--tools-file", tools_file))
    require_exclusive(
        ("--output-schema", output_schema), ("--output-schema-def", output_schema_def)
    )

    _execute_create_from_flags(
        name=name,
        provider=provider,
        model=model,
        tags=tags,
        description=description,
        scope=scope,
        temperature=temperature,
        max_tokens=max_tokens,
        system_template=system_template,
        tools=tools,
        tools_file=tools_file,
        base_agent=base_agent,
        system_version=system_version,
        input_schema=input_schema,
        output_schema=output_schema,
        output_schema_def=output_schema_def,
        headers=headers,
        top_p=top_p,
        top_k=top_k,
        frequency_penalty=frequency_penalty,
        presence_penalty=presence_penalty,
        seed=seed,
        stop=stop,
        logprobs=logprobs,
        top_logprobs=top_logprobs,
        user=user,
    )


@async_command
async def _execute_create(request: CreateAgentRequest) -> None:
    """Execute the agent creation with the given request."""
    async with authenticated_client() as (_config, client):
        response = await client.create_agent(request)

    present_agent_create_success(response)


def parse_tools(
    tools: Optional[str],
    tools_file: Optional[Path],
) -> list[AgentToolConfig] | None:
    """Parse tool configurations from CLI input.

    Args:
        tools: JSON string (or @file.json reference) containing tool configs
        tools_file: Path to JSON file containing tool configs

    Returns:
        List of AgentToolConfig objects, or None if no tools provided

    Raises:
        typer.BadParameter: If JSON is invalid or doesn't match schema
    """
    if tools is None and tools_file is None:
        return None

    if tools_file is not None:
        try:
            json_content = tools_file.read_text()
        except IOError as e:
            raise typer.BadParameter(f"Failed to read tools file: {e}")
    else:
        try:
            json_content = resolve_content(tools)
        except FileContentError as e:
            raise typer.BadParameter(str(e))

    if not json_content:
        return None

    try:
        tools_data_raw: object = json.loads(json_content)
    except json.JSONDecodeError as e:
        raise typer.BadParameter(f"Invalid JSON in tools: {e}")

    if not isinstance(tools_data_raw, list):
        raise typer.BadParameter("Tools must be a JSON array")

    tools_data: list[object] = cast(list[object], tools_data_raw)

    if not tools_data:
        return None

    tool_configs: list[AgentToolConfig] = []
    for i, tool_data in enumerate(tools_data):
        if not isinstance(tool_data, dict):
            raise typer.BadParameter(f"Tool at index {i} must be an object")
        try:
            tool_configs.append(AgentToolConfig(**cast(dict[str, Any], tool_data)))
        except ValidationError as e:
            errors = e.errors()
            if errors:
                first_error = errors[0]
                field = ".".join(str(loc) for loc in first_error["loc"])
                msg = first_error["msg"]
                raise typer.BadParameter(f"Tool at index {i}: {field} - {msg}")
            raise typer.BadParameter(f"Tool at index {i}: {e}")

    return tool_configs


_parse_tools = parse_tools


def parse_output_schema_def(
    output_schema_def: Optional[str],
) -> dict[str, Any] | None:
    """Parse output schema definition from CLI input.

    Args:
        output_schema_def: JSON string (or @file.json reference) containing schema

    Returns:
        Parsed schema dict, or None if not provided

    Raises:
        typer.BadParameter: If JSON is invalid
    """
    if output_schema_def is None:
        return None

    try:
        json_content = resolve_content(output_schema_def)
    except FileContentError as e:
        raise typer.BadParameter(str(e))

    if not json_content:
        return None

    try:
        schema_data: object = json.loads(json_content)
    except json.JSONDecodeError as e:
        raise typer.BadParameter(f"Invalid JSON in output-schema-def: {e}")

    if not isinstance(schema_data, dict):
        raise typer.BadParameter("Output schema definition must be a JSON object")

    return cast(dict[str, Any], schema_data)


def parse_headers(
    headers: Optional[list[str]],
) -> dict[str, str] | None:
    """Parse headers from CLI input.

    Args:
        headers: List of key:value strings

    Returns:
        Parsed headers dict, or None if not provided

    Raises:
        typer.BadParameter: If header format is invalid
    """
    if headers is None:
        return None

    parsed: dict[str, str] = {}
    for h in headers:
        if ":" not in h:
            raise typer.BadParameter(
                f"Invalid header format '{h}'. Use 'key:value' format."
            )
        key, value = h.split(":", 1)
        parsed[key.strip()] = value.strip()

    return parsed if parsed else None


def parse_stop_sequences(stop: Optional[str]) -> str | list[str] | None:
    """Parse stop sequences from CLI input.

    Args:
        stop: Stop sequence string (comma-separated for multiple)

    Returns:
        Single string or list of strings, or None if not provided
    """
    if stop is None:
        return None

    if "," in stop:
        sequences = [s.strip() for s in stop.split(",") if s.strip()]
        return sequences if sequences else None

    return stop


def _execute_create_from_flags(
    name: str,
    provider: str,
    model: str,
    tags: str,
    description: Optional[str],
    scope: Optional[str],
    temperature: Optional[float],
    max_tokens: Optional[int],
    system_template: Optional[str],
    tools: Optional[str],
    tools_file: Optional[Path],
    base_agent: Optional[str],
    system_version: Optional[UUID],
    input_schema: Optional[str],
    output_schema: Optional[str],
    output_schema_def: Optional[str],
    headers: Optional[list[str]],
    top_p: Optional[float],
    top_k: Optional[int],
    frequency_penalty: Optional[float],
    presence_penalty: Optional[float],
    seed: Optional[int],
    stop: Optional[str],
    logprobs: Optional[bool],
    top_logprobs: Optional[int],
    user: Optional[str],
) -> None:
    """Build and execute agent creation from command flags."""
    provider_enum = validate_provider(provider)

    tag_list = parse_tags_required(tags)

    model_config = AgentModelConfig(
        provider_key=provider_enum,
        provider_model_name=model,
        preference_order=0,
    )

    ownership_scope = OwnershipScope.ORGANIZATION
    if scope:
        ownership_scope = validate_scope(scope)

    gen_params = None
    if any(
        [
            temperature is not None,
            max_tokens is not None,
            top_p is not None,
            top_k is not None,
            frequency_penalty is not None,
            presence_penalty is not None,
            seed is not None,
            stop is not None,
            logprobs is not None,
            top_logprobs is not None,
            user is not None,
        ]
    ):
        gen_params = GenerationParameters(
            temperature=temperature,
            max_tokens=max_tokens,
            top_p=top_p,
            top_k=top_k,
            frequency_penalty=frequency_penalty,
            presence_penalty=presence_penalty,
            seed=seed,
            stop=parse_stop_sequences(stop),
            logprobs=logprobs,
            top_logprobs=top_logprobs,
            user=user,
        )

    tool_configs = parse_tools(tools, tools_file)

    parsed_output_schema = parse_output_schema_def(output_schema_def)

    parsed_headers = parse_headers(headers)

    request = CreateAgentRequest(
        name=name,
        models=[model_config],
        tags=tag_list,
        scope=ownership_scope,
        description=description,
        system_instruction_template=system_template,
        generation_params=gen_params,
        tools=tool_configs,
        base_agent_id=base_agent,
        system_instruction_version_id=system_version,
        input_schema_id=input_schema,
        output_schema_id=output_schema,
        output_schema=parsed_output_schema,
        default_request_headers=parsed_headers,
    )

    _execute_create(request)
