# -*- coding: utf-8 -*-
"""Streaming output and conversation management functions."""
# pylint: disable=too-many-branches,too-many-statements

from __future__ import annotations

import asyncio
import sys

from rich.console import Console
from rich.syntax import Syntax
from rich.text import Text

from dashscope.acli.agent import Agent
from dashscope.acli.config import Config
from dashscope.acli.deliverable import (
    collect_deliverables,
    surface_deliverables,
)
from dashscope.acli.utils import (
    AsyncSpinner,
    UserAbortedTurn,
    message_text_for_compress,
)

console = Console()


def _render_tool_trail(line: str) -> None:
    """Render a `[tool_name] → result` trail line. For write_file (and any
    other diff-bearing tools), split out the unified diff section and
    render it with Syntax("diff") so additions/removals are color-coded.

    Trail format: `[write_file] → Wrote file: path (N chars)` then
    `\\n--- diff ---\\n<diff>`.
    """
    if "[write_file]" in line and "--- diff ---" in line:
        head, _, diff_body = line.partition("--- diff ---")
        console.print(Text(head.rstrip(), style="dim cyan"))
        diff_body = diff_body.strip("\n")
        if diff_body:
            console.print(
                Syntax(
                    diff_body,
                    "diff",
                    theme="ansi_dark",
                    background_color="default",
                ),
            )
        return
    console.print(Text(line, style="dim cyan"))


async def _do_compress(agent: Agent) -> None:
    """Compress the current conversation into a short summary."""
    old_count = len(agent.messages)
    old_chars = sum(len(str(m.get("content", ""))) for m in agent.messages)
    console.print("[dim]Compressing conversation context...[/dim]")
    conversation_dump = ""
    for m in agent.messages:
        conversation_dump += (
            f"[{m.get('role', '?')}]: {message_text_for_compress(m)}\n"
        )
    compress_messages = [
        {
            "role": "system",
            "content": (
                "Compress the following conversation into a brief "
                "summary, keeping key decisions, code changes, file "
                "paths, and user preferences. Output the summary "
                "directly, without explanation."
            ),
        },
        {"role": "user", "content": conversation_dump},
    ]
    try:
        resp = await agent.provider.chat(compress_messages, tools=[])
        summary = resp.content if hasattr(resp, "content") else str(resp)
        agent.messages = [
            {
                "role": "user",
                "content": (
                    "Below is a summary of the earlier conversation "
                    "(generated by compression, for background only; "
                    "model names, providers, and other config it "
                    "mentions may be outdated — the current config "
                    "is governed by the system prompt):\n"
                    f"{summary}"
                ),
            },
            {
                "role": "assistant",
                "content": (
                    "Got it — I have the earlier conversation "
                    "context. Please continue."
                ),
            },
        ]
        agent.save_session()
        new_chars = sum(len(str(m.get("content", ""))) for m in agent.messages)
        console.print(
            f"[green]Compressed: {old_count} messages → 2, "
            f"{old_chars} chars → {new_chars} chars[/green]",
        )
    except Exception as e:
        console.print(f"[red]Compression failed: {e}[/red]")


async def _do_summarize(
    agent: Agent,
    messages_to_summarize: list | None = None,
    silent: bool = False,
) -> dict | None:
    """Summarize a task and record to experience tracker.

    Args:
        agent: The agent instance.
        messages_to_summarize: Messages to summarize. If None, use
            agent.messages.
        silent: If True, suppress console output (for auto-trigger).

    Returns:
        Parsed summary dict or None on failure.
    """
    import json as _json

    tracker = getattr(agent, "experience_tracker", None)
    if not tracker:
        if not silent:
            console.print(
                "[yellow]Experience tracker not initialized[/yellow]",
            )
        return None

    msgs = (
        messages_to_summarize
        if messages_to_summarize is not None
        else agent.messages
    )
    if len(msgs) < 2:
        if not silent:
            console.print(
                "[yellow]Not enough conversation to summarize[/yellow]",
            )
        return None

    if not silent:
        console.print("[dim]Summarizing current task...[/dim]")

    conversation_dump = ""
    tools_used: list[str] = []
    for m in msgs:
        role = m.get("role", "?")
        content = m.get("content", "")
        if isinstance(content, list):
            content = " ".join(
                c.get("text", "") for c in content if isinstance(c, dict)
            )
        conversation_dump += f"[{role}]: {content}\n"
        if role == "assistant" and "tool_calls" in m:
            for tc in m["tool_calls"]:
                if "function" in tc:
                    name = tc["function"]["name"]
                    if name not in tools_used:
                        tools_used.append(name)

    summarize_messages = [
        {
            "role": "system",
            "content": (
                "You are a task summarizer. Based on the conversation, "
                "output a summary in this JSON format (output JSON "
                "only, no explanation):\n"
                '{"task": "one-line description of the completed task", '
                '"steps": ["key step 1", "key step 2"], '
                '"lesson": "lessons learned (if any)", '
                '"outcome": "success or partial"}'
            ),
        },
        {"role": "user", "content": conversation_dump},
    ]

    try:
        resp = await agent.provider.chat(summarize_messages, tools=[])
        raw = resp.content if hasattr(resp, "content") else str(resp)

        parsed = None
        try:
            start = raw.find("{")
            end = raw.rfind("}") + 1
            if 0 <= start < end:
                parsed = _json.loads(raw[start:end])
        except Exception:
            parsed = None

        task = (parsed or {}).get("task", "") or raw.split("\n", 1)[0][:100]
        lesson = (parsed or {}).get("lesson", "")
        steps = (parsed or {}).get("steps", [])
        outcome = (parsed or {}).get("outcome", "success")
        if outcome not in ("success", "partial", "failure"):
            outcome = "success"

        if lesson and steps:
            lesson = f"Steps: {'; '.join(steps)}\nLesson: {lesson}"
        elif steps:
            lesson = f"Steps: {'; '.join(steps)}"

        tracker.record_experience(
            task_summary=task[:200],
            tools_used=tools_used,
            outcome=outcome,
            lesson=lesson,
        )

        if not silent:
            console.print(
                "\n[bold green]✓ Task summary recorded[/bold green]",
            )
            console.print(f"  [bold]Task:[/bold] {task}")
            if steps:
                console.print("  [bold]Steps:[/bold]")
                for s in steps:
                    console.print(f"    - {s}")
            if lesson and not steps:
                console.print(f"  [bold]Lesson:[/bold] {lesson}")
            console.print()

        return parsed or {
            "task": task,
            "steps": steps,
            "lesson": lesson,
            "outcome": outcome,
        }
    except Exception as e:
        if not silent:
            console.print(f"[red]Summarization failed: {e}[/red]")
        return None


async def _stream_response(agent: Agent, config: Config, user_input):
    """Stream agent response with real-time output and tool call indicators."""
    buffer = ""
    first_chunk = True
    spinner_active = True
    spinner = AsyncSpinner("Thinking...")
    await spinner.__aenter__()

    # Install SIGINT handler so Ctrl+C aborts the turn instead of
    # killing the process.
    import signal

    loop = asyncio.get_running_loop()
    aborted = False

    def _sigint_handler():
        nonlocal aborted
        aborted = True

    try:
        loop.add_signal_handler(signal.SIGINT, _sigint_handler)
    except NotImplementedError:
        pass

    # Initialize streaming TTS feeder if enabled
    tts_feeder = None
    if config.tts_enabled:
        try:
            from dashscope.acli.ui.tts import StreamingTTSFeeder, is_available

            ok, _err = is_available()
            if ok:
                tts_feeder = StreamingTTSFeeder(
                    api_key=config.tongyi_api_key,
                    model=config.tts_model,
                    voice=config.tts_voice,
                    speech_rate=config.tts_speed,
                )
                tts_feeder.start()
        except Exception:
            tts_feeder = None

    try:
        async for chunk in agent.run_stream(user_input):
            if aborted:
                console.print("\n[yellow]Interrupted[/yellow]")
                break

            # Stop spinner on first chunk OR empty chunk (tool execution
            # signal)
            if chunk == "" and spinner_active:
                await spinner.__aexit__(None, None, None)
                spinner_active = False
            if first_chunk:
                if spinner_active:
                    await spinner.__aexit__(None, None, None)
                    spinner_active = False
                first_chunk = False

            if chunk.startswith("\n[") and "] →" in chunk:
                if buffer:
                    sys.stdout.write("\n")
                    sys.stdout.flush()
                    buffer = ""
                _render_tool_trail(chunk.strip())
            else:
                buffer += chunk
                sys.stdout.write(chunk)
                sys.stdout.flush()
                # Feed chunk to streaming TTS
                if tts_feeder:
                    tts_feeder.feed(chunk)
    except UserAbortedTurn:
        console.print("\n[dim]Turn aborted[/dim]")
    except Exception as e:
        console.print(f"\n[red]Error: {e}[/red]")
    finally:
        # Restore default SIGINT handler
        try:
            loop.remove_signal_handler(signal.SIGINT)
        except (NotImplementedError, RuntimeError):
            pass
        if spinner_active:
            await spinner.__aexit__(None, None, None)
        if buffer:
            sys.stdout.write("\n")
            sys.stdout.flush()
            agent.last_output = buffer
        # Stop the TTS feeder on ALL exit paths, otherwise its worker
        # thread leaks one per turn (empty-buffer turns never reached
        # finish()/cancel()).
        if tts_feeder:
            try:
                if buffer.strip():
                    # Flush remaining text to streaming TTS and wait
                    err = tts_feeder.finish()
                    if err:
                        console.print(f"[yellow]TTS: {err}[/yellow]")
                else:
                    tts_feeder.cancel()
            except Exception as e:
                console.print(f"[yellow]TTS error: {e}[/yellow]")
            finally:
                thread = getattr(tts_feeder, "_worker_thread", None)
                if thread is not None:
                    thread.join(timeout=5)

        # Surface any files produced by tool calls this turn.
        try:
            surface_deliverables(collect_deliverables(agent.messages))
        except Exception:
            pass
