from __future__ import annotations

import html
import json
import time
import uuid

import gradio as gr
from agents import Runner

from .db import ChatHistoryStore


TOOL_BLOCK_START = "<!--OPENKITE_TOOL_CALLS_START-->"
TOOL_BLOCK_END = "<!--OPENKITE_TOOL_CALLS_END-->"
FOOTER_BLOCK_START = "<!--OPENKITE_UI_FOOTER_START-->"
FOOTER_BLOCK_END = "<!--OPENKITE_UI_FOOTER_END-->"


def _strip_wrapped_block(text: str, start_marker: str, end_marker: str) -> str:
    while True:
        start = text.find(start_marker)
        if start == -1:
            break
        end = text.find(end_marker, start + len(start_marker))
        if end == -1:
            text = text[:start]
            break
        text = text[:start] + text[end + len(end_marker):]
    return text


def _strip_ui_metadata(text: str) -> str:
    cleaned = _strip_wrapped_block(text, TOOL_BLOCK_START, TOOL_BLOCK_END)
    cleaned = _strip_wrapped_block(cleaned, FOOTER_BLOCK_START, FOOTER_BLOCK_END)
    return cleaned.strip()


def _extract_text(content) -> str:
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        parts: list[str] = []
        for item in content:
            if isinstance(item, dict):
                parts.append(item.get("text") or item.get("value") or "")
            else:
                parts.append(str(item))
        return "".join(parts)
    return str(content)


def _build_input(history: list[dict], message: str) -> list[dict]:
    messages: list[dict] = []
    for turn in history:
        role = turn.get("role", "")
        if role not in {"user", "assistant"}:
            continue
        text = _extract_text(turn.get("content", ""))
        if role == "assistant":
            text = _strip_ui_metadata(text)
        if text:
            messages.append({"role": role, "content": text})
    messages.append({"role": "user", "content": message})
    return messages


def _render_tool_call_dropdown(name: str, args: str, status: str) -> str:
    icon = "⏳" if status == "running" else "✅"
    label = "In progress" if status == "running" else "Completed"
    safe_name = html.escape(name)
    safe_args = html.escape(args) if args else "No arguments"
    return f"<li>{icon} <strong>{safe_name}</strong> - {label}<br/><pre>{safe_args}</pre></li>"


def _render_tool_calls_container(tool_calls: list[tuple[str, str]], include_running: bool) -> str:
    if not tool_calls:
        return ""

    items: list[str] = []
    for index, (name, args) in enumerate(tool_calls):
        status = "done"
        if include_running and index == len(tool_calls) - 1:
            status = "running"
        items.append(_render_tool_call_dropdown(name, args, status))

    running_suffix = " (running)" if include_running else ""
    open_attr = " open" if include_running else ""
    return (
        f"<details{open_attr}>"
        f"<summary>🛠️ Tool calls used ({len(tool_calls)}){running_suffix}</summary>"
        f"<ul>{''.join(items)}</ul>"
        f"</details>"
    )


def _tool_status_block(tool_calls: list[tuple[str, str]]) -> str:
    body = _render_tool_calls_container(tool_calls, include_running=True)
    return f"Agent is working...\n\n{body}"


def _tool_calls_block(tool_calls: list[tuple[str, str]]) -> str:
    body = _render_tool_calls_container(tool_calls, include_running=False)
    if not body:
        return ""
    return f"{TOOL_BLOCK_START}\n{body}\n{TOOL_BLOCK_END}\n\n"


class ChatService:
    def __init__(self, agent, store: ChatHistoryStore) -> None:
        self._agent = agent
        self._store = store

    def history_choices(self) -> list[tuple[str, str]]:
        return [(summary.title, summary.session_id) for summary in self._store.list_conversations()]

    def load_history(self, session_id: str) -> list[dict]:
        return self._store.load_conversation(session_id)

    async def chat(self, message: str, history: list[dict], session_id: str = ""):
        start = time.perf_counter()
        history = history or []
        input_messages = _build_input(history, message)

        tool_calls: list[tuple[str, str]] = []
        yield ("Thinking...", session_id, gr.update())

        final_output = ""
        try:
            result = Runner.run_streamed(self._agent, input_messages)
            async for event in result.stream_events():
                    if event.type == "run_item_stream_event" and event.name == "tool_called":
                        raw = event.item.raw_item
                        tool_name = getattr(raw, "name", None) or "tool"
                        raw_args = getattr(raw, "arguments", "") or ""

                        try:
                            parsed_args = json.loads(raw_args)
                            args_str = ", ".join(f"{k}={v}" for k, v in parsed_args.items())
                        except Exception:
                            args_str = str(raw_args)

                        if len(args_str) > 80:
                            args_str = args_str[:77] + "..."

                        tool_calls.append((tool_name, args_str))
                        yield (_tool_status_block(tool_calls), session_id, gr.update())

            final_output = str(result.final_output or "")
        except Exception as exc:
            final_output = f"Request failed: {exc}"

        elapsed = time.perf_counter() - start
        footer = (
            f"\n\n{FOOTER_BLOCK_START}\n"
            f"---\n"
            f"Response time: {elapsed:.1f}s\n"
            f"{FOOTER_BLOCK_END}"
        )
        final_text = _tool_calls_block(tool_calls) + final_output + footer

        if not session_id:
            session_id = str(uuid.uuid4())

        full_history = input_messages + [{"role": "assistant", "content": final_text}]
        self._store.save_conversation(session_id, full_history)

        yield (final_text, session_id, gr.update(choices=self.history_choices(), value=session_id))
