"""
Operon Plasmid Registry -- Horizontal Gene Transfer Demo
========================================================

Dynamic tool acquisition from a searchable registry with capability gating.
Agents can discover, acquire, execute, and release tools at runtime -- like
bacteria exchanging plasmids via horizontal gene transfer.

Run locally:
    pip install gradio
    python space-plasmid/app.py

Deploy to HuggingFace Spaces:
    Copy this directory to a new HF Space with sdk=gradio.
"""

import sys
from pathlib import Path

import gradio as gr

_repo_root = Path(__file__).resolve().parent.parent
if str(_repo_root) not in sys.path:
    sys.path.insert(0, str(_repo_root))

from operon_ai.core.types import Capability
from operon_ai.organelles.mitochondria import Mitochondria
from operon_ai.organelles.plasmid import Plasmid, PlasmidRegistry


# ---------------------------------------------------------------------------
# Preset capability sets
# ---------------------------------------------------------------------------

CAPABILITY_PRESETS: dict[str, set[Capability]] = {
    "No restrictions": set(),
    "READ_FS only": {Capability.READ_FS},
    "NET only": {Capability.NET},
    "NET + READ_FS": {Capability.NET, Capability.READ_FS},
    "EXEC_CODE only": {Capability.EXEC_CODE},
    "All capabilities": {Capability.NET, Capability.READ_FS, Capability.EXEC_CODE, Capability.WRITE_FS},
}

# ---------------------------------------------------------------------------
# Build default registry
# ---------------------------------------------------------------------------

def _build_registry() -> PlasmidRegistry:
    registry = PlasmidRegistry()

    registry.register(Plasmid(
        name="reverse",
        description="Reverse a string",
        func=lambda s: s[::-1],
        tags=frozenset({"text", "utility"}),
        version="1.0.0",
    ))
    registry.register(Plasmid(
        name="word_count",
        description="Count words in a string",
        func=lambda s: len(s.split()),
        tags=frozenset({"text", "analysis"}),
        version="1.0.0",
    ))
    registry.register(Plasmid(
        name="uppercase",
        description="Convert string to uppercase",
        func=lambda s: s.upper(),
        tags=frozenset({"text", "transform"}),
        version="1.0.0",
    ))
    registry.register(Plasmid(
        name="char_count",
        description="Count characters in a string",
        func=lambda s: len(s),
        tags=frozenset({"text", "analysis"}),
        version="1.2.0",
    ))
    registry.register(Plasmid(
        name="fetch_url",
        description="Fetch a URL (simulated)",
        func=lambda url: f"<html>Content from {url}</html>",
        tags=frozenset({"network", "io"}),
        version="1.0.0",
        required_capabilities=frozenset({Capability.NET}),
    ))
    registry.register(Plasmid(
        name="read_file",
        description="Read a file (simulated)",
        func=lambda path: f"Contents of {path}",
        tags=frozenset({"filesystem", "io"}),
        version="1.0.0",
        required_capabilities=frozenset({Capability.READ_FS}),
    ))
    registry.register(Plasmid(
        name="run_script",
        description="Execute a script (simulated)",
        func=lambda code: f"Executed: {code}",
        tags=frozenset({"execution", "code"}),
        version="2.0.0",
        required_capabilities=frozenset({Capability.EXEC_CODE}),
    ))
    return registry


# ---------------------------------------------------------------------------
# Session state
# ---------------------------------------------------------------------------

_registry = _build_registry()
_mito: Mitochondria | None = None
_event_log: list[str] = []


def _reset_agent(caps_label: str) -> str:
    global _mito, _event_log
    _event_log = []

    if caps_label == "No restrictions":
        _mito = Mitochondria(silent=True)
        _event_log.append("Agent created with NO capability restrictions.")
    else:
        caps = CAPABILITY_PRESETS.get(caps_label, set())
        _mito = Mitochondria(allowed_capabilities=caps, silent=True)
        cap_names = sorted(c.value for c in caps) if caps else ["none"]
        _event_log.append(f"Agent created with capabilities: {cap_names}")

    return _render_state()


def _render_state() -> str:
    if _mito is None:
        return '<div style="padding:16px;color:#6b7280;">No agent created yet. Select capabilities and click Create.</div>'

    tools = list(_mito.tools.keys())
    tool_badges = ""
    if tools:
        tool_badges = " ".join(
            f'<span style="background:#8b5cf6;color:white;padding:2px 8px;'
            f'border-radius:4px;font-size:0.85em;">{t}</span>'
            for t in tools
        )
    else:
        tool_badges = '<span style="color:#9ca3af;font-style:italic;">none</span>'

    caps = _mito.allowed_capabilities
    if caps is None:
        cap_text = "unrestricted"
    elif caps:
        cap_text = ", ".join(sorted(c.value for c in caps))
    else:
        cap_text = "none (empty set)"

    html = (
        f'<div style="padding:16px;border-radius:8px;border:1px solid #d1d5db;background:#f9fafb;">'
        f'<div style="font-weight:700;margin-bottom:8px;">Agent State</div>'
        f'<div style="margin-bottom:4px;"><strong>Capabilities:</strong> {cap_text}</div>'
        f'<div style="margin-bottom:4px;"><strong>Tools ({len(tools)}):</strong> {tool_badges}</div>'
        f'<div style="font-size:0.85em;color:#6b7280;">ROS: {_mito.get_ros_level():.2f} | '
        f'Operations: {_mito._operations_count}</div>'
        f'</div>'
    )
    return html


def _render_log() -> str:
    if not _event_log:
        return ""
    rows = ""
    for i, entry in enumerate(reversed(_event_log[-20:]), 1):
        bg = "#f0fdf4" if "OK" in entry or "Success" in entry else (
            "#fef2f2" if "BLOCKED" in entry or "Error" in entry or "Failed" in entry
            else "#f9fafb"
        )
        rows += f'<div style="padding:6px 10px;background:{bg};border-bottom:1px solid #e5e7eb;font-size:0.9em;">{entry}</div>'
    return f'<div style="border:1px solid #d1d5db;border-radius:8px;overflow:hidden;max-height:400px;overflow-y:auto;">{rows}</div>'


# ---------------------------------------------------------------------------
# Registry browsing
# ---------------------------------------------------------------------------

def browse_registry(search_query: str) -> str:
    if search_query.strip():
        results = _registry.search(search_query)
    else:
        results = [_registry.get(item["name"]) for item in _registry.list_available()]

    if not results:
        return '<div style="padding:12px;color:#6b7280;">No plasmids found.</div>'

    rows = ""
    for p in results:
        caps = sorted(c.value for c in p.required_capabilities) if p.required_capabilities else ["none"]
        tags = sorted(p.tags)
        cap_color = "#22c55e" if caps == ["none"] else "#ef4444"
        rows += (
            f'<tr>'
            f'<td style="padding:8px;font-weight:600;">{p.name}</td>'
            f'<td style="padding:8px;">{p.description}</td>'
            f'<td style="padding:8px;">{p.version}</td>'
            f'<td style="padding:8px;">{", ".join(tags)}</td>'
            f'<td style="padding:8px;"><span style="color:{cap_color};">{", ".join(caps)}</span></td>'
            f'</tr>'
        )

    return (
        f'<table style="width:100%;border-collapse:collapse;">'
        f'<tr style="background:#f3f4f6;"><th style="padding:8px;text-align:left;">Name</th>'
        f'<th style="padding:8px;text-align:left;">Description</th>'
        f'<th style="padding:8px;text-align:left;">Version</th>'
        f'<th style="padding:8px;text-align:left;">Tags</th>'
        f'<th style="padding:8px;text-align:left;">Required Caps</th></tr>'
        f'{rows}</table>'
    )


# ---------------------------------------------------------------------------
# Acquire / Release / Execute
# ---------------------------------------------------------------------------

def acquire_plasmid(plasmid_name: str) -> tuple[str, str]:
    if _mito is None:
        _event_log.append("Error: Create an agent first.")
        return _render_state(), _render_log()

    name = plasmid_name.strip()
    if not name:
        _event_log.append("Error: Enter a plasmid name.")
        return _render_state(), _render_log()

    result = _mito.acquire(name, _registry)
    if result.success:
        _event_log.append(f"OK -- Acquired '{name}' successfully.")
    else:
        _event_log.append(f"BLOCKED -- Acquire '{name}' failed: {result.error}")

    return _render_state(), _render_log()


def release_plasmid(plasmid_name: str) -> tuple[str, str]:
    if _mito is None:
        _event_log.append("Error: Create an agent first.")
        return _render_state(), _render_log()

    name = plasmid_name.strip()
    if not name:
        _event_log.append("Error: Enter a tool name to release.")
        return _render_state(), _render_log()

    try:
        _mito.release(name)
        _event_log.append(f"OK -- Released '{name}' (plasmid curing).")
    except ValueError as e:
        _event_log.append(f"Failed -- Release '{name}': {e}")

    return _render_state(), _render_log()


def execute_tool(expression: str) -> tuple[str, str, str]:
    if _mito is None:
        _event_log.append("Error: Create an agent first.")
        return "", _render_state(), _render_log()

    expr = expression.strip()
    if not expr:
        return "", _render_state(), _render_log()

    result = _mito.metabolize(expr)
    if result.success and result.atp:
        result_html = (
            f'<div style="padding:12px;border-radius:8px;border:2px solid #22c55e;background:#f0fdf4;">'
            f'<span style="font-weight:700;color:#16a34a;">Result:</span> '
            f'<span style="font-family:monospace;font-size:1.2em;">{result.atp.value!r}</span>'
            f'</div>'
        )
        _event_log.append(f"OK -- `{expr}` = {result.atp.value!r}")
    else:
        result_html = (
            f'<div style="padding:12px;border-radius:8px;border:2px solid #ef4444;background:#fef2f2;">'
            f'<span style="font-weight:700;color:#dc2626;">Error:</span> '
            f'<span style="font-family:monospace;">{result.error}</span>'
            f'</div>'
        )
        _event_log.append(f"Failed -- `{expr}`: {result.error}")

    return result_html, _render_state(), _render_log()


# ---------------------------------------------------------------------------
# Full scenario demo
# ---------------------------------------------------------------------------

def run_scenario() -> tuple[str, str]:
    global _mito, _event_log
    _event_log = []
    lines: list[str] = []

    lines.append("### Scenario: Capability-Gated Tool Acquisition\n")

    # Step 1: Create restricted agent
    _mito = Mitochondria(allowed_capabilities={Capability.READ_FS}, silent=True)
    _event_log.append("Agent created with capabilities: ['read_fs']")
    lines.append("**Step 1:** Create agent with `READ_FS` capability only.\n")

    # Step 2: Acquire safe tools
    r1 = _mito.acquire("reverse", _registry)
    _event_log.append(f"Acquire 'reverse': {'OK' if r1.success else 'BLOCKED -- ' + str(r1.error)}")
    lines.append(f"**Step 2:** Acquire `reverse` (no caps required): **{'Success' if r1.success else 'Blocked'}**\n")

    r2 = _mito.acquire("word_count", _registry)
    _event_log.append(f"Acquire 'word_count': {'OK' if r2.success else 'BLOCKED -- ' + str(r2.error)}")
    lines.append(f"**Step 3:** Acquire `word_count` (no caps required): **{'Success' if r2.success else 'Blocked'}**\n")

    # Step 3: Try to acquire NET tool
    r3 = _mito.acquire("fetch_url", _registry)
    _event_log.append(f"Acquire 'fetch_url': {'OK' if r3.success else 'BLOCKED -- ' + str(r3.error)}")
    lines.append(f"**Step 4:** Acquire `fetch_url` (requires `NET`): **{'Success' if r3.success else 'Blocked'}**\n")
    if not r3.success:
        lines.append(f"> *{r3.error}*\n")

    # Step 4: Acquire FS tool (allowed)
    r4 = _mito.acquire("read_file", _registry)
    _event_log.append(f"Acquire 'read_file': {'OK' if r4.success else 'BLOCKED -- ' + str(r4.error)}")
    lines.append(f"**Step 5:** Acquire `read_file` (requires `READ_FS`): **{'Success' if r4.success else 'Blocked'}**\n")

    # Step 5: Execute acquired tools
    lines.append("**Step 6:** Execute acquired tools:\n")

    res = _mito.metabolize('reverse("hello world")')
    val = res.atp.value if res.success and res.atp else res.error
    _event_log.append(f"Execute reverse('hello world'): {val}")
    lines.append(f"- `reverse(\"hello world\")` = `{val}`\n")

    res = _mito.metabolize('word_count("the quick brown fox jumps")')
    val = res.atp.value if res.success and res.atp else res.error
    _event_log.append(f"Execute word_count: {val}")
    lines.append(f"- `word_count(\"the quick brown fox jumps\")` = `{val}`\n")

    # Step 6: Release and verify
    _mito.release("reverse")
    _event_log.append("OK -- Released 'reverse' (plasmid curing).")
    lines.append("**Step 7:** Release `reverse` (plasmid curing).\n")

    res = _mito.metabolize('reverse("test")')
    _event_log.append(f"Execute after release: {'Success' if res.success else 'Failed -- ' + str(res.error)}")
    lines.append(f"**Step 8:** Execute `reverse(\"test\")` after release: **{'Success' if res.success else 'Blocked'}** (tool removed)\n")

    # Step 7: Re-acquire
    r5 = _mito.acquire("reverse", _registry)
    _event_log.append(f"Re-acquire 'reverse': {'OK' if r5.success else 'BLOCKED'}")
    lines.append(f"**Step 9:** Re-acquire `reverse`: **{'Success' if r5.success else 'Blocked'}**\n")

    lines.append(f"\n**Final tools:** {list(_mito.tools.keys())}")

    return "\n".join(lines), _render_log()


# ---------------------------------------------------------------------------
# Gradio UI
# ---------------------------------------------------------------------------

def build_app() -> gr.Blocks:
    with gr.Blocks(title="Operon Plasmid Registry") as app:
        gr.Markdown(
            "# Operon Plasmid Registry -- Horizontal Gene Transfer\n"
            "Dynamic tool acquisition with optional capability gating (Paper §6.2). "
            "Agents discover and absorb tools from a registry. When a capability "
            "envelope is configured, acquisition checks reject tools that exceed it.\n\n"
            "[GitHub](https://github.com/coredipper/operon) | "
            "[Paper](https://github.com/coredipper/operon/tree/main/article)"
        )

        with gr.Tabs():
            # ── Tab 1: Interactive ────────────────────────────────
            with gr.TabItem("Interactive"):
                with gr.Row():
                    with gr.Column(scale=1):
                        gr.Markdown("### 1. Create Agent")
                        caps_dropdown = gr.Dropdown(
                            choices=list(CAPABILITY_PRESETS.keys()),
                            value="READ_FS only",
                            label="Allowed Capabilities",
                        )
                        create_btn = gr.Button("Create Agent", variant="primary")
                        agent_state = gr.HTML(label="Agent State")

                    with gr.Column(scale=2):
                        gr.Markdown("### 2. Registry")
                        search_input = gr.Textbox(
                            label="Search plasmids",
                            placeholder="e.g. text, reverse, network...",
                        )
                        registry_html = gr.HTML(value=browse_registry(""))

                with gr.Row():
                    with gr.Column():
                        gr.Markdown("### 3. Acquire / Release")
                        plasmid_input = gr.Textbox(
                            label="Plasmid name",
                            placeholder="e.g. reverse",
                        )
                        with gr.Row():
                            acquire_btn = gr.Button("Acquire", variant="primary")
                            release_btn = gr.Button("Release", variant="secondary")
                    with gr.Column():
                        gr.Markdown("### 4. Execute")
                        expr_input = gr.Textbox(
                            label="Expression",
                            placeholder='e.g. reverse("hello")',
                        )
                        exec_btn = gr.Button("Execute", variant="primary")
                        exec_result = gr.HTML()

                gr.Markdown("### Event Log")
                log_html = gr.HTML()

                # Bindings
                create_btn.click(fn=_reset_agent, inputs=[caps_dropdown], outputs=[agent_state])
                search_input.change(fn=browse_registry, inputs=[search_input], outputs=[registry_html])
                search_input.submit(fn=browse_registry, inputs=[search_input], outputs=[registry_html])
                acquire_btn.click(fn=acquire_plasmid, inputs=[plasmid_input], outputs=[agent_state, log_html])
                release_btn.click(fn=release_plasmid, inputs=[plasmid_input], outputs=[agent_state, log_html])
                exec_btn.click(fn=execute_tool, inputs=[expr_input], outputs=[exec_result, agent_state, log_html])
                expr_input.submit(fn=execute_tool, inputs=[expr_input], outputs=[exec_result, agent_state, log_html])

            # ── Tab 2: Full scenario ──────────────────────────────
            with gr.TabItem("Guided Scenario"):
                gr.Markdown(
                    "### Capability-Gated Tool Acquisition\n\n"
                    "Run a complete scenario demonstrating:\n"
                    "- Agent creation with limited capabilities\n"
                    "- Safe tool acquisition (no caps required)\n"
                    "- Blocked acquisition (insufficient capabilities)\n"
                    "- Tool execution, release (plasmid curing), and re-acquisition"
                )
                scenario_btn = gr.Button("Run Scenario", variant="primary")
                scenario_md = gr.Markdown()
                scenario_log = gr.HTML()
                scenario_btn.click(fn=run_scenario, outputs=[scenario_md, scenario_log])

    return app


if __name__ == "__main__":
    app = build_app()
    app.launch(theme=gr.themes.Soft())
