import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional


VALID_STATES = ["CREATED", "VALIDATED", "SENT", "ACKED", "FILLED", "REJECTED", "TIMEOUT", "RECONCILED"]
FINAL_STATES = {"FILLED", "REJECTED", "RECONCILED"}


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


def _load_state(path: Path) -> Dict[str, Any]:
    if not path.exists():
        return {"signals": {}}
    return json.loads(path.read_text(encoding="utf-8"))


def _save_state(path: Path, state: Dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")


def create_or_get_signal_state(path: Path, signal_id: str) -> Dict[str, Any]:
    state = _load_state(path)
    signals = state.setdefault("signals", {})
    if signal_id not in signals:
        signals[signal_id] = {
            "signal_id": signal_id,
            "state": "CREATED",
            "history": [{"state": "CREATED", "ts": _now()}],
        }
        _save_state(path, state)
    return signals[signal_id]


def get_signal_state(path: Path, signal_id: str) -> Optional[Dict[str, Any]]:
    state = _load_state(path)
    return state.get("signals", {}).get(signal_id)


def is_final_state(path: Path, signal_id: str) -> bool:
    entry = get_signal_state(path, signal_id)
    if not entry:
        return False
    return str(entry.get("state", "")).upper() in FINAL_STATES


def transition_state(
    path: Path,
    signal_id: str,
    new_state: str,
    reason: Optional[str] = None,
    metadata: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
    if new_state not in VALID_STATES:
        raise ValueError("INVALID_STATE")
    state = _load_state(path)
    signals = state.setdefault("signals", {})
    entry = signals.setdefault(
        signal_id,
        {"signal_id": signal_id, "state": "CREATED", "history": [{"state": "CREATED", "ts": _now()}]},
    )
    entry["state"] = new_state
    event = {"state": new_state, "ts": _now()}
    if reason:
        event["reason"] = reason
    if isinstance(metadata, dict) and metadata:
        event["metadata"] = metadata
    entry.setdefault("history", []).append(event)
    _save_state(path, state)
    return entry


def transition_states(path: Path, signal_id: str, transitions: list[Dict[str, Any]]) -> Dict[str, Any]:
    entry: Optional[Dict[str, Any]] = None
    for item in transitions:
        if not isinstance(item, dict):
            continue
        new_state = str(item.get("state", "") or "").upper()
        if not new_state:
            continue
        entry = transition_state(
            path,
            signal_id,
            new_state,
            reason=str(item.get("reason", "") or "") or None,
            metadata=item.get("metadata", {}) if isinstance(item.get("metadata", {}), dict) else None,
        )
    return entry or create_or_get_signal_state(path, signal_id)


def _report_owned_by_pipeline(report: Dict[str, Any]) -> bool:
    if str(report.get("writer_owner", "") or "").lower() == "pipeline":
        return True
    if str(report.get("request_origin", "") or "").lower() == "pipeline_dispatch":
        return True
    attribution = report.get("attribution", {}) if isinstance(report.get("attribution", {}), dict) else {}
    if str(attribution.get("writer_owner", "") or "").lower() == "pipeline":
        return True
    return False


def _report_ticket(report: Dict[str, Any]) -> int:
    for key in ("ticket", "deal_id", "order_id"):
        value = report.get(key, 0)
        try:
            ticket = int(value or 0)
        except (TypeError, ValueError):
            continue
        if ticket > 0:
            return ticket
    return 0


def apply_execution_report(path: Path, signal_id: str, report: Dict[str, Any]) -> Dict[str, Any]:
    status = str(report.get("status", "")).upper()
    if status == "ACKED":
        return transition_state(path, signal_id, "ACKED")
    if status == "FILLED":
        ticket = _report_ticket(report)
        if ticket <= 0:
            if _report_owned_by_pipeline(report):
                return transition_state(path, signal_id, "RECONCILED",
                                        reason="FAKE_FILL_PIPELINE_OWNED_TICKET_ZERO")
            return transition_state(path, signal_id, "RECONCILED",
                                    reason="FILLED_WITH_INVALID_TICKET")
        return transition_state(path, signal_id, "FILLED")
    if status == "REJECTED":
        return transition_state(path, signal_id, "REJECTED", reason=report.get("reason"))
    if status == "TIMEOUT":
        return transition_state(path, signal_id, "TIMEOUT")
    return transition_state(path, signal_id, "RECONCILED", reason="UNKNOWN_REPORT_STATUS")


def reconcile_open_signals(path: Path) -> Dict[str, Any]:
    state = _load_state(path)
    changed = []
    for signal_id, entry in state.get("signals", {}).items():
        if entry.get("state") in {"SENT", "TIMEOUT"}:
            entry["state"] = "RECONCILED"
            entry.setdefault("history", []).append({"state": "RECONCILED", "ts": _now(), "reason": "AUTO_RECONCILE"})
            changed.append(signal_id)
    _save_state(path, state)
    return {"reconciled_signals": changed}
