"""TASK2750 — check_cron_fire_outcome.py regression.

cron fire outcome을 executable로 판정(사람 로그해석 대체). 7 enum + tz + raw key 0.
★ r2: NOW를 explicit KST aware로 고정, KST/UTC 해석 + schedule_id exact matching 테스트 추가.
"""
import json
import os
import sys
import tempfile
from datetime import datetime, timezone, timedelta

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "scripts"))
from check_cron_fire_outcome import classify, OUTCOMES, _parse_fire_at, _id_match  # noqa: E402

KST = timezone(timedelta(hours=9))
# ★ explicit KST aware → epoch (서버 로컬 tz 추정 제거, 다른 환경에서도 동일)
NOW = datetime(2026, 6, 14, 23, 55, 0, tzinfo=KST).timestamp()


def _env(tmp):
    store = os.path.join(tmp, "schedule")
    hist = os.path.join(tmp, "history")
    ws = os.path.join(tmp, "workspace")
    for d in (store, hist, ws):
        os.makedirs(d, exist_ok=True)
    clog = os.path.join(tmp, "cron.log")
    open(clog, "w").close()
    return store, hist, ws, clog


def _sched(store, sid, at, chat=6937032012):
    open(os.path.join(store, sid + ".json"), "w").write(
        json.dumps({"id": sid, "chat_id": chat, "schedule": at, "once": True,
                    "bot_key_verifier": "DEADBEEF_HASH_NOT_RAW_KEY"}))


def _classify(sid, **kw):
    return classify(schedule_id=sid, chat_id=6937032012, now=NOW, **kw)


def test_should_execute_plus_chat_busy_is_busy_skip():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T1", "2026-06-14 23:52:32")
        open(clog, "w").write("[scheduler_loop] id=T1, should execute, is_busy=true\n"
                              "[scheduler_loop] id=T1, chat busy, already pending -> skip\n")
        r = _classify("T1", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "BUSY_SKIP_ALREADY_AWAKE", r


def test_fire_record_plus_spawn_is_fired_and_spawned():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T2", "2026-06-14 23:52:32")
        open(os.path.join(hist, "T2.log"), "w").write(
            json.dumps({"ts": "2026-06-14T23:53:00+09:00", "status": "ok", "chat_id": 6937032012}) + "\n")
        os.makedirs(os.path.join(ws, "T2"))
        r = _classify("T2", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "FIRED_AND_SPAWNED", r


def test_fire_record_no_spawn_is_fired_no_spawn_proof():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T3", "2026-06-14 23:52:32")
        open(os.path.join(hist, "T3.log"), "w").write(
            json.dumps({"ts": "2026-06-14T23:53:00+09:00", "status": "ok", "chat_id": 6937032012}) + "\n")
        r = _classify("T3", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "FIRED_NO_SPAWN_PROOF", r


def test_before_due_is_pending_not_due():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T4", "2026-06-15 10:00:00")
        r = _classify("T4", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "PENDING_NOT_DUE", r


def test_due_no_record_is_due_but_not_fired():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T5", "2026-06-14 23:52:32")
        r = _classify("T5", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "DUE_BUT_NOT_FIRED", r


def test_removed_plus_no_proof_is_removed_before_fire():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        r = _classify("T6gone", expected_fire_at="2026-06-14 23:52:32",
                      store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "REMOVED_BEFORE_FIRE", r


def test_timezone_kst_naive_due_consistent():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T7", "2026-06-14 23:52:32")
        r1 = _classify("T7", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        r2 = _classify("T7", expected_fire_at="2026-06-14 23:52:32",
                       store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r1["outcome"] == r2["outcome"] == "DUE_BUT_NOT_FIRED", (r1, r2)


def test_no_raw_key_or_verifier_in_output():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "T8", "2026-06-14 23:52:32")
        r = _classify("T8", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        blob = json.dumps(r)
        assert "bot_key_verifier" not in blob and "DEADBEEF" not in blob, "verifier/key leaked"
        assert "c119085" not in blob, "raw ANU key leaked"


def test_outcome_always_in_enum():
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        r = _classify("NONEXISTENT", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] in OUTCOMES, r


# ── r2 견고성 테스트 (Gemini findings) ───────────────────────────────────────

def test_kst_naive_parse_is_aware_and_tz_independent():
    """naive 'YYYY-MM-DD HH:MM:SS'를 KST aware로 해석 — 서버 tz와 무관하게 고정 epoch."""
    got = _parse_fire_at("2026-06-14 23:52:32")
    expect = datetime(2026, 6, 14, 23, 52, 32, tzinfo=KST).timestamp()
    assert got == expect, (got, expect)


def test_utc_offset_parse_differs_from_kst_by_9h():
    """ISO offset 명시(+00:00)는 KST(+09:00)와 정확히 9시간(32400s) 차이."""
    kst = _parse_fire_at("2026-06-14 23:52:32")           # naive → KST
    utc = _parse_fire_at("2026-06-14T23:52:32+00:00")     # UTC aware
    assert utc - kst == 9 * 3600, (utc, kst, utc - kst)


def test_z_suffix_parsed_as_utc():
    z = _parse_fire_at("2026-06-14T23:52:32Z")
    utc = _parse_fire_at("2026-06-14T23:52:32+00:00")
    assert z == utc, (z, utc)


def test_schedule_id_exact_match_no_substring_false_positive():
    """sid가 다른 토큰의 부분문자열이어도 false match 안 됨(word-boundary)."""
    # 'C7B5018F'가 'XC7B5018FY'의 substring이지만 exact id가 아님
    assert _id_match("[scheduler_loop] id=C7B5018F, should execute", "C7B5018F") is True
    assert _id_match("[loop] id=XC7B5018FY, should execute", "C7B5018F") is False
    assert _id_match("path=/home/x/.cokacdir/schedule/C7B5018F.json", "C7B5018F") is True
    assert _id_match("nothing here", "C7B5018F") is False


def test_substring_false_positive_does_not_classify_busy_skip():
    """다른 id(C7B5018FEXTRA)의 should execute/chat busy 로그가 C7B5018F로 오판되지 않음."""
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)
        _sched(store, "C7B5018F", "2026-06-14 23:52:32")
        # 다른 id의 busy skip 로그만 존재 → C7B5018F는 BUSY_SKIP 아님
        open(clog, "w").write("[scheduler_loop] id=C7B5018FEXTRA, should execute, is_busy=true\n"
                              "[scheduler_loop] id=C7B5018FEXTRA, chat busy, already pending -> skip\n")
        r = _classify("C7B5018F", store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "DUE_BUT_NOT_FIRED", r  # busy skip 오판 아님
        assert r["evidence"]["cron_log_appears"] is False, r


def test_c7b5018f_realcase_fixture_still_busy_skip():
    """C7B5018F 실제 로그 패턴(due + busy skip + removed) → BUSY_SKIP_ALREADY_AWAKE 유지."""
    with tempfile.TemporaryDirectory() as t:
        store, hist, ws, clog = _env(t)  # schedule 제거 상태(removed)
        open(clog, "w").write(
            "[read_schedule_entry] result: id=C7B5018F, type=absolute, schedule=2026-06-14 23:52:32, last_run=None\n"
            "[scheduler_loop] id=C7B5018F, should execute, is_busy=true\n"
            "[scheduler_loop] id=C7B5018F, chat busy, already pending -> skip\n")
        r = classify(schedule_id="C7B5018F", chat_id=6937032012,
                     expected_fire_at="2026-06-14 23:52:32", now=NOW,
                     store=store, history_dir=hist, workspace_root=ws, cron_log=clog)
        assert r["outcome"] == "BUSY_SKIP_ALREADY_AWAKE", r
