"""alice dialog — Facilitate a conversation between two models."""

from __future__ import annotations

from typing import Any

import click
from botocore.exceptions import ClientError
from rich.markdown import Markdown
from rich.panel import Panel

from alice_cli import personality
from alice_cli.aws.bedrock_context import create_bedrock_context
from alice_cli.console import (
    err_console,
    print_banner,
    print_rule,
    spinner,
)
from alice_cli.constants import DEFAULT_MAX_TOKENS
from alice_cli.errors import AliceCLIError
from alice_cli.models import DEFAULT_MODEL_ALIAS, SaveFormat, model_help_text, resolve_model_id
from alice_cli.save import save_dialog_session


def _default_dialog_system(my_alias: str, other_alias: str, user_system: str | None) -> str:
    """Build a system prompt that frames the model as a dialog participant."""
    base = (
        f"You are {my_alias} in a dialog with {other_alias}. "
        "A human has posed a topic for discussion. "
        "Build on what has been said so far — add new ideas, "
        "challenge assumptions, or deepen the analysis. "
        "Do not simply repeat or rephrase the previous message. "
        "Keep your response focused and concise."
    )
    if user_system:
        return f"{user_system}\n\n{base}"
    return base


def _build_messages_for(
    transcript: list[tuple[str, str]],
    my_label: str,
    opening_prompt: str,
) -> list[dict[str, Any]]:
    """Build a Converse-API message list from the shared transcript.

    Each transcript entry is ``(speaker_label, text)``.  Entries where
    ``speaker_label == my_label`` become ``"assistant"`` messages; all
    others become ``"user"`` messages.  The opening prompt is always
    prepended as the first ``"user"`` message so the model has the
    original context.

    Adjacent messages with the same role are merged (the Converse API
    requires strict user/assistant alternation).
    """
    raw: list[dict[str, Any]] = [{"role": "user", "content": [{"text": opening_prompt}]}]
    for speaker, text in transcript:
        role = "assistant" if speaker == my_label else "user"
        raw.append({"role": role, "content": [{"text": text}]})

    # Merge consecutive same-role messages to satisfy the alternation rule.
    merged: list[dict[str, Any]] = []
    for msg in raw:
        if merged and merged[-1]["role"] == msg["role"]:
            merged[-1]["content"].extend(msg["content"])
        else:
            merged.append(msg)

    # The list must start with "user" and end with "user" (the model replies).
    # If the last message is "assistant" (shouldn't happen in normal flow),
    # append a continuation nudge.
    if merged and merged[-1]["role"] == "assistant":
        merged.append({"role": "user", "content": [{"text": "Please continue."}]})

    return merged


def _print_turn(alias: str, color: str, text: str) -> None:
    """Render one turn of the dialog as a Rich panel."""
    err_console.print(
        Panel(
            Markdown(text) if text.strip() else "[dim](empty response)[/dim]",
            title=f"[{color} bold]{alias}[/{color} bold]",
            border_style=color,
            padding=(1, 2),
        )
    )


@click.command("dialog")
@click.option("--model-a", default=DEFAULT_MODEL_ALIAS, help=model_help_text())
@click.option("--model-b", default="haiku", help=model_help_text())
@click.option("--rounds", default=3, show_default=True, help="Number of back-and-forth exchanges")
@click.option(
    "--system-a",
    default=None,
    help="System prompt for model A (optional)",
)
@click.option(
    "--system-b",
    default=None,
    help="System prompt for model B (optional)",
)
@click.option(
    "-o",
    "--output",
    "save_path",
    default=None,
    type=click.Path(),
    help="Write dialog transcript to a local file",
)
@click.option(
    "-f",
    "--format",
    "fmt",
    default="markdown",
    type=click.Choice(["markdown", "json", "text"], case_sensitive=False),
    help="Output file format (default: markdown)",
)
@click.argument("prompt", nargs=-1, type=click.UNPROCESSED, required=True)
@click.pass_context
def dialog_cmd(
    ctx: click.Context,
    model_a: str,
    model_b: str,
    rounds: int,
    system_a: str | None,
    system_b: str | None,
    save_path: str | None,
    fmt: str,
    prompt: tuple[str, ...],
) -> None:
    """Watch two models converse with each other.

    Sends an opening prompt to model A, then alternates between models
    for the specified number of rounds.

    \b
    Examples:
        alice dialog "What is the meaning of life?"
        alice dialog --model-a sonnet --model-b opus --rounds 5 "Debate free will"
        alice dialog --system-a "You are a poet" --system-b "You are a critic" "Write a haiku"
    """
    config = ctx.obj["config"]
    prompt_text = " ".join(prompt).strip()
    if not prompt_text:
        raise AliceCLIError("Please provide an opening prompt for the dialog.")

    print_banner(personality.BANNER, config.quiet)
    err_console.print(
        f"  [bold]Dialog[/bold]: [cyan]{model_a}[/cyan] ↔ [magenta]{model_b}[/magenta]"
        f"  •  {rounds} round{'s' if rounds != 1 else ''}\n"
    )

    # --- Authenticate and build Bedrock client ---
    context = create_bedrock_context(config)
    client = context.client
    resolved_a = resolve_model_id(model_a, context.inference_profile_arns)
    resolved_b = resolve_model_id(model_b, context.inference_profile_arns)

    # --- Conversation loop ---
    # A single shared transcript records every turn as (speaker_label, text).
    # Before each model's turn we rebuild its message list from the transcript,
    # mapping its own past turns to "assistant" and the other model's turns to
    # "user".  This gives both models full context of the discussion.
    transcript: list[tuple[str, str]] = []

    sys_a = [{"text": _default_dialog_system(model_a, model_b, system_a)}]
    sys_b = [{"text": _default_dialog_system(model_b, model_a, system_b)}]

    for round_num in range(1, rounds + 1):
        print_rule(config.quiet)
        if not config.quiet:
            err_console.print(f"  [dim]── Round {round_num}/{rounds} ──[/dim]\n")

        # Model A's turn
        messages_a = _build_messages_for(transcript, model_a, prompt_text)
        with spinner(f"[cyan]{model_a}[/cyan] is thinking…", config.quiet):
            try:
                resp_a = client.converse(
                    modelId=resolved_a,
                    messages=messages_a,
                    system=sys_a,
                    inferenceConfig={"maxTokens": DEFAULT_MAX_TOKENS},
                )
            except ClientError as exc:
                raise AliceCLIError(f"Bedrock API error ({model_a}): {exc}") from exc

        text_a = "\n".join(
            b["text"]
            for b in resp_a.get("output", {}).get("message", {}).get("content", [])
            if "text" in b
        )
        transcript.append((model_a, text_a))
        _print_turn(model_a, "cyan", text_a)

        # Model B's turn
        messages_b = _build_messages_for(transcript, model_b, prompt_text)
        with spinner(f"[magenta]{model_b}[/magenta] is thinking…", config.quiet):
            try:
                resp_b = client.converse(
                    modelId=resolved_b,
                    messages=messages_b,
                    system=sys_b,
                    inferenceConfig={"maxTokens": DEFAULT_MAX_TOKENS},
                )
            except ClientError as exc:
                raise AliceCLIError(f"Bedrock API error ({model_b}): {exc}") from exc

        text_b = "\n".join(
            b["text"]
            for b in resp_b.get("output", {}).get("message", {}).get("content", [])
            if "text" in b
        )
        transcript.append((model_b, text_b))
        _print_turn(model_b, "magenta", text_b)

    print_rule(config.quiet)
    if not config.quiet:
        err_console.print(f"\n  [green]✓[/green] Dialog complete — {rounds} rounds exchanged.")

    if save_path:
        save_dialog_session(
            path=save_path,
            prompt=prompt_text,
            model_a=model_a,
            model_b=model_b,
            rounds=rounds,
            transcript=transcript,
            quiet=config.quiet,
            fmt=SaveFormat(fmt.lower()),
        )
