#!/usr/bin/env python3
from __future__ import annotations

import json
import os
import socket
import sys
import urllib.error
import urllib.parse
import urllib.request
import uuid
from pathlib import Path
from typing import Any

from dxcli_runtime_config import load_portable_config
from dxcli_sessions import MANAGED_GLOBAL_ID_ENV, utc_now


TELEGRAM_MESSAGE_LIMIT = 4096


def _as_text(value: Any) -> str | None:
    if value is None:
        return None
    return str(value)


def _read_tail(path: str, *, max_bytes: int | None = 65536) -> str:
    if not path:
        return ""
    try:
        file_path = Path(path).expanduser()
        with file_path.open("rb") as handle:
            if max_bytes is not None:
                handle.seek(0, 2)
                size = handle.tell()
                handle.seek(max(size - max_bytes, 0), 0)
            return handle.read().decode("utf-8", errors="replace")
    except OSError:
        return ""


def _collect_text(value: Any) -> list[str]:
    if isinstance(value, str):
        stripped = value.strip()
        return [stripped] if stripped else []
    if isinstance(value, list):
        texts: list[str] = []
        for item in value:
            texts.extend(_collect_text(item))
        return texts
    if isinstance(value, dict):
        texts: list[str] = []
        for key in ("text", "message", "content", "last_assistant_message"):
            if key in value:
                texts.extend(_collect_text(value.get(key)))
        return texts
    return []


def _transcript_excerpt(path: str, *, max_chars: int | None = None) -> str:
    raw = _read_tail(path, max_bytes=None)
    if not raw.strip():
        return ""
    rendered: list[str] = []
    for line in raw.splitlines():
        stripped = line.strip()
        if not stripped:
            continue
        try:
            decoded = json.loads(stripped)
        except json.JSONDecodeError:
            rendered.append(stripped)
            continue
        texts = _collect_text(decoded)
        if not texts:
            continue
        role = ""
        if isinstance(decoded, dict):
            role = str(decoded.get("role") or decoded.get("type") or "").strip()
        body = "\n".join(texts).strip()
        rendered.append(f"{role}: {body}" if role else body)
    excerpt = "\n".join(rendered).strip()
    if max_chars is None or len(excerpt) <= max_chars:
        return excerpt
    return "..." + excerpt[-(max_chars - 3) :]


def _resolve_global_id() -> str:
    return os.getenv(MANAGED_GLOBAL_ID_ENV, "").strip()


def _build_event(payload: dict[str, Any], global_id: str) -> dict[str, Any]:
    transcript_path = str(payload.get("transcript_path") or "")
    last_assistant_message = _as_text(payload.get("last_assistant_message"))
    completion_log = str(last_assistant_message or "").strip() or _transcript_excerpt(transcript_path)
    return {
        "event_id": uuid.uuid4().hex,
        "event": str(payload.get("hook_event_name") or "Stop"),
        "host": socket.gethostname(),
        "global_id": global_id,
        "session_id": str(payload.get("session_id") or ""),
        "turn_id": str(payload.get("turn_id") or ""),
        "cwd": str(payload.get("cwd") or os.getcwd()),
        "transcript_path": transcript_path,
        "model": str(payload.get("model") or ""),
        "permission_mode": str(payload.get("permission_mode") or ""),
        "stop_hook_active": bool(payload.get("stop_hook_active", False)),
        "last_assistant_message": last_assistant_message,
        "completion_log": completion_log,
        "timestamp": utc_now(),
    }


def _format_message(event: dict[str, Any]) -> str:
    title = "dxcli portable Codex task completed"
    global_id = str(event.get("global_id") or "-")
    cwd = str(event.get("cwd") or "-")
    host = str(event.get("host") or "-")
    model = str(event.get("model") or "-")
    completion = str(event.get("completion_log") or event.get("last_assistant_message") or "").strip()
    if not completion:
        completion = "(no completion text captured)"

    message = (
        f"{title}\n"
        f"host: {host}\n"
        f"global_id: {global_id}\n"
        f"cwd: {cwd}\n"
        f"model: {model}\n"
        f"time: {event.get('timestamp')}\n\n"
        f"{completion}"
    )
    return message


def _split_telegram_text(text: str, *, limit: int = TELEGRAM_MESSAGE_LIMIT) -> list[str]:
    normalized = str(text or "")
    if not normalized:
        return [""]

    chunks: list[str] = []
    start = 0
    text_length = len(normalized)
    while start < text_length:
        end = min(start + limit, text_length)
        if end < text_length:
            window = normalized[start:end]
            split_at = max(window.rfind("\n\n"), window.rfind("\n"), window.rfind(" "))
            if split_at >= limit // 2:
                end = start + split_at + 1
        chunk = normalized[start:end]
        if not chunk:
            end = min(start + limit, text_length)
            chunk = normalized[start:end]
        chunks.append(chunk)
        start = end
    return chunks


def _append_spool_event(event: dict[str, Any]) -> None:
    config = load_portable_config()
    spool_path = str(config["hook_spool_path"])
    os.makedirs(os.path.dirname(spool_path), exist_ok=True)
    with open(spool_path, "a", encoding="utf-8") as handle:
        handle.write(json.dumps(event, ensure_ascii=False, sort_keys=True) + "\n")


def _deliver_telegram(bot_token: str, chat_id: str, event: dict[str, Any]) -> None:
    url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
    for chunk in _split_telegram_text(_format_message(event)):
        data = urllib.parse.urlencode(
            {
                "chat_id": chat_id,
                "text": chunk,
                "disable_web_page_preview": "true",
            }
        ).encode("utf-8")
        request = urllib.request.Request(
            url,
            data=data,
            headers={
                "Content-Type": "application/x-www-form-urlencoded",
                "User-Agent": "dxcli-portable-codex-hook/1.0",
            },
            method="POST",
        )
        with urllib.request.urlopen(request, timeout=8) as response:
            response.read()


def main() -> int:
    try:
        payload = json.load(sys.stdin)
    except json.JSONDecodeError:
        return 0
    if not isinstance(payload, dict):
        return 0

    event = _build_event(payload, _resolve_global_id())
    config = load_portable_config()
    bot_token = str(config.get("telegram_bot_token") or "").strip()
    chat_id = str(config.get("telegram_chat_id") or "").strip()
    if not bot_token or not chat_id:
        _append_spool_event(event)
        return 0

    try:
        _deliver_telegram(bot_token, chat_id, event)
    except (urllib.error.URLError, TimeoutError, OSError):
        _append_spool_event(event)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
