"""anu-confirm-bot — Telegram inline button webhook for Tier 2 1-tap approval."""
from __future__ import annotations

import json
import logging
import subprocess
import time
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any

from flask import Flask, jsonify, request

from . import config
from .signer import sign_callback, verify_callback

logger = logging.getLogger("anu_confirm_bot")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")

app = Flask(__name__)


def _telegram_api(method: str, payload: dict[str, Any]) -> dict:
    """Telegram Bot API 호출 (urllib, 외부 의존 0)."""
    if not config.BOT_TOKEN:
        logger.warning("BOT_TOKEN 미설정 — dry-run mode")
        return {"ok": False, "dry_run": True, "method": method, "payload": payload}
    url = f"https://api.telegram.org/bot{config.BOT_TOKEN}/{method}"
    data = urllib.parse.urlencode(payload).encode("utf-8")
    try:
        with urllib.request.urlopen(url, data=data, timeout=10) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except Exception as exc:
        logger.error(f"Telegram API 호출 실패: {exc}")
        return {"ok": False, "error": str(exc)}


def send_approval_card(task_id: str, pr_num: int, summary: dict[str, Any]) -> dict:
    """Tier 2 머지 후보 PR에 대해 inline button 카드 발송.

    summary: {"files": int, "additions": int, "deletions": int, "level": int, ...}
    """
    task_num = int(task_id.replace("task-", "")) if task_id.startswith("task-") else int(task_id)
    expiry = int(time.time()) + config.TTL_SECONDS
    cb_a = sign_callback("a", task_num, pr_num, expiry, config.SECRET_KEY)
    cb_r = sign_callback("r", task_num, pr_num, expiry, config.SECRET_KEY)
    cb_d = sign_callback("d", task_num, pr_num, expiry, config.SECRET_KEY)

    text = (
        f"★ task-{task_num} Tier 2 머지 승인 필요\n"
        f"- 변경: {summary.get('files', '?')} 파일, "
        f"+{summary.get('additions', '?')} / -{summary.get('deletions', '?')}\n"
        f"- PR #{pr_num} · Lv.{summary.get('level', '?')} · scope_guard PASS · qc PASS\n"
        f"- 만료: 5분 후"
    )
    keyboard = {
        "inline_keyboard": [[
            {"text": "✅ Approve", "callback_data": cb_a},
            {"text": "❌ Reject", "callback_data": cb_r},
            {"text": "👁 Diff", "callback_data": cb_d},
        ]]
    }
    payload = {
        "chat_id": config.CHAT_ID,
        "text": text,
        "reply_markup": json.dumps(keyboard, ensure_ascii=False),
    }
    return _telegram_api("sendMessage", payload)


def _processed_marker(task_num: int, pr_num: int, action: str) -> Path:
    return Path(config.PROCESSED_DIR) / f"processed-{task_num}-{pr_num}-{action}"


def _is_processed(task_num: int, pr_num: int, action: str) -> bool:
    return _processed_marker(task_num, pr_num, action).exists()


def _mark_processed(task_num: int, pr_num: int, action: str, result: dict) -> None:
    marker = _processed_marker(task_num, pr_num, action)
    marker.parent.mkdir(parents=True, exist_ok=True)
    marker.write_text(
        json.dumps({"timestamp": int(time.time()), "result": result}, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def _audit_append(record: dict) -> None:
    """memory/audit/auto-merge.log에 본 webhook 기원 entry 기록."""
    audit_dir = Path(config.WORKSPACE_ROOT) / "memory" / "audit"
    audit_dir.mkdir(parents=True, exist_ok=True)
    audit_log = audit_dir / "auto-merge.log"
    sequence = 1
    if audit_log.exists():
        try:
            sequence = sum(1 for _ in audit_log.open("r", encoding="utf-8")) + 1
        except OSError:
            sequence = 1
    record.setdefault("timestamp", time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime()))
    record["sequence"] = sequence
    record["merger"] = "anu_confirm_bot"
    with audit_log.open("a", encoding="utf-8") as f:
        f.write(json.dumps(record, ensure_ascii=False) + "\n")


def _execute_approve(task_num: int, pr_num: int) -> dict:
    """gh pr merge 호출. GitHub API가 직렬화 보장.

    Lock-in 1 First-line: cancelled 검사 + guard.sh 강제 실행이 함수 첫 statement.
    Lock-in 2 Hard stop: 가드 실패 시 gh pr merge subprocess 진입 0회 보장.
    """
    _cancelled_marker = Path(config.WORKSPACE_ROOT) / "memory" / "events" / f"task-{task_num}.cancelled"
    if _cancelled_marker.exists():
        return {"ok": False, "task_num": task_num, "pr_num": pr_num, "stderr": f"cancelled — merge blocked (task-{task_num})", "blocked_by": "anu_confirm_bot.cancelled_marker"}
    _guard_sh = Path(config.WORKSPACE_ROOT) / "scripts" / "guard.sh"
    _task_id = f"task-{task_num}"
    for _stage in ("pre-push", "qc-check"):
        _gp = subprocess.run(["bash", str(_guard_sh), _stage, _task_id], capture_output=True, text=True, timeout=60)
        if _gp.returncode != 0:
            return {"ok": False, "task_num": task_num, "pr_num": pr_num, "stderr": f"guard.sh {_stage} FAIL: {_gp.stderr[-300:]}", "returncode": _gp.returncode, "blocked_by": f"guard.sh.{_stage}"}
    # task-2449 Fix 5: gh pr merge 직접 호출 폐기 → taskctl merge 라우팅
    # 회장 절대 기준: taskctl을 거치지 않고는 main을 절대 변경할 수 없다.
    _taskctl = Path(config.WORKSPACE_ROOT) / "scripts" / "taskctl.py"
    cmd = ["python3", str(_taskctl), "merge", _task_id]
    try:
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
        return {
            "ok": proc.returncode == 0,
            "task_num": task_num,
            "pr_num": pr_num,
            "stdout": proc.stdout[-500:],
            "stderr": proc.stderr[-500:],
            "returncode": proc.returncode,
            "routed_via": "taskctl.merge",
        }
    except Exception as exc:
        return {"ok": False, "task_num": task_num, "pr_num": pr_num, "error": str(exc), "routed_via": "taskctl.merge"}


def _execute_reject(task_num: int, pr_num: int) -> dict:
    """reject: PR에 코멘트 추가하고 escalate 알림."""
    cmd = ["gh", "pr", "comment", str(pr_num), "--repo", config.GH_REPO,
           "--body", f"[REJECT] Tier 2 인라인 버튼 거부 (task-{task_num}). 회장 검토 필요."]
    try:
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        return {"ok": proc.returncode == 0, "stdout": proc.stdout[-500:], "stderr": proc.stderr[-500:]}
    except Exception as exc:
        return {"ok": False, "error": str(exc)}


def _execute_diff(task_num: int, pr_num: int) -> dict:
    """diff: PR 페이지 URL 반환 (Telegram에 메시지 발송)."""
    pr_url = f"https://github.com/{config.GH_REPO}/pull/{pr_num}"
    return {"ok": True, "task_num": task_num, "pr_num": pr_num, "diff_url": pr_url}


@app.route("/healthz", methods=["GET"])
def healthz():
    return jsonify({"status": "ok", "trigger_marker": config.TRIGGER_MARKER})


@app.route("/webhook", methods=["POST"])
def telegram_webhook():
    """Telegram inline button callback 수신."""
    try:
        update = request.get_json(force=True)
    except Exception:
        return jsonify({"ok": False, "reason": "invalid_json"}), 400

    callback_query = update.get("callback_query")
    if not callback_query:
        return jsonify({"ok": True, "ignored": "no_callback_query"}), 200

    callback_data = callback_query.get("data", "")
    callback_id = callback_query.get("id", "")

    verified = verify_callback(callback_data, config.SECRET_KEY)
    if not verified["ok"]:
        logger.warning(f"callback 검증 실패: {verified['reason']} | data={callback_data}")
        _telegram_api("answerCallbackQuery", {"callback_query_id": callback_id, "text": f"❌ 검증 실패: {verified['reason']}", "show_alert": True})
        _audit_append({
            "task_id": "unknown",
            "pr_number": None,
            "tier": "tier2",
            "outcome": "callback_rejected",
            "reason": verified["reason"],
            "callback_data_prefix": callback_data[:32],
        })
        return jsonify({"ok": False, "reason": verified["reason"]}), 401

    action = verified["action"]
    task_num = verified["task_num"]
    pr_num = verified["pr_num"]

    if _is_processed(task_num, pr_num, action):
        _telegram_api("answerCallbackQuery", {"callback_query_id": callback_id, "text": "이미 처리됨 (replay 차단)", "show_alert": True})
        return jsonify({"ok": False, "reason": "already_processed"}), 409

    if action == "a":
        result = _execute_approve(task_num, pr_num)
        outcome = "merged" if result.get("ok") else "merge_failed"
    elif action == "r":
        result = _execute_reject(task_num, pr_num)
        outcome = "rejected"
    elif action == "d":
        result = _execute_diff(task_num, pr_num)
        outcome = "diff_shown"
    else:
        result = {"ok": False, "error": "unknown_action"}
        outcome = "rejected"

    _mark_processed(task_num, pr_num, action, result)
    _audit_append({
        "task_id": f"task-{task_num}",
        "pr_number": pr_num,
        "tier": "tier2",
        "outcome": outcome,
        "action": action,
        "callback_result": {"ok": result.get("ok"), "stdout": result.get("stdout", "")[:200]},
    })

    user_msg = {
        "merged": "✅ 머지 완료",
        "merge_failed": f"❌ 머지 실패: {result.get('stderr', 'unknown')[:100]}",
        "rejected": "❌ Reject 처리 — PR에 코멘트 추가",
        "diff_shown": f"PR diff: {result.get('diff_url', '')}",
    }.get(outcome, "처리 완료")
    _telegram_api("answerCallbackQuery", {"callback_query_id": callback_id, "text": user_msg, "show_alert": False})

    return jsonify({"ok": True, "outcome": outcome, "action": action, "task_num": task_num, "pr_num": pr_num}), 200


def main() -> None:
    logger.info(f"anu-confirm-bot 시작: {config.LISTEN_HOST}:{config.LISTEN_PORT}, trigger={config.TRIGGER_MARKER}")
    app.run(host=config.LISTEN_HOST, port=config.LISTEN_PORT, debug=False)


if __name__ == "__main__":
    main()
