"""v3.6 Runtime Harness — Layer 4: Closeout Marker Watcher.

chair_authorization_id=CHAIR-AUTH-TASK-2704-V36-CONTROL-PLANE-P0-MVP-260528

Contract:
- scan_closeout_markers(events_dir) -> list[dict]
  Synchronous scan — NOT a daemon. Suitable for watchdog or finish-task hook.

- detect_closeout_state(task_id) -> dict
  Returns {"state": <6-state enum>, "markers_found": [...], "priority": int, "reason": str}

- 6 states: WORK_CLOSEOUT_STARTED, FINISH_IN_PROGRESS, CALLBACK_LAUNCHED,
            DONE, ANU_RECEIVED, CHAIR_REPORTED

- 5 closeout marker patterns:
  1. <task_id>.*active.json
  2. <task_id>.harness-mvp-active*
  3. <task_id>.done*
  4. <task_id>.callback-*
  5. <task_id>.completion.txt

- Recovery priority (encoded as priority field):
  normal_callback (1) > closeout_marker_watcher (2) > fallback_safety_net (3)

- signal_anu(task_id, state, priority): writes signal file to
  memory/events/<task_id>.anu-signal-<state>.json (no ANU spawn).
  ANU body picks up signal asynchronously.
"""
from __future__ import annotations

import glob
import json
import os
import time
from datetime import datetime, timezone
from typing import Optional

CHAIR_AUTHORIZATION_ID = "CHAIR-AUTH-TASK-2704-V36-CONTROL-PLANE-P0-MVP-260528"

# Closeout state enum
WORK_CLOSEOUT_STARTED = "WORK_CLOSEOUT_STARTED"
FINISH_IN_PROGRESS = "FINISH_IN_PROGRESS"
CALLBACK_LAUNCHED = "CALLBACK_LAUNCHED"
DONE = "DONE"
ANU_RECEIVED = "ANU_RECEIVED"
CHAIR_REPORTED = "CHAIR_REPORTED"

# State ordinal for progression checks
_CLOSEOUT_ORDER = [
    WORK_CLOSEOUT_STARTED,
    FINISH_IN_PROGRESS,
    CALLBACK_LAUNCHED,
    DONE,
    ANU_RECEIVED,
    CHAIR_REPORTED,
]
_CLOSEOUT_ORDINAL = {s: i for i, s in enumerate(_CLOSEOUT_ORDER)}

# Recovery priority values
PRIORITY_NORMAL_CALLBACK = 1
PRIORITY_CLOSEOUT_MARKER_WATCHER = 2
PRIORITY_FALLBACK_SAFETY_NET = 3

_WORKSPACE = "/home/jay/workspace"
_EVENTS_DIR = os.path.join(_WORKSPACE, "memory/events")


# ─── Public API ───────────────────────────────────────────────────────────────

def scan_closeout_markers(events_dir: str = _EVENTS_DIR) -> list:
    """Scan events_dir for all closeout markers across all tasks.

    Returns list of dicts: [{task_id, pattern_matched, marker_path, mtime_seconds_ago}]
    Never raises — returns [] on any error.
    """
    try:
        return _scan_closeout_markers_impl(events_dir)
    except Exception:
        return []


def detect_closeout_state(
    task_id: str,
    events_dir: Optional[str] = None,
) -> dict:
    """Detect closeout state for a specific task_id.

    Returns:
        {
            "state": str,           # 6-state enum
            "markers_found": list,  # matching marker paths
            "priority": int,        # recovery priority (1=normal_callback, 2=watcher, 3=fallback)
            "reason": str,          # decision reason
        }

    Never raises.
    """
    try:
        return _detect_closeout_state_impl(
            task_id=task_id,
            events_dir=events_dir or _EVENTS_DIR,
        )
    except Exception as exc:
        return {
            "state": WORK_CLOSEOUT_STARTED,
            "markers_found": [],
            "priority": PRIORITY_CLOSEOUT_MARKER_WATCHER,
            "reason": f"detect_closeout_state safe-fail: {exc}",
        }


def signal_anu(
    task_id: str,
    state: str,
    priority: int,
    events_dir: Optional[str] = None,
) -> Optional[dict]:
    """Write an ANU signal file to events_dir.

    Does NOT spawn ANU. ANU body picks up the signal asynchronously.
    Returns the signal dict on success, None on failure. Never raises.

    Signal file: memory/events/<task_id>.anu-signal-<state>.json
    """
    try:
        return _signal_anu_impl(
            task_id=task_id,
            state=state,
            priority=priority,
            events_dir=events_dir or _EVENTS_DIR,
        )
    except Exception:
        return None


# ─── task-2716: PR diff hygiene — 보조 결선(read-only detection, 차단 아님) ──────

def detect_pr_diff_contamination(
    changed_files: list,
    expected_files: Optional[list] = None,
) -> Optional[dict]:
    """watcher 보조 결선: PR diff 의 미선언 artifact 혼입을 *보고만* 한다(차단 X).

    주 차단 결선은 finish-task.sh pre-push 단계가 담당한다. 여기서는
    pr_diff_hygiene_guard.detect_contamination 을 재사용해 read-only 로 감지만
    수행하고, 결과 dict(detection_only=True)를 반환한다. 절대 raise 하지 않는다.
    """
    try:
        from pr_diff_hygiene_guard import detect_contamination  # type: ignore
    except ImportError:
        try:
            import importlib.util

            _p = os.path.join(os.path.dirname(__file__), "pr_diff_hygiene_guard.py")
            _spec = importlib.util.spec_from_file_location("pr_diff_hygiene_guard", _p)
            if _spec is None or _spec.loader is None:
                return None
            _mod = importlib.util.module_from_spec(_spec)
            _spec.loader.exec_module(_mod)
            detect_contamination = _mod.detect_contamination
        except Exception:
            return None
    try:
        return detect_contamination(changed_files, expected_files)
    except Exception:
        return None


# ─── Internal implementation ──────────────────────────────────────────────────

def _scan_closeout_markers_impl(events_dir: str) -> list:
    results = []
    now = time.time()

    if not os.path.isdir(events_dir):
        return results

    # Pattern 1: *.*active.json
    for path in glob.glob(os.path.join(events_dir, "*.*active.json")):
        task_id = _extract_task_id(path, ".*active.json")
        if task_id:
            results.append(_marker_entry(task_id, "active_json", path, now))

    # Pattern 2: *.harness-mvp-active*
    for path in glob.glob(os.path.join(events_dir, "*.harness-mvp-active*")):
        task_id = _extract_task_id_prefix(path, ".harness-mvp-active")
        if task_id:
            results.append(_marker_entry(task_id, "harness_mvp_active", path, now))

    # Pattern 3: *.done* (but not *.done.dispatched* etc. — done family only)
    for path in glob.glob(os.path.join(events_dir, "*.done*")):
        task_id = _extract_task_id_prefix(path, ".done")
        if task_id:
            results.append(_marker_entry(task_id, "done", path, now))

    # Pattern 4: *.callback-*
    for path in glob.glob(os.path.join(events_dir, "*.callback-*")):
        task_id = _extract_task_id_prefix(path, ".callback-")
        if task_id:
            results.append(_marker_entry(task_id, "callback", path, now))

    # Pattern 5: *.completion.txt
    for path in glob.glob(os.path.join(events_dir, "*.completion.txt")):
        task_id = _extract_task_id_prefix(path, ".completion.txt")
        if task_id:
            results.append(_marker_entry(task_id, "completion_txt", path, now))

    return results


def _detect_closeout_state_impl(task_id: str, events_dir: str) -> dict:
    now = time.time()
    markers_found = []

    # Check each of the 5 patterns
    active_json = glob.glob(os.path.join(events_dir, f"{task_id}.*active.json"))
    harness_mvp = glob.glob(os.path.join(events_dir, f"{task_id}.harness-mvp-active*"))
    done_files = glob.glob(os.path.join(events_dir, f"{task_id}.done*"))
    callback_files = glob.glob(os.path.join(events_dir, f"{task_id}.callback-*"))
    completion_txt = glob.glob(os.path.join(events_dir, f"{task_id}.completion.txt"))
    anu_signal_files = glob.glob(os.path.join(events_dir, f"{task_id}.anu-signal-*"))

    markers_found.extend(active_json)
    markers_found.extend(harness_mvp)

    # ── ANU_RECEIVED: ANU signal present ─────────────────────────────────────
    if anu_signal_files:
        # Check if the signal indicates CHAIR_REPORTED
        for sf in anu_signal_files:
            if "CHAIR_REPORTED" in sf:
                return _closeout_result(
                    CHAIR_REPORTED,
                    markers_found + anu_signal_files,
                    PRIORITY_NORMAL_CALLBACK,
                    "ANU signal CHAIR_REPORTED found",
                )
        return _closeout_result(
            ANU_RECEIVED,
            markers_found + anu_signal_files,
            PRIORITY_NORMAL_CALLBACK,
            "ANU received signal found",
        )

    # ── DONE: .done + task-timers completed ──────────────────────────────────
    done_marker_present = len(done_files) > 0
    notified_done = any("notified" in p or "anu-notified" in p for p in done_files)

    if done_marker_present:
        markers_found.extend(done_files)
        if notified_done or len(done_files) >= 2:
            return _closeout_result(
                DONE,
                markers_found,
                PRIORITY_NORMAL_CALLBACK,
                "done + notified markers present",
            )

    # ── CALLBACK_LAUNCHED: callback marker present ────────────────────────────
    if callback_files:
        markers_found.extend(callback_files)
        launch_files = [p for p in callback_files if "launch" in p]
        if launch_files:
            return _closeout_result(
                CALLBACK_LAUNCHED,
                markers_found,
                PRIORITY_NORMAL_CALLBACK,
                "callback-launch marker present",
            )

    # ── FINISH_IN_PROGRESS: completion.txt or callback markers without launch ─
    if completion_txt or callback_files:
        if completion_txt:
            markers_found.extend(completion_txt)
        if callback_files and not any("launch" in p for p in callback_files):
            markers_found.extend(callback_files)
        return _closeout_result(
            FINISH_IN_PROGRESS,
            markers_found,
            PRIORITY_CLOSEOUT_MARKER_WATCHER,
            "completion.txt or callback-cause markers — finish-task in progress",
        )

    # ── WORK_CLOSEOUT_STARTED: harness-mvp-active or *active.json found ───────
    if active_json or harness_mvp:
        return _closeout_result(
            WORK_CLOSEOUT_STARTED,
            markers_found,
            PRIORITY_CLOSEOUT_MARKER_WATCHER,
            "closeout marker (active.json or harness-mvp-active) found — work closeout started",
        )

    # No markers found — not in closeout
    return _closeout_result(
        WORK_CLOSEOUT_STARTED,
        [],
        PRIORITY_FALLBACK_SAFETY_NET,
        "no closeout markers found — awaiting first closeout marker",
    )


def _signal_anu_impl(
    task_id: str,
    state: str,
    priority: int,
    events_dir: str,
) -> dict:
    now_utc = datetime.now(timezone.utc)
    ts_iso = now_utc.isoformat()
    ts_file = now_utc.strftime("%Y%m%d%H%M%S")

    priority_name = {
        1: "normal_callback",
        2: "closeout_marker_watcher",
        3: "fallback_safety_net",
    }.get(priority, "unknown")

    signal = {
        "marker_version": "v1",
        "marker_type": "anu_signal",
        "task_id": task_id,
        "closeout_state": state,
        "priority": priority,
        "priority_name": priority_name,
        "signal_time": ts_iso,
        "chair_authorization_id": CHAIR_AUTHORIZATION_ID,
    }

    filename = f"{task_id}.anu-signal-{state}.json"
    signal_path = os.path.join(events_dir, filename)

    os.makedirs(events_dir, exist_ok=True)
    tmp_path = signal_path + ".tmp"
    try:
        with open(tmp_path, "w", encoding="utf-8") as fh:
            json.dump(signal, fh, ensure_ascii=False, indent=2)
        os.replace(tmp_path, signal_path)
    finally:
        if os.path.exists(tmp_path):
            try:
                os.unlink(tmp_path)
            except Exception:
                pass

    # Log the ANU signal event
    try:
        from scripts.harness.v36.runtime_decision_logger import log_runtime_event
        log_runtime_event(
            event_type="anu_signal_written",
            task_id=task_id,
            payload={
                "signal_path": signal_path,
                "closeout_state": state,
                "priority": priority,
            },
        )
    except Exception:
        pass

    return signal


# ─── Helper utilities ─────────────────────────────────────────────────────────

def _marker_entry(task_id: str, pattern_matched: str, path: str, now: float) -> dict:
    try:
        mtime_s = int(now - os.path.getmtime(path))
    except Exception:
        mtime_s = -1
    return {
        "task_id": task_id,
        "pattern_matched": pattern_matched,
        "marker_path": path,
        "mtime_seconds_ago": mtime_s,
    }


def _extract_task_id(path: str, suffix_pattern: str) -> Optional[str]:
    """Extract task_id from a path by stripping the suffix pattern."""
    try:
        basename = os.path.basename(path)
        # For patterns like .*active.json, strip from the last dot before "active"
        idx = basename.find(".") if "." in basename else -1
        if idx > 0:
            candidate = basename[:idx]
            # Basic validation: task-NNNN format
            if candidate.startswith("task-"):
                return candidate
        return None
    except Exception:
        return None


def _extract_task_id_prefix(path: str, separator: str) -> Optional[str]:
    """Extract task_id as prefix before separator in basename."""
    try:
        basename = os.path.basename(path)
        idx = basename.find(separator)
        if idx > 0:
            candidate = basename[:idx]
            if candidate.startswith("task-"):
                return candidate
        return None
    except Exception:
        return None


def _closeout_result(state: str, markers: list, priority: int, reason: str) -> dict:
    return {
        "state": state,
        "markers_found": markers,
        "priority": priority,
        "reason": reason,
    }
