#!/usr/bin/env python3
"""Circuit Breaker - hooks 자동 강제 시 과도한 에러 감지 및 halt.

상태 파일: /home/jay/workspace/.metrics/circuit_breaker_state.json

발동 조건:
- warning 누적 15회 → halt
- critical 누적 30회 → halt
- 동일 (tool, error_code, file_path) 3-tuple 연속 3회 → halt

CLI:
    python3 circuit_breaker.py check <file_path>
    python3 circuit_breaker.py record <severity> <file_path> <error_code>
    python3 circuit_breaker.py reset
"""

import json
import os
import sys
import tempfile
from datetime import datetime, timedelta, timezone

KST = timezone(timedelta(hours=9))

STATE_PATH = os.environ.get(
    "CIRCUIT_BREAKER_STATE",
    "/home/jay/workspace/.metrics/circuit_breaker_state.json",
)

WARNING_THRESHOLD = 15
CRITICAL_THRESHOLD = 30
CONSECUTIVE_THRESHOLD = 3


def _load_state(state_path: str) -> dict:
    """상태 파일 로드. 없으면 초기 상태 반환."""
    try:
        with open(state_path, "r", encoding="utf-8") as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {
            "schema_version": "1.0",
            "updated_at": datetime.now(KST).isoformat(),
            "warning_count": 0,
            "critical_count": 0,
            "recent_events": [],
            "halted": False,
            "halt_reason": None,
        }


def _save_state(state: dict, state_path: str) -> None:
    """atomic write로 상태 저장."""
    state["updated_at"] = datetime.now(KST).isoformat()
    dir_path = os.path.dirname(state_path)
    os.makedirs(dir_path, exist_ok=True)
    with tempfile.NamedTemporaryFile(mode="w", dir=dir_path, delete=False, suffix=".tmp") as f:
        json.dump(state, f, indent=2, ensure_ascii=False)
        tmp_path = f.name
    os.replace(tmp_path, state_path)


def _check_consecutive_halt(recent_events: list, new_event: dict) -> bool:
    """동일 (tool, error_code, file_path) 3-tuple이 연속 3회이면 True."""
    key = (new_event.get("tool", "Write"), new_event["error_code"], new_event["file_path"])
    consecutive = 0
    for event in reversed(recent_events[-3:]):
        if (event.get("tool", "Write"), event["error_code"], event["file_path"]) == key:
            consecutive += 1
        else:
            break
    return consecutive >= 2  # 기존 2회 + 신규 1회 = 3회


def cmd_check(file_path: str, state_path: str = STATE_PATH) -> str:  # noqa: ARG001
    """현재 상태에서 실행 허용 여부 확인. file_path는 CLI 인터페이스 호환용."""
    state = _load_state(state_path)
    if state.get("halted", False):
        return "halt"
    return "ok"


def cmd_record(
    severity: str,
    file_path: str,
    error_code: str,
    tool: str = "Write",
    state_path: str = STATE_PATH,
) -> str:
    """이벤트 기록 + halt 조건 검사."""
    state = _load_state(state_path)

    new_event = {
        "tool": tool,
        "error_code": error_code,
        "file_path": file_path,
        "severity": severity,
        "timestamp": datetime.now(KST).isoformat(),
    }

    # 카운트 증가
    if severity == "critical":
        state["critical_count"] = state.get("critical_count", 0) + 1
    else:
        state["warning_count"] = state.get("warning_count", 0) + 1

    # 3-tuple 연속 감지
    if _check_consecutive_halt(state.get("recent_events", []), new_event):
        state["halted"] = True
        state["halt_reason"] = f"consecutive_3tuple: ({tool}, {error_code}, {file_path})"

    # 임계값 검사
    if state.get("warning_count", 0) >= WARNING_THRESHOLD:
        state["halted"] = True
        state["halt_reason"] = f"warning_threshold: {state['warning_count']} >= {WARNING_THRESHOLD}"

    if state.get("critical_count", 0) >= CRITICAL_THRESHOLD:
        state["halted"] = True
        state["halt_reason"] = f"critical_threshold: {state['critical_count']} >= {CRITICAL_THRESHOLD}"

    # 이벤트 추가 (최근 100개만 유지)
    events = state.get("recent_events", [])
    events.append(new_event)
    state["recent_events"] = events[-100:]

    _save_state(state, state_path)

    return "halt" if state.get("halted") else "ok"


def cmd_reset(state_path: str = STATE_PATH) -> str:
    """수동 리셋."""
    state = {
        "schema_version": "1.0",
        "updated_at": datetime.now(KST).isoformat(),
        "warning_count": 0,
        "critical_count": 0,
        "recent_events": [],
        "halted": False,
        "halt_reason": None,
    }
    _save_state(state, state_path)
    return "ok"


def main() -> None:
    if len(sys.argv) < 2:
        print("Usage: circuit_breaker.py <check|record|reset> [args...]", file=sys.stderr)
        sys.exit(1)

    command = sys.argv[1]

    if command == "check":
        if len(sys.argv) < 3:
            print("Usage: circuit_breaker.py check <file_path>", file=sys.stderr)
            sys.exit(1)
        result = cmd_check(sys.argv[2])
        if result == "halt":
            sys.exit(2)
        sys.exit(0)

    elif command == "record":
        if len(sys.argv) < 5:
            print("Usage: circuit_breaker.py record <severity> <file_path> <error_code>", file=sys.stderr)
            sys.exit(1)
        result = cmd_record(
            severity=sys.argv[2],
            file_path=sys.argv[3],
            error_code=sys.argv[4],
            tool=sys.argv[5] if len(sys.argv) > 5 else "Write",
        )
        if result == "halt":
            sys.exit(2)
        sys.exit(0)

    elif command == "reset":
        cmd_reset()
        print("Circuit breaker reset.")
        sys.exit(0)

    else:
        print(f"Unknown command: {command}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
