from __future__ import annotations

import fcntl
import json
import os
import time
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional

import config_manager
try:
    from src.execution_contract import apply_execution_contract_defaults
except ModuleNotFoundError:
    from execution_contract import apply_execution_contract_defaults


OPEN_CONFIRM_GRACE_SEC = 120
CLOSE_TRANSITION_GRACE_SEC = 60  # closed → flat auto-transition after 60s
AGENT_FLAT_RECONCILE_SEC = 30


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _data_dir(data_dir: Optional[Path] = None) -> Path:
    resolved = data_dir if data_dir is not None else config_manager.get_data_dir()
    resolved.mkdir(parents=True, exist_ok=True)
    return resolved


def lifecycle_path(data_dir: Optional[Path] = None) -> Path:
    return _data_dir(data_dir) / "position_lifecycle.json"


def _agent_state_path(data_dir: Optional[Path] = None) -> Path:
    return _data_dir(data_dir) / "bridge" / "state" / "agent_state.json"


def _lock_path(data_dir: Optional[Path] = None) -> Path:
    return _data_dir(data_dir) / "position_lifecycle.lock"


@contextmanager
def _lifecycle_lock(data_dir: Optional[Path] = None, *, shared: bool = False):
    lock_path = _lock_path(data_dir)
    lock_path.parent.mkdir(parents=True, exist_ok=True)
    handle = lock_path.open("a+", encoding="utf-8")
    try:
        fcntl.flock(handle.fileno(), fcntl.LOCK_SH if shared else fcntl.LOCK_EX)
        yield
    finally:
        try:
            fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
        except Exception:
            pass
        handle.close()


def _default_payload() -> dict[str, Any]:
    now_iso = _now_iso()
    return {
        "updated_at": now_iso,
        "current": {
            "status": "flat",
            "updated_at": now_iso,
        },
        "history": [],
    }


def _parse_iso_ts(raw: Any) -> float:
    text = str(raw or "").strip()
    if not text:
        return 0.0
    try:
        return datetime.fromisoformat(text.replace("Z", "+00:00")).timestamp()
    except Exception:
        return 0.0


def _sanitize_current(current: dict[str, Any], *, now_ts: float | None = None) -> dict[str, Any]:
    if not isinstance(current, dict):
        return _default_payload()["current"]
    normalized = dict(current)
    status = str(normalized.get("status", "flat") or "flat").strip().lower()
    if status not in {"open", "opening", "closing", "closed", "flat"}:
        status = "flat"
    normalized["status"] = status

    if status not in {"open", "opening"}:
        # Auto-transition closed → flat after grace period so the lifecycle
        # file doesn't permanently show a stale "closed" position.
        if status == "closed":
            now_value = float(now_ts if now_ts is not None else time.time())
            closed_at = _parse_iso_ts(normalized.get("closed_at", normalized.get("updated_at", "")))
            age_sec = max(0.0, now_value - closed_at) if closed_at > 0 else 0.0
            if age_sec > CLOSE_TRANSITION_GRACE_SEC:
                normalized.update(
                    {
                        "status": "flat",
                        "status_label": "空闲",
                        "side": "NONE",
                        "ticket": 0,
                        "volume": 0.0,
                        "volume_lots": 0.0,
                        "entry_price": 0.0,
                        "sl": 0.0,
                        "tp": 0.0,
                        "exit_price": 0.0,
                        "realized_pnl": 0.0,
                        "reason": "auto_transition_closed_to_flat",
                    }
                )
        return normalized

    now_value = float(now_ts if now_ts is not None else time.time())
    updated_ts = max(
        _parse_iso_ts(normalized.get("updated_at")),
        _parse_iso_ts(normalized.get("opened_at")),
    )
    age_sec = max(0.0, now_value - updated_ts) if updated_ts > 0 else OPEN_CONFIRM_GRACE_SEC + 1.0
    ticket = int(normalized.get("ticket", 0) or 0)
    entry_price = float(normalized.get("entry_price", 0.0) or 0.0)

    if status == "open":
        if ticket > 0 and entry_price > 0:
            return normalized
        if age_sec <= OPEN_CONFIRM_GRACE_SEC:
            normalized["status"] = "opening"
            normalized["status_label"] = "开仓中"
            normalized["reason"] = str(normalized.get("reason", "") or "waiting_fill_confirm")
            return normalized

    if ticket <= 0 and age_sec > OPEN_CONFIRM_GRACE_SEC:
        normalized.update(
            {
                "status": "flat",
                "status_label": "空闲",
                "side": "NONE",
                "ticket": 0,
                "volume": 0.0,
                "volume_lots": 0.0,
                "entry_price": 0.0,
                "sl": 0.0,
                "tp": 0.0,
                "reason": "stale_unconfirmed_open_recovered",
            }
        )
    return normalized


def _sanitize_payload(payload: dict[str, Any], *, now_ts: float | None = None) -> dict[str, Any]:
    normalized = dict(payload) if isinstance(payload, dict) else _default_payload()
    current = normalized.get("current", {})
    if not isinstance(current, dict):
        current = _default_payload()["current"]
    normalized["current"] = _sanitize_current(current, now_ts=now_ts)
    if not isinstance(normalized.get("history", []), list):
        normalized["history"] = []
    return normalized


def _read_position_lifecycle_unlocked(data_dir: Optional[Path] = None) -> dict[str, Any]:
    path = lifecycle_path(data_dir)
    payload = _default_payload()
    if not path.exists():
        return payload
    try:
        loaded = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return payload
    if not isinstance(loaded, dict):
        return payload
    payload.update(loaded)
    if not isinstance(payload.get("current", {}), dict):
        payload["current"] = _default_payload()["current"]
    if not isinstance(payload.get("history", []), list):
        payload["history"] = []
    return payload


def _read_agent_state_unlocked(data_dir: Optional[Path] = None) -> dict[str, Any]:
    path = _agent_state_path(data_dir)
    if not path.exists():
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return payload if isinstance(payload, dict) else {}


def _should_flatten_from_agent_state(
    current: dict[str, Any],
    agent_state: dict[str, Any],
    *,
    now_ts: float | None = None,
) -> tuple[bool, str]:
    if not isinstance(current, dict) or not isinstance(agent_state, dict):
        return False, ""
    status = str(current.get("status", "flat") or "flat").strip().lower()
    if status not in {"open", "opening", "closing"}:
        return False, ""
    if bool(agent_state.get("position_open", False)) or int(agent_state.get("position_ticket", 0) or 0) > 0:
        return False, ""

    now_value = float(now_ts if now_ts is not None else time.time())
    current_ts = max(
        _parse_iso_ts(current.get("updated_at")),
        _parse_iso_ts(current.get("opened_at")),
    )
    age_sec = max(0.0, now_value - current_ts) if current_ts > 0 else OPEN_CONFIRM_GRACE_SEC + 1.0
    last_close_ts = max(
        float(agent_state.get("last_close_time", 0.0) or 0.0),
        float(agent_state.get("last_closed_ts", 0.0) or 0.0),
    )

    if last_close_ts > 0 and current_ts > 0 and last_close_ts >= current_ts:
        return True, "agent_state_confirms_flat_after_close"
    if status in {"open", "closing"} and age_sec > AGENT_FLAT_RECONCILE_SEC:
        return True, "agent_state_confirms_flat"
    if status == "opening" and age_sec > OPEN_CONFIRM_GRACE_SEC:
        return True, "stale_opening_agent_flat"
    return False, ""


def _reconcile_payload(payload: dict[str, Any], *, data_dir: Optional[Path] = None, now_ts: float | None = None) -> tuple[dict[str, Any], bool]:
    normalized = _sanitize_payload(payload, now_ts=now_ts)
    changed = normalized != payload
    current = normalized.get("current", {})
    agent_state = _read_agent_state_unlocked(data_dir)
    should_flatten, reason = _should_flatten_from_agent_state(current, agent_state, now_ts=now_ts)
    if not should_flatten:
        return normalized, changed

    if isinstance(current, dict) and current:
        closed_snapshot = _merge_current(
            current,
            {
                "status": "closed",
                "status_label": "刚平仓",
                "closed_at": _now_iso(),
                "reason": reason,
                "source": "agent_state_reconcile",
            },
        )
        _append_history(normalized, closed_snapshot)
    normalized["current"] = {
        "status": "flat",
        "status_label": "空闲",
        "updated_at": _now_iso(),
        "reason": reason,
        "source": "agent_state_reconcile",
    }
    return normalized, True


def load_position_lifecycle(data_dir: Optional[Path] = None) -> dict[str, Any]:
    with _lifecycle_lock(data_dir, shared=True):
        payload = _read_position_lifecycle_unlocked(data_dir)
    reconciled, changed = _reconcile_payload(payload, data_dir=data_dir)
    if not changed:
        return reconciled
    with _lifecycle_lock(data_dir):
        latest = _read_position_lifecycle_unlocked(data_dir)
        latest_reconciled, latest_changed = _reconcile_payload(latest, data_dir=data_dir)
        if latest_changed:
            _write_position_lifecycle_unlocked(latest_reconciled, data_dir)
        return latest_reconciled


def reconcile_position_lifecycle(data_dir: Optional[Path] = None, *, now_ts: float | None = None) -> dict[str, Any]:
    with _lifecycle_lock(data_dir):
        payload = _read_position_lifecycle_unlocked(data_dir)
        reconciled, changed = _reconcile_payload(payload, data_dir=data_dir, now_ts=now_ts)
        if changed:
            _write_position_lifecycle_unlocked(reconciled, data_dir)
        return reconciled


def _write_position_lifecycle_unlocked(payload: dict[str, Any], data_dir: Optional[Path] = None) -> Path:
    path = lifecycle_path(data_dir)
    path.parent.mkdir(parents=True, exist_ok=True)
    payload["updated_at"] = _now_iso()
    tmp_path = path.with_name(f"{path.name}.{os.getpid()}.{time.time_ns()}.tmp")
    try:
        with open(tmp_path, "w", encoding="utf-8") as handle:
            handle.write(json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(tmp_path, path)
    finally:
        try:
            if tmp_path.exists():
                tmp_path.unlink()
        except Exception:
            pass
    return path


def save_position_lifecycle(payload: dict[str, Any], data_dir: Optional[Path] = None) -> Path:
    with _lifecycle_lock(data_dir):
        return _write_position_lifecycle_unlocked(payload, data_dir)


def _normalize_contributors(raw: Any) -> list[dict[str, Any]]:
    if not isinstance(raw, list):
        return []
    normalized: list[dict[str, Any]] = []
    for item in raw:
        if not isinstance(item, dict):
            continue
        normalized.append(
            {
                "instance_id": str(item.get("instance_id", "") or ""),
                "strategy_id": str(item.get("strategy_id", "") or ""),
                "action": str(item.get("action", "") or ""),
                "side": str(item.get("side", "") or ""),
                "weight": float(item.get("weight", 0.0) or 0.0),
                "confidence": float(item.get("confidence", 0.0) or 0.0),
                "signed_score": float(item.get("signed_score", 0.0) or 0.0),
                "reason": str(item.get("reason", "") or ""),
            }
        )
    return normalized


def normalize_position_attribution(
    attribution: Optional[dict[str, Any]],
    *,
    action: str = "",
    fallback: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    base = fallback if isinstance(fallback, dict) else {}
    payload = attribution if isinstance(attribution, dict) else {}
    merged = {**base, **payload}
    normalized_action = str(action or merged.get("action", "") or "").upper()

    selected_instance_id = str(
        merged.get("selected_instance_id")
        or merged.get("attributed_instance_id")
        or merged.get("instance_id")
        or ""
    ).strip()
    selected_strategy_id = str(
        merged.get("selected_strategy_id")
        or merged.get("attributed_strategy_id")
        or merged.get("strategy_id")
        or ""
    ).strip()

    entry_owner_instance_id = str(
        merged.get("entry_owner_instance_id")
        or base.get("entry_owner_instance_id", "")
        or selected_instance_id
    ).strip()
    entry_owner_strategy_id = str(
        merged.get("entry_owner_strategy_id")
        or base.get("entry_owner_strategy_id", "")
        or selected_strategy_id
    ).strip()

    current_manager_instance_id = str(
        merged.get("current_manager_instance_id")
        or base.get("current_manager_instance_id", "")
        or entry_owner_instance_id
    ).strip()
    current_manager_strategy_id = str(
        merged.get("current_manager_strategy_id")
        or base.get("current_manager_strategy_id", "")
        or entry_owner_strategy_id
    ).strip()

    expected_exit_owner_instance_id = str(
        merged.get("expected_exit_owner_instance_id")
        or base.get("expected_exit_owner_instance_id", "")
        or current_manager_instance_id
        or entry_owner_instance_id
    ).strip()
    expected_exit_owner_strategy_id = str(
        merged.get("expected_exit_owner_strategy_id")
        or base.get("expected_exit_owner_strategy_id", "")
        or current_manager_strategy_id
        or entry_owner_strategy_id
    ).strip()

    exit_owner_instance_id = str(
        merged.get("exit_owner_instance_id")
        or base.get("exit_owner_instance_id", "")
        or (
            selected_instance_id
            if normalized_action in {"CLOSE", "REDUCE"}
            else ""
        )
    ).strip()
    exit_owner_strategy_id = str(
        merged.get("exit_owner_strategy_id")
        or base.get("exit_owner_strategy_id", "")
        or (
            selected_strategy_id
            if normalized_action in {"CLOSE", "REDUCE"}
            else ""
        )
    ).strip()

    if normalized_action == "OPEN":
        current_manager_instance_id = current_manager_instance_id or entry_owner_instance_id
        current_manager_strategy_id = current_manager_strategy_id or entry_owner_strategy_id
        expected_exit_owner_instance_id = expected_exit_owner_instance_id or current_manager_instance_id
        expected_exit_owner_strategy_id = expected_exit_owner_strategy_id or current_manager_strategy_id
    elif normalized_action in {"CLOSE", "REDUCE"}:
        exit_owner_instance_id = exit_owner_instance_id or current_manager_instance_id or entry_owner_instance_id
        exit_owner_strategy_id = exit_owner_strategy_id or current_manager_strategy_id or entry_owner_strategy_id
        current_manager_instance_id = exit_owner_instance_id or current_manager_instance_id
        current_manager_strategy_id = exit_owner_strategy_id or current_manager_strategy_id

    normalized = {
        "decision_id": str(merged.get("decision_id", "") or ""),
        "signal_id": str(merged.get("signal_id", "") or ""),
        "trace_id": str(merged.get("trace_id", "") or ""),
        "strategy_id": selected_strategy_id or entry_owner_strategy_id,
        "instance_id": selected_instance_id or entry_owner_instance_id,
        "selected_instance_id": selected_instance_id,
        "selected_strategy_id": selected_strategy_id,
        "attributed_instance_id": str(merged.get("attributed_instance_id", selected_instance_id) or ""),
        "attributed_strategy_id": str(merged.get("attributed_strategy_id", selected_strategy_id) or ""),
        "entry_owner_instance_id": entry_owner_instance_id,
        "entry_owner_strategy_id": entry_owner_strategy_id,
        "current_manager_instance_id": current_manager_instance_id,
        "current_manager_strategy_id": current_manager_strategy_id,
        "expected_exit_owner_instance_id": expected_exit_owner_instance_id,
        "expected_exit_owner_strategy_id": expected_exit_owner_strategy_id,
        "exit_owner_instance_id": exit_owner_instance_id,
        "exit_owner_strategy_id": exit_owner_strategy_id,
        "regime": str(merged.get("regime", "") or ""),
        "agent_direction": str(merged.get("agent_direction", "") or ""),
        "session": merged.get("session", {}) if isinstance(merged.get("session", {}), dict) else {},
        "interpreter": merged.get("interpreter", {}) if isinstance(merged.get("interpreter", {}), dict) else {},
        "confidence": float(merged.get("confidence", 0.0) or 0.0),
        "action": normalized_action or str(merged.get("action", "") or "").upper(),
        "side": str(merged.get("side", "NONE") or "NONE").upper(),
        "symbol": str(merged.get("symbol", "XAUUSD") or "XAUUSD"),
        "volume_lots": float(merged.get("volume_lots", merged.get("volume", 0.0)) or 0.0),
    }
    apply_execution_contract_defaults(normalized, merged)
    normalized["contributors"] = _normalize_contributors(merged.get("contributors", []))
    normalized["risk_decision"] = (
        merged.get("risk_decision", {})
        if isinstance(merged.get("risk_decision", {}), dict)
        else {}
    )
    return normalized


def position_state_fields_from_attribution(
    attribution: Optional[dict[str, Any]],
    *,
    action: str = "",
    fallback: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
    normalized = normalize_position_attribution(attribution, action=action, fallback=fallback)
    return {
        "position_strategy_id": normalized.get("strategy_id", ""),
        "position_signal_id": normalized.get("signal_id", ""),
        "position_decision_id": normalized.get("decision_id", ""),
        "position_trace_id": normalized.get("trace_id", ""),
        "position_confidence": float(normalized.get("confidence", 0.0) or 0.0),
        "position_writer_owner": normalized.get("writer_owner", ""),
        "position_execution_owner_mode": normalized.get("execution_owner_mode", ""),
        "position_session_info": normalized.get("session", {}),
        "position_session_metrics": normalized.get("session", {}),
        "position_interpreter": normalized.get("interpreter", {}),
        "position_attribution": normalized,
        "position_agent_direction": normalized.get("agent_direction", ""),
        "position_entry_owner_instance_id": normalized.get("entry_owner_instance_id", ""),
        "position_entry_owner_strategy_id": normalized.get("entry_owner_strategy_id", ""),
        "position_current_manager_instance_id": normalized.get("current_manager_instance_id", ""),
        "position_current_manager_strategy_id": normalized.get("current_manager_strategy_id", ""),
        "position_expected_exit_owner_instance_id": normalized.get("expected_exit_owner_instance_id", ""),
        "position_expected_exit_owner_strategy_id": normalized.get("expected_exit_owner_strategy_id", ""),
        "position_exit_owner_instance_id": normalized.get("exit_owner_instance_id", ""),
        "position_exit_owner_strategy_id": normalized.get("exit_owner_strategy_id", ""),
        "position_contributors": normalized.get("contributors", []),
    }


def _merge_current(current: dict[str, Any], updates: dict[str, Any]) -> dict[str, Any]:
    merged = dict(current) if isinstance(current, dict) else {}
    for key, value in updates.items():
        if value is None:
            continue
        merged[key] = value
    merged["updated_at"] = _now_iso()
    return merged


def _append_history(payload: dict[str, Any], current: dict[str, Any]) -> None:
    history = payload.get("history", [])
    if not isinstance(history, list):
        history = []
    if current:
        history.append(current)
    payload["history"] = history[-50:]


def sync_open_request(
    attribution: Optional[dict[str, Any]],
    *,
    data_dir: Optional[Path] = None,
    reason: str = "",
) -> dict[str, Any]:
    with _lifecycle_lock(data_dir):
        payload = _read_position_lifecycle_unlocked(data_dir)
        normalized = normalize_position_attribution(attribution, action="OPEN")
        payload["current"] = _merge_current(
            payload.get("current", {}),
            {
                **normalized,
                "status": "opening",
                "status_label": "开仓中",
                "reason": str(reason or ""),
            },
        )
        _write_position_lifecycle_unlocked(payload, data_dir)
        return payload


def sync_open_fill(
    attribution: Optional[dict[str, Any]],
    *,
    ticket: int,
    side: str,
    volume: float,
    entry_price: float,
    sl: float = 0.0,
    tp: float = 0.0,
    symbol: str = "XAUUSD",
    data_dir: Optional[Path] = None,
    source: str = "",
) -> dict[str, Any]:
    with _lifecycle_lock(data_dir):
        payload = _read_position_lifecycle_unlocked(data_dir)
        fallback = payload.get("current", {}) if isinstance(payload.get("current", {}), dict) else {}
        normalized = normalize_position_attribution(attribution, action="OPEN", fallback=fallback)
        confirmed_ticket = int(ticket or 0)
        confirmed_entry = float(entry_price or 0.0)
        if confirmed_ticket <= 0 or confirmed_entry <= 0:
            payload["current"] = _merge_current(
                fallback,
                {
                    **normalized,
                    "status": "opening",
                    "status_label": "开仓中",
                    "ticket": 0,
                    "side": str(side or normalized.get("side", "NONE") or "NONE").upper(),
                    "volume": float(volume or normalized.get("volume_lots", 0.0) or 0.0),
                    "entry_price": max(0.0, confirmed_entry),
                    "sl": float(sl or 0.0),
                    "tp": float(tp or 0.0),
                    "symbol": str(symbol or normalized.get("symbol", "XAUUSD") or "XAUUSD"),
                    "reason": "waiting_fill_confirm",
                    "source": str(source or ""),
                },
            )
            _write_position_lifecycle_unlocked(payload, data_dir)
            return payload
        payload["current"] = _merge_current(
            fallback,
            {
                **normalized,
                "status": "open",
                "status_label": "持仓中",
                "ticket": confirmed_ticket,
                "side": str(side or normalized.get("side", "NONE") or "NONE").upper(),
                "volume": float(volume or normalized.get("volume_lots", 0.0) or 0.0),
                "entry_price": confirmed_entry,
                "sl": float(sl or 0.0),
                "tp": float(tp or 0.0),
                "symbol": str(symbol or normalized.get("symbol", "XAUUSD") or "XAUUSD"),
                "opened_at": fallback.get("opened_at") or _now_iso(),
                "source": str(source or ""),
            },
        )
        _write_position_lifecycle_unlocked(payload, data_dir)
        return payload


def sync_close_request(
    attribution: Optional[dict[str, Any]],
    *,
    ticket: int = 0,
    data_dir: Optional[Path] = None,
    reason: str = "",
) -> dict[str, Any]:
    with _lifecycle_lock(data_dir):
        payload = _read_position_lifecycle_unlocked(data_dir)
        fallback = payload.get("current", {}) if isinstance(payload.get("current", {}), dict) else {}
        normalized = normalize_position_attribution(attribution, action="CLOSE", fallback=fallback)
        payload["current"] = _merge_current(
            fallback,
            {
                **normalized,
                "status": "closing",
                "status_label": "平仓中",
                "ticket": int(ticket or fallback.get("ticket", 0) or 0),
                "reason": str(reason or ""),
            },
        )
        _write_position_lifecycle_unlocked(payload, data_dir)
        return payload


def sync_close_fill(
    attribution: Optional[dict[str, Any]],
    *,
    ticket: int = 0,
    exit_price: float = 0.0,
    pnl: float = 0.0,
    side: str = "",
    data_dir: Optional[Path] = None,
    reason: str = "",
    source: str = "",
) -> dict[str, Any]:
    with _lifecycle_lock(data_dir):
        payload = _read_position_lifecycle_unlocked(data_dir)
        fallback = payload.get("current", {}) if isinstance(payload.get("current", {}), dict) else {}
        normalized = normalize_position_attribution(attribution, action="CLOSE", fallback=fallback)
        closed_snapshot = _merge_current(
            fallback,
            {
                **normalized,
                "status": "closed",
                "status_label": "刚平仓",
                "ticket": int(ticket or fallback.get("ticket", 0) or 0),
                "side": str(side or fallback.get("side", normalized.get("side", "NONE")) or "NONE").upper(),
                "exit_price": float(exit_price or 0.0),
                "realized_pnl": float(pnl or 0.0),
                "closed_at": _now_iso(),
                "reason": str(reason or ""),
                "source": str(source or ""),
            },
        )
        _append_history(payload, closed_snapshot)
        payload["current"] = closed_snapshot
        _write_position_lifecycle_unlocked(payload, data_dir)
        return payload


def sync_flat_recovery(
    *,
    data_dir: Optional[Path] = None,
    reason: str = "",
    source: str = "",
) -> dict[str, Any]:
    with _lifecycle_lock(data_dir):
        payload = _read_position_lifecycle_unlocked(data_dir)
        fallback = payload.get("current", {}) if isinstance(payload.get("current", {}), dict) else {}
        current_status = str(fallback.get("status", "flat") or "flat").strip().lower()
        if current_status not in {"flat", "closed"} and fallback:
            closed_snapshot = _merge_current(
                fallback,
                {
                    "status": "closed",
                    "status_label": "刚平仓",
                    "closed_at": _now_iso(),
                    "reason": str(reason or "bridge_state_confirms_flat"),
                    "source": str(source or "runtime_reconcile"),
                },
            )
            _append_history(payload, closed_snapshot)
        payload["current"] = {
            "status": "flat",
            "status_label": "空闲",
            "updated_at": _now_iso(),
            "reason": str(reason or "bridge_state_confirms_flat"),
            "source": str(source or "runtime_reconcile"),
        }
        _write_position_lifecycle_unlocked(payload, data_dir)
        return payload
