from __future__ import annotations

import json
import os
import hashlib
import re
from pathlib import Path
from typing import Any


HOME = Path.home()
IVOX_DIR = HOME / ".config" / "ivox"
HOOK_SH = str(IVOX_DIR / "hook-speak.sh")
PI_EXTENSION = str(IVOX_DIR / "ivox.ts")


def read_json(path: Path, default: Any) -> Any:
    if not path.exists():
        return default
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise SystemExit(f"invalid json: {path}: {exc}") from exc


def write_json(path: Path, data: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    backup = path.with_suffix(path.suffix + ".ivox.bak")
    if path.exists() and not backup.exists():
        backup.write_text(path.read_text(encoding="utf-8"), encoding="utf-8")
    path.write_text(
        json.dumps(data, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )


def backup_once(path: Path) -> None:
    backup = path.with_suffix(path.suffix + ".ivox.bak")
    if path.exists() and not backup.exists():
        backup.write_text(path.read_text(encoding="utf-8"), encoding="utf-8")


def codex_hook_hash(command: str, timeout: int = 30) -> str:
    identity = {
        "event_name": "stop",
        "hooks": [
            {
                "async": False,
                "command": command,
                "timeout": timeout,
                "type": "command",
            }
        ],
    }
    payload = json.dumps(
        identity,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")
    return "sha256:" + hashlib.sha256(payload).hexdigest()


def stop_hook(command: str, timeout: int | None = None) -> dict[str, Any]:
    hook: dict[str, Any] = {"type": "command", "command": command}
    if timeout is not None:
        hook["timeout"] = timeout
    return {"hooks": [hook]}


def install_claude() -> None:
    path = HOME / ".claude" / "settings.json"
    cfg = read_json(path, {})
    if not isinstance(cfg, dict):
        raise SystemExit(f"unexpected config shape: {path}")

    hooks = cfg.setdefault("hooks", {})
    hooks["Stop"] = [stop_hook(f"bash {HOOK_SH} claude")]
    write_json(path, cfg)
    print(f"claude: {path}")


def install_codex() -> None:
    path = HOME / ".codex" / "hooks.json"
    cfg = read_json(path, {})
    if not isinstance(cfg, dict):
        raise SystemExit(f"unexpected config shape: {path}")

    command = f"bash {HOOK_SH} codex"
    hooks = cfg.setdefault("hooks", {})
    hooks["Stop"] = [stop_hook(command, timeout=30)]
    write_json(path, cfg)
    install_codex_trust(codex_hook_hash(command))
    print(f"codex: {path}")


def install_codex_trust(trusted_hash: str) -> None:
    path = HOME / ".codex" / "config.toml"
    key = f'{HOME}/.codex/hooks.json:stop:0:0'
    block_header = f'[hooks.state."{key}"]'
    if path.exists():
        text = path.read_text(encoding="utf-8")
    else:
        text = ""

    backup_once(path)
    if block_header in text:
        pattern = re.compile(
            rf'(\[hooks\.state\."{re.escape(key)}"\]\n)(.*?)(?=\n\[|\Z)',
            re.S,
        )

        def replace(match: re.Match[str]) -> str:
            body = match.group(2)
            if re.search(r"(?m)^trusted_hash\s*=", body):
                body = re.sub(
                    r'(?m)^trusted_hash\s*=.*$',
                    f'trusted_hash = "{trusted_hash}"',
                    body,
                )
            else:
                body = body.rstrip() + f'\ntrusted_hash = "{trusted_hash}"\n'
            if not re.search(r"(?m)^enabled\s*=", body):
                body = 'enabled = true\n' + body
            return match.group(1) + body.rstrip() + "\n"

        text = pattern.sub(replace, text, count=1)
    else:
        if "[hooks.state]" not in text:
            text = text.rstrip() + "\n\n[hooks.state]\n"
        text = (
            text.rstrip()
            + f'\n\n{block_header}\n'
            + 'enabled = true\n'
            + f'trusted_hash = "{trusted_hash}"\n'
        )
    path.write_text(text, encoding="utf-8")


def install_pi() -> None:
    path = Path(os.environ.get("PI_CODING_AGENT_DIR", HOME / ".pi" / "agent")) / "settings.json"
    cfg = read_json(path, {})
    if not isinstance(cfg, dict):
        raise SystemExit(f"unexpected config shape: {path}")

    extensions = cfg.get("extensions", [])
    if not isinstance(extensions, list):
        extensions = []

    filtered = [
        item
        for item in extensions
        if isinstance(item, str)
        and "ispeak" not in item.lower()
        and item != PI_EXTENSION
    ]
    filtered.append(PI_EXTENSION)
    cfg["extensions"] = filtered
    write_json(path, cfg)
    print(f"pi: {path}")


def main() -> None:
    install_claude()
    install_codex()
    install_pi()


if __name__ == "__main__":
    main()
