"""OpenCode local plugin installation helpers for AITP v5."""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any

from brain.v5.hook_install_templates import write_opencode_plugin_bridge


_AITP_PLUGIN_MARKER = "AITP_V5_OPENCODE_PLUGIN"


def install_opencode_plugin_file(
    path: str | Path,
    installation: dict[str, Any],
    runtime_gate_protocols: dict[str, Any] | None = None,
    *,
    workspace_base: str,
    session_id: str,
    bridge_path: str | Path | None = None,
) -> dict[str, Any]:
    """Write an OpenCode local plugin that calls the AITP v5 hook runner."""

    plugin_path = Path(path)
    resolved_bridge_path = Path(bridge_path) if bridge_path else _default_bridge_path(plugin_path)
    bridge = write_opencode_plugin_bridge(
        resolved_bridge_path,
        installation,
        runtime_gate_protocols,
        session_id=session_id,
    )
    pre_tool_hook = _pre_tool_hook(
        workspace_base=workspace_base,
        session_id=session_id,
        bridge_payload_path=bridge["payload_path"],
    )
    post_tool_hook = _post_tool_hook(workspace_base=workspace_base, session_id=session_id)
    plugin = {
        "kind": "opencode_local_plugin",
        "runtime": "opencode",
        "plugin_name": "AITPV5Plugin",
        "path": str(plugin_path),
        "lifecycle_events": ["tool.execute.before", "tool.execute.after"],
        "pre_tool": pre_tool_hook,
        "post_tool": post_tool_hook,
        "truth_rule": "plugin is runtime metadata only; typed records remain authoritative",
        "summary_inputs_trusted": False,
    }
    source = _plugin_source(plugin)
    created = not plugin_path.exists()
    if not created:
        current = plugin_path.read_text(encoding="utf-8")
        if _AITP_PLUGIN_MARKER not in current and current != source:
            raise ValueError(f"refusing to overwrite non-AITP OpenCode plugin: {plugin_path}")
        changed = current != source
    else:
        changed = True

    plugin_path.parent.mkdir(parents=True, exist_ok=True)
    if changed:
        plugin_path.write_text(source, encoding="utf-8")

    return {
        "kind": "opencode_hook_installation",
        "runtime": "opencode",
        "source_protocol_field": "runtime_hook_installation",
        "installation_mode": installation["installation_mode"],
        "native_installer_available": True,
        "fixture_installer_available": True,
        "summary_inputs_trusted": False,
        "can_update_kernel_state": False,
        "can_update_claim_trust": False,
        "path": str(plugin_path),
        "plugin_path": str(plugin_path),
        "bridge_path": bridge["path"],
        "bridge_payload_path": bridge["payload_path"],
        "bridge": bridge,
        "plugin": plugin,
        "created": created,
        "changed": changed,
    }


def _default_bridge_path(plugin_path: Path) -> Path:
    if plugin_path.parent.name == "plugins":
        return plugin_path.parent.parent / "AITP_V5_PLUGIN_BRIDGE.md"
    return plugin_path.parent / "AITP_V5_PLUGIN_BRIDGE.md"


def _pre_tool_hook(*, workspace_base: str, session_id: str, bridge_payload_path: str) -> dict[str, Any]:
    runner = _adapter_event_runner_path()
    return {
        "lifecycle_event": "pre_tool",
        "command_kind": "stdin_json_runner",
        "cwd": str(_repo_root()),
        "argv": [
            sys.executable,
            str(runner),
            "pre-tool",
            "--base",
            workspace_base,
            "--runtime",
            "opencode",
            "--session-id",
            session_id,
            "--bridge-path",
            bridge_payload_path,
        ],
        "stdin": "<platform-event-json>",
        "output_kind": "pre_tool_policy_decision",
        "may_block": True,
        "state_mutation": "none",
    }


def _post_tool_hook(*, workspace_base: str, session_id: str) -> dict[str, Any]:
    runner = _adapter_event_runner_path()
    return {
        "lifecycle_event": "post_tool",
        "command_kind": "stdin_json_runner",
        "cwd": str(_repo_root()),
        "argv": [
            sys.executable,
            str(runner),
            "post-tool",
            "--base",
            workspace_base,
            "--runtime",
            "opencode",
            "--session-id",
            session_id,
        ],
        "stdin": "<platform-event-json>",
        "output_kind": "hook_trace_event_record",
        "may_block": False,
        "state_mutation": "append_trace_event",
    }


def _plugin_source(plugin: dict[str, Any]) -> str:
    return f"""// {_AITP_PLUGIN_MARKER}
// Generated by AITP v5. Runtime metadata only; typed kernel records remain authoritative.
import {{ spawnSync }} from "node:child_process"

const PRE_TOOL_ARGV = {_js(plugin["pre_tool"]["argv"])}
const POST_TOOL_ARGV = {_js(plugin["post_tool"]["argv"])}
const RUNNER_CWD = {_js(plugin["pre_tool"]["cwd"])}
const SESSION_ID = {_js(plugin["pre_tool"]["argv"][8])}

function normalizeToolName(input, output) {{
  if (typeof input?.tool === "string") return input.tool
  if (typeof input?.tool?.name === "string") return input.tool.name
  if (typeof output?.tool === "string") return output.tool
  if (typeof output?.tool?.name === "string") return output.tool.name
  return ""
}}

function normalizeToolInput(input, output) {{
  if (output?.args && typeof output.args === "object") return output.args
  if (input?.args && typeof input.args === "object") return input.args
  if (input?.tool?.input && typeof input.tool.input === "object") return input.tool.input
  return {{}}
}}

function eventPayload(lifecycleEvent, input, output) {{
  const toolName = normalizeToolName(input, output)
  return {{
    runtime: "opencode",
    lifecycle_event: lifecycleEvent,
    session_id: SESSION_ID,
    tool_name: toolName,
    tool: {{ name: toolName, input: normalizeToolInput(input, output) }},
    opencode_input: input,
    opencode_output: output,
  }}
}}

function runAitp(argv, event) {{
  const result = spawnSync(argv[0], argv.slice(1), {{
    cwd: RUNNER_CWD,
    input: JSON.stringify(event),
    encoding: "utf8",
  }})
  if (result.error) throw result.error
  let payload = {{}}
  if (result.stdout && result.stdout.trim()) {{
    try {{
      payload = JSON.parse(result.stdout)
    }} catch (error) {{
      throw new Error(`AITP v5 hook runner returned invalid JSON: ${{result.stdout}}`)
    }}
  }}
  if (result.status === 2 || payload.exit_code === 2 || payload.block === true || payload.allowed === false) {{
    throw new Error(payload.message || "AITP v5 blocked OpenCode tool execution")
  }}
  if (result.status !== 0) {{
    throw new Error(result.stderr || `AITP v5 hook runner exited with status ${{result.status}}`)
  }}
  return payload
}}

export const AITPV5Plugin = async () => {{
  return {{
    "tool.execute.before": async (input, output) => {{
      runAitp(PRE_TOOL_ARGV, eventPayload("pre_tool", input, output))
    }},
    "tool.execute.after": async (input, output) => {{
      try {{
        runAitp(POST_TOOL_ARGV, eventPayload("post_tool", input, output))
      }} catch (error) {{
        console.warn(`AITP v5 post-tool trace failed: ${{error.message}}`)
      }}
    }},
  }}
}}
"""


def _js(value: Any) -> str:
    return json.dumps(value, ensure_ascii=False)


def _repo_root() -> Path:
    return Path(__file__).resolve().parents[2]


def _adapter_event_runner_path() -> Path:
    return _repo_root() / "hooks" / "aitp_v5_adapter_event_runner.py"
