from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from rich.markup import escape
from textual.binding import Binding
from textual.containers import Vertical, VerticalScroll
from textual.widgets import ProgressBar, Static

from ferp.core.script_runner import ScriptResult
from ferp.core.state import AppState, AppStateStore, ScriptRunState
from ferp.widgets.panels import ContentPanel


class OutputPanelContainer(VerticalScroll):
    BINDINGS = [
        Binding("c", "clear_output", "Clear output", show=True),
    ]

    def action_clear_output(self) -> None:
        try:
            panel = self.query_one(ScriptOutputPanel)
        except Exception:
            return
        panel.action_clear_output()


class ScriptOutputPanel(ContentPanel):
    """Specialized panel responsible for rendering script status and errors."""

    _MAX_VALUE_CHARS = 2500
    _TRUNCATION_SUFFIX = "\n... (truncated)"

    def __init__(
        self,
        *,
        title: str = "Script Output",
        panel_id: str = "output_panel",
        initial_message: str = "No script output.",
        state_store: AppStateStore,
    ) -> None:
        super().__init__(initial_message, id=panel_id, title=title)
        self._state_store = state_store
        self._state_subscription = self._handle_state_update
        self._last_script_run: ScriptRunState | None = None
        self._initial_message = initial_message
        self._showing_initial = True
        self._progress_header: Static | None = None
        self._progress_message: Static | None = None
        self._progress_bar: ProgressBar | None = None
        self._progress_status: Static | None = None

    def on_mount(self) -> None:
        super().on_mount()
        self._state_store.subscribe(self._state_subscription)
        self._set_border_title("Status")

    def on_unmount(self) -> None:
        self._state_store.unsubscribe(self._state_subscription)

    def show_error(self, error: BaseException) -> None:
        self.remove_children()
        self.update_content("[bold $error]Error:[/bold $error]\n" + escape(str(error)))
        self._showing_initial = False

    def show_result(
        self,
        script_name: str,
        target: Path,
        result: ScriptResult,
        transcript_path: Path | None = None,
    ) -> None:
        status = result.status.value.replace("_", " ").title()
        lines: list[str] = [
            f"[bold $text-primary]Script:[/bold $text-primary] {escape(script_name)}",
            f"[bold $text-primary]Target:[/bold $text-primary] {escape(str(target.name))}",
            f"[bold $text-primary]Status:[/bold $text-primary] {status}",
        ]

        if result.exit_code is not None:
            lines.append(
                f"[bold $text-primary]Exit Code:[/bold $text-primary] {result.exit_code}"
            )

        if result.results:
            total = len(result.results)
            for index, payload in enumerate(result.results, start=1):
                header_text, header_style = self._result_header(payload, index, total)
                lines.append(
                    f"\n[bold {header_style}]{escape(header_text)}[/bold {header_style}]\n"
                )
                format_hint = payload.get("_format")
                if (
                    isinstance(format_hint, str)
                    and format_hint.strip().lower() == "json"
                ):
                    cleaned = {
                        key: value
                        for key, value in payload.items()
                        if not (isinstance(key, str) and key.startswith("_"))
                    }
                    lines.append(self._format_pair("json", cleaned))
                    continue
                for key, value in payload.items():
                    if isinstance(key, str) and key.startswith("_"):
                        continue
                    lines.append(self._format_pair(key, value))

        if result.error:
            lines.append("\n[bold $error]Error:[/bold $error]\n" + escape(result.error))

        if transcript_path:
            lines.append(
                f"\n[bold $secondary]Transcript:[/bold $secondary] [$text-secondary]{escape(str(transcript_path.name))}[/$text-secondary]"
            )

        self.remove_children()
        self.update_content("\n".join(lines))
        self._showing_initial = False

    def _handle_state_update(self, state: AppState) -> None:
        self._set_border_subtitle(state.status)
        script_run = state.script_run
        if self._last_script_run == script_run:
            return
        self._last_script_run = script_run
        self._render_script_state(script_run)

    def _render_script_state(self, script_run: ScriptRunState) -> None:
        phase = script_run.phase
        if phase == "idle":
            return
        if phase == "running":
            self._set_border_title("Process Output")
            self._render_progress(script_run)
            return
        if phase == "awaiting_input":
            return
        if phase == "result" and script_run.result is not None:
            self._set_border_title("Process Output")
            self._clear_progress()
            script_name = script_run.script_name or "Script"
            target = script_run.target_path or Path(".")
            self.show_result(
                script_name,
                target,
                script_run.result,
                script_run.transcript_path,
            )
            return
        if phase == "error" and script_run.error:
            self._set_border_title("Process Output")
            self._clear_progress()
            self.remove_children()
            self.update_content(
                "[bold $error]Error:[/bold $error]\n" + escape(script_run.error)
            )
            return

    def _set_border_title(self, title: str) -> None:
        try:
            container = self.app.query_one("#output_panel_container")
        except Exception:
            self.border_title = title
        else:
            container.border_title = title

    def _set_border_subtitle(self, subtitle: str) -> None:
        try:
            container = self.app.query_one("#output_panel_container")
        except Exception:
            self.border_subtitle = subtitle
        else:
            container.border_subtitle = subtitle

    def action_clear_output(self) -> None:
        self._clear_progress()
        self.remove_children()
        self.update_content(self._initial_message)
        self._set_border_title("Status")
        self._showing_initial = True

    def set_initial_message(self, message: str) -> None:
        self._initial_message = message
        if self._showing_initial:
            self.update_content(self._initial_message)

    def _render_progress(self, script_run: ScriptRunState) -> None:
        script_name = script_run.script_name or "Script"
        target = script_run.target_path
        target_label = escape(str(target)) if target else "Unknown"
        header_text = (
            f"[bold $primary]Script:[/bold $primary] {escape(script_name)}\n"
            f"[bold $primary]Target:[/bold $primary] {target_label}"
        )

        if self._progress_bar is None or self._progress_header is None:
            self.remove_children()
            self._progress_header = Static(header_text, id="progress_header")
            self._progress_message = Static("", id="progress_message")
            self._progress_bar = ProgressBar(
                total=None,
                show_eta=False,
                id="script_progress_bar",
                show_percentage=False,
            )
            self._progress_status = Static(
                "[dim]Working, please wait...[/dim]",
                id="progress_status",
            )
            self.mount(
                self._progress_header,
                Vertical(
                    self._progress_message,
                    self._progress_bar,
                    self._progress_status,
                    id="progress-container",
                ),
            )
        else:
            self._progress_header.update(header_text)

        if self._progress_message is not None:
            self._progress_message.update(escape(script_run.progress_message or ""))
        if self._progress_bar is not None:
            total = script_run.progress_total
            current = script_run.progress_current
            if total is not None and total >= 0 and current is not None:
                self._progress_bar.update(
                    total=total, progress=max(0.0, min(current, total))
                )
            else:
                self._progress_bar.update(total=None)
        if self._progress_status is not None and script_run.progress_line:
            self._progress_status.update(script_run.progress_line)

    def _clear_progress(self) -> None:
        self._progress_header = None
        self._progress_message = None
        self._progress_bar = None
        self._progress_status = None

    def _format_pair(self, key: Any, value: Any) -> str:
        label = f"[bold $text-primary]{escape(str(key))}:[/bold $text-primary]"
        body = escape(self._stringify_value(value))
        return f"{label} {body}"

    def _result_header(
        self,
        payload: dict[str, Any],
        index: int,
        total: int,
    ) -> tuple[str, str]:
        header_text = f"Result {index} of {total}"
        status = "success"
        custom_title = payload.get("_title")
        if isinstance(custom_title, str) and custom_title.strip():
            header_text = custom_title.strip()
        custom_status = payload.get("_status")
        if isinstance(custom_status, str):
            status = custom_status.strip().lower()
        status_to_style = {
            "success": "$success",
            "ok": "$success",
            "warn": "$warning",
            "warning": "$warning",
            "error": "$error",
            "fail": "$error",
            "failed": "$error",
        }
        return header_text, status_to_style.get(status, "$success")

    def _stringify_value(self, value: Any) -> str:
        if value is None:
            return "null"
        if isinstance(value, (str, int, float, bool)):
            return self._truncate_value(str(value))
        if isinstance(value, (dict, list)):
            try:
                return self._truncate_value(
                    json.dumps(value, indent=2, ensure_ascii=True)
                )
            except (TypeError, ValueError):
                return self._truncate_value(str(value))
        return self._truncate_value(str(value))

    def _truncate_value(self, value: str) -> str:
        if len(value) <= self._MAX_VALUE_CHARS:
            return value
        return value[: self._MAX_VALUE_CHARS] + self._TRUNCATION_SUFFIX
