"""iEvo TUI — interactive terminal dashboard."""

from __future__ import annotations

import shutil
from pathlib import Path

from rich.text import Text
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import VerticalScroll
from textual.reactive import reactive
from textual.widgets import (
    Footer,
    Header,
    RichLog,
    SelectionList,
    Static,
    TabbedContent,
    TabPane,
    Tree,
)
from textual.widgets.selection_list import Selection

from ievo.core.config import AGENTS_DIR, CLAUDE_AGENTS_DIR, find_project_root
from ievo.core.project import ProjectManifest, compare_agent_versions, compare_template_versions
from ievo.core.registry import Registry
from ievo.core.templates import refresh_templates
from ievo.marketplace import bundled_agent_path
from ievo.version import __version__

LOGO = """[#4ade80]
 ██╗ ███████╗██╗   ██╗ ██████╗
 ╚═╝ ██╔════╝██║   ██║██╔═══██╗
 ██╗ █████╗  ██║   ██║██║   ██║
 ██║ ██╔══╝  ╚██╗ ██╔╝██║   ██║
 ██║ ███████╗ ╚████╔╝ ╚██████╔╝
 ╚═╝ ╚══════╝  ╚═══╝   ╚═════╝[/#4ade80]
[#555555]Self-evolving AI agent framework[/#555555]"""


MODEL_COLORS = {
    "opus": "#f87171",
    "sonnet": "#a78bfa",
    "haiku": "#22d3ee",
}


class IevoApp(App):
    """iEvo interactive dashboard."""

    TITLE = "iEvo"
    SUB_TITLE = "Self-evolving AI agent framework"
    AUTO_FOCUS = "#agents-list"

    CSS = """
    Screen {
        background: #0c0c0c;
    }

    Header {
        background: #111111;
        color: #4ade80;
    }

    Footer {
        background: #111111;
    }

    FooterKey .footer-key--key {
        background: #222222;
        color: #4ade80;
    }

    FooterKey .footer-key--description {
        color: #b0b0b0;
    }

    #logo {
        height: auto;
        padding: 0 2;
    }

    #project-info {
        height: auto;
        padding: 0 2;
        color: #555555;
    }

    TabbedContent {
        height: 1fr;
    }

    ContentSwitcher {
        background: #0c0c0c;
    }

    TabPane {
        background: #0c0c0c;
    }

    Underline > .underline--bar {
        color: #4ade80;
    }

    Tab.-active {
        color: #4ade80;
    }

    Tab {
        color: #555555;
    }

    #agents-list {
        height: 1fr;
        background: #0c0c0c;
    }

    SelectionList > .selection-list--button {
        color: #4ade80;
    }

    #pipeline-view {
        padding: 1 2;
        background: #0c0c0c;
    }

    #evo-log {
        height: 1fr;
        padding: 0 1;
        background: #0c0c0c;
    }

    #project-tree {
        height: 1fr;
        background: #0c0c0c;
    }

    .section-title {
        padding: 0 1;
        color: #4ade80;
        text-style: bold;
    }

    #no-project {
        padding: 2 4;
        text-align: center;
    }
    """

    BINDINGS = [
        Binding("a", "tab('agents')", "Agents"),
        Binding("p", "tab('pipeline')", "Pipeline"),
        Binding("e", "tab('evolution')", "Evolution"),
        Binding("f", "tab('files')", "Files"),
        Binding("r", "refresh_data", "Refresh"),
        Binding("t", "refresh_templates", "Refresh templates"),
        Binding("u", "update_outdated", "Update outdated"),
        Binding("q", "quit", "Quit"),
    ]

    project_root: reactive[Path | None] = reactive(None)
    _manifest: ProjectManifest | None = None
    _agent_registry: Registry | None = None
    _outdated: dict[str, tuple[str, str]] = {}

    def on_mount(self) -> None:
        self.project_root = find_project_root()

    def compose(self) -> ComposeResult:
        yield Header()
        yield Static(LOGO, id="logo", markup=True)
        yield Static("", id="project-info")

        with TabbedContent(initial="agents"):
            with TabPane("Agents", id="agents"):
                yield SelectionList[str](id="agents-list")

            with TabPane("Pipeline", id="pipeline"):
                yield VerticalScroll(
                    Static("", id="pipeline-view", markup=True),
                )

            with TabPane("Evolution", id="evolution"):
                yield RichLog(id="evo-log", highlight=True, markup=True)

            with TabPane("Files", id="files"):
                yield Tree("project", id="project-tree")

        yield Footer()

    def watch_project_root(self, root: Path | None) -> None:
        """Called when project_root changes."""
        info = self.query_one("#project-info", Static)
        if root is None:
            info.update(
                "[#555555]No project found. Run:[/#555555] [bold #4ade80]ievo init[/bold #4ade80]"
            )
            self._manifest = None
            self._agent_registry = None
            return

        self._manifest = ProjectManifest.load(root)
        self._agent_registry = Registry.load_cached()
        info.update(
            f"[bold]{self._manifest.project}[/bold] "
            f"[dim]v{self._manifest.version}[/dim]  [dim]{root}[/dim]"
        )
        self._load_agents(root, self._manifest)
        if self._outdated:
            n = len(self._outdated)
            self.notify(
                f"{n} agent(s) outdated. Press u to update all.",
                severity="warning",
                timeout=8,
            )
        if compare_template_versions(self._manifest, __version__):
            self.notify(
                f"Templates may be outdated (project: {self._manifest.cli_version}, "
                f"cli: {__version__}). Press t to refresh.",
                severity="warning",
                timeout=8,
            )
        self._load_pipeline(self._manifest)
        self._load_evolution(root, self._manifest)
        self._load_file_tree(root)

    def _load_agents(self, root: Path, manifest: ProjectManifest) -> None:
        """Populate agent selection list from bundled registry."""
        selection_list = self.query_one("#agents-list", SelectionList)
        selection_list.clear_options()

        if self._agent_registry is None:
            self._outdated = {}
            return

        self._outdated = compare_agent_versions(manifest, self._agent_registry)

        entries = self._agent_registry.list_all()
        for entry in entries:
            is_installed = manifest.has_agent(entry.name)
            color = MODEL_COLORS.get(entry.model, "white")

            parts: list[tuple[str, str] | str] = [
                (entry.name, "bold"),
                "  ",
                (entry.model, color),
            ]
            if entry.mandatory:
                parts += ["  ", ("[mandatory]", "#fbbf24")]
            if entry.name in self._outdated:
                old, new = self._outdated[entry.name]
                parts += ["  ", (f"[outdated {old} \u2192 {new}]", "#fbbf24")]
            if entry.description:
                parts += ["  ", (f"\u2014 {entry.description}", "dim")]

            prompt = Text.assemble(*parts)
            selection_list.add_option(Selection(prompt, entry.name, initial_state=is_installed))

    def on_selection_list_selection_toggled(
        self, event: SelectionList.SelectionToggled[str]
    ) -> None:
        """Handle agent checkbox toggle — install or remove agent."""
        root = self.project_root
        if root is None or self._manifest is None or self._agent_registry is None:
            return

        agent_name: str = event.selection.value
        selection_list = self.query_one("#agents-list", SelectionList)
        is_now_selected = agent_name in selection_list.selected

        entry = self._agent_registry.get(agent_name)

        if is_now_selected:
            self._install_agent(root, agent_name, entry)
            # Refresh outdated state after install: agent is now up to date
            self._outdated.pop(agent_name, None)
        else:
            if entry and entry.mandatory:
                self.notify(
                    f"{agent_name} is a mandatory agent. Removing it may break the SDD pipeline.",
                    title="Warning",
                    severity="warning",
                    timeout=5,
                )
            self._uninstall_agent(root, agent_name)

        self._load_pipeline(self._manifest)

    def _install_agent(self, root: Path, name: str, entry=None) -> None:
        """Install agent .md and update manifest."""
        dest_dir = root / CLAUDE_AGENTS_DIR
        dest_dir.mkdir(parents=True, exist_ok=True)
        dest_file = dest_dir / f"{name}.md"

        bundled = bundled_agent_path(name)
        if bundled:
            shutil.copy2(bundled, dest_file)
        else:
            dest_file.write_text(
                f"---\nname: {name}\ndescription: Placeholder agent\n"
                f"model: sonnet\n---\n\n# {name}\n\n"
                "Placeholder. Install from marketplace for full agent.\n"
            )

        version = entry.version if entry else "0.1.0"
        self._manifest.add_agent(name, version)
        self._manifest.save(root)

        self.notify(f"Installed {name}", severity="information", timeout=3)

    def _uninstall_agent(self, root: Path, name: str) -> None:
        """Remove agent .md and update manifest."""
        claude_agent_md = root / CLAUDE_AGENTS_DIR / f"{name}.md"
        if claude_agent_md.exists():
            claude_agent_md.unlink()

        self._manifest.remove_agent(name)
        self._manifest.save(root)

        self.notify(f"Removed {name}", severity="information", timeout=3)

    def _load_pipeline(self, manifest: ProjectManifest) -> None:
        view = self.query_one("#pipeline-view", Static)
        agents = list(manifest.agents.keys())

        if not agents:
            view.update("[dim]No agents installed.[/dim]")
            return

        pipeline = []
        pipeline.append(
            "[bold #e0e0e0]SDD Pipeline[/bold #e0e0e0]  "
            "[#555555]— two human gates, everything between is automated[/#555555]\n"
        )

        # Build pipeline diagram
        boxes = []
        for a in agents:
            model_color = "#a78bfa"  # default sonnet purple
            if self._agent_registry:
                entry = self._agent_registry.get(a)
                if entry:
                    model_color = MODEL_COLORS.get(entry.model, "#a78bfa")
            boxes.append(
                f"[#222222]┌{'─' * (len(a) + 2)}┐[/#222222]\n"
                f"[#222222]│[/#222222] [{model_color}]{a}[/{model_color}] [#222222]│[/#222222]\n"
                f"[#222222]└{'─' * (len(a) + 2)}┘[/#222222]"
            )

        # Show as flow
        pipeline.append("  [bold #4ade80]YOU[/bold #4ade80]")
        pipeline.append("   [#555555]│[/#555555]")
        for i, box in enumerate(boxes):
            pipeline.append("   [#555555]▼[/#555555]")
            for line in box.split("\n"):
                pipeline.append(f"  {line}")
            if i < len(boxes) - 1:
                pipeline.append("   [#555555]│[/#555555]")
        pipeline.append("   [#555555]▼[/#555555]")
        pipeline.append("  [bold #4ade80]YOU[/bold #4ade80] [#555555](review gate)[/#555555]")

        view.update("\n".join(pipeline))

    def _load_evolution(self, root: Path, manifest: ProjectManifest) -> None:
        log_widget = self.query_one("#evo-log", RichLog)
        log_widget.clear()

        any_entries = False
        for name in manifest.agents:
            evo_log = root / AGENTS_DIR / name / "EVOLUTION_LOG.md"
            if evo_log.exists():
                content = evo_log.read_text().strip()
                if content and content != "# Evolution Log":
                    log_widget.write(f"[bold #a78bfa]── {name} ──[/bold #a78bfa]")
                    log_widget.write(content)
                    log_widget.write("")
                    any_entries = True

        if not any_entries:
            log_widget.write("[#555555]No evolution entries yet.[/#555555]")
            log_widget.write("[#555555]Agents will learn from their mistakes over time.[/#555555]")

    def _load_file_tree(self, root: Path) -> None:
        tree = self.query_one("#project-tree", Tree)
        tree.clear()
        tree.root.set_label(str(root.name))
        tree.root.expand()

        self._add_path_to_tree(tree.root, root, depth=0, max_depth=3)

    def _add_path_to_tree(self, node, path: Path, depth: int, max_depth: int) -> None:
        if depth >= max_depth:
            return

        try:
            entries = sorted(path.iterdir(), key=lambda p: (not p.is_dir(), p.name))
        except PermissionError:
            return

        for entry in entries:
            if entry.name.startswith("."):
                continue
            if entry.is_dir():
                branch = node.add(f"[bold]{entry.name}/[/bold]", expand=depth < 1)
                self._add_path_to_tree(branch, entry, depth + 1, max_depth)
            else:
                node.add_leaf(f"[dim]{entry.name}[/dim]")

    def action_tab(self, tab_id: str) -> None:
        self.query_one(TabbedContent).active = tab_id

    def action_refresh_data(self) -> None:
        self.project_root = find_project_root()

    def action_update_outdated(self) -> None:
        """Update all outdated agents to their latest bundled versions."""
        root = self.project_root
        if root is None or self._manifest is None or self._agent_registry is None:
            return
        if not self._outdated:
            self.notify("All agents are up to date", severity="information", timeout=3)
            return
        updated_names: list[str] = []
        for name in self._outdated:
            entry = self._agent_registry.get(name)
            self._install_agent(root, name, entry)
            updated_names.append(name)
        self._load_agents(root, self._manifest)
        self._load_pipeline(self._manifest)
        names_str = ", ".join(updated_names)
        self.notify(
            f"Updated {len(updated_names)} agent(s): {names_str}",
            severity="information",
            timeout=5,
        )

    def action_refresh_templates(self) -> None:
        """Refresh project templates from bundled marketplace content."""
        root = self.project_root
        if root is None or self._manifest is None:
            return
        if not compare_template_versions(self._manifest, __version__):
            self.notify("Templates are up to date", severity="information", timeout=3)
            return
        refresh_templates(root, __version__)
        # Reload manifest to pick up updated cli_version
        self._manifest = ProjectManifest.load(root)
        self.notify(
            f"Templates refreshed (cli: {__version__})",
            severity="information",
            timeout=5,
        )


def run_tui() -> None:
    """Launch the iEvo TUI dashboard."""
    app = IevoApp()
    app.run()
