"""Per-strategy daily entry ledger and run mutex (atomic JSON on disk)."""

from __future__ import annotations

import json
import time
from datetime import date, datetime
from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo

NY = ZoneInfo("America/New_York")


def _ledger_path(state_dir: Path, strategy_id: str, today: date) -> Path:
    return state_dir / f"entry_ledger_{strategy_id}_{today.isoformat()}.json"


def _lock_path(state_dir: Path, strategy_id: str) -> Path:
    return state_dir / f"run_lock_{strategy_id}.json"


def _atomic_write_json(path: Path, payload: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp = path.with_suffix(".tmp")
    tmp.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
    tmp.replace(path)


def _load_ledger(path: Path, strategy_id: str, today: date) -> dict[str, Any]:
    if not path.is_file():
        return {
            "date": today.isoformat(),
            "strategy_id": strategy_id,
            "entries_submitted": 0,
            "entries_reserved": 0,
            "runs": 0,
            "last_run_id": "",
            "entries": [],
        }
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        data = {}
    if not isinstance(data, dict) or str(data.get("date")) != today.isoformat():
        return {
            "date": today.isoformat(),
            "strategy_id": strategy_id,
            "entries_submitted": 0,
            "entries_reserved": 0,
            "runs": 0,
            "last_run_id": "",
            "entries": [],
        }
    data.setdefault("strategy_id", strategy_id)
    data.setdefault("entries_submitted", 0)
    data.setdefault("entries_reserved", 0)
    data.setdefault("runs", 0)
    data.setdefault("last_run_id", "")
    data.setdefault("entries", [])
    return data


def acquire_run_lock(
    state_dir: Path,
    strategy_id: str,
    run_id: str,
    *,
    ttl_sec: float = 300,
    poll_sec: float = 0.25,
) -> None:
    """Block until this run_id holds the strategy run lock (stale locks expire after ttl_sec)."""
    state_dir.mkdir(parents=True, exist_ok=True)
    lock_p = _lock_path(state_dir, strategy_id)
    now = time.time()
    while True:
        if lock_p.is_file():
            try:
                cur = json.loads(lock_p.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                cur = {}
            if isinstance(cur, dict):
                holder = str(cur.get("run_id", ""))
                ts = float(cur.get("ts", 0))
                if holder == run_id or (now - ts) >= ttl_sec:
                    break
            else:
                break
        else:
            break
        time.sleep(poll_sec)
        now = time.time()

    payload = {
        "strategy_id": strategy_id,
        "run_id": run_id,
        "ts": time.time(),
        "acquired_at": datetime.now(NY).isoformat(),
    }
    _atomic_write_json(lock_p, payload)
    print(f"[LEDGER] run lock acquired strategy={strategy_id} run_id={run_id}")


def release_run_lock(state_dir: Path, strategy_id: str, run_id: str) -> None:
    lock_p = _lock_path(state_dir, strategy_id)
    if not lock_p.is_file():
        return
    try:
        cur = json.loads(lock_p.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        cur = {}
    if isinstance(cur, dict) and str(cur.get("run_id", "")) == run_id:
        lock_p.unlink(missing_ok=True)
        print(f"[LEDGER] run lock released strategy={strategy_id} run_id={run_id}")


def touch_run_ledger(state_dir: Path, strategy_id: str, run_id: str, today: date | None = None) -> None:
    """Increment daily run count and set last_run_id (no entry slot consumed)."""
    today = today or date.today()
    path = _ledger_path(state_dir, strategy_id, today)
    ledger = _load_ledger(path, strategy_id, today)
    ledger["runs"] = int(ledger.get("runs", 0)) + 1
    ledger["last_run_id"] = run_id
    _atomic_write_json(path, ledger)


def check_and_reserve_entry(
    state_dir: Path,
    strategy_id: str,
    run_id: str,
    max_new_entries_per_day: int,
    *,
    dry_run: bool = False,
    today: date | None = None,
) -> tuple[bool, str]:
    """Reserve one entry slot for today if under the daily cap."""
    today = today or date.today()
    if max_new_entries_per_day <= 0:
        return False, "max_new_entries_per_day is zero"

    path = _ledger_path(state_dir, strategy_id, today)
    ledger = _load_ledger(path, strategy_id, today)
    submitted = int(ledger.get("entries_submitted", 0))
    reserved = int(ledger.get("entries_reserved", 0))
    used = submitted + reserved
    if used >= max_new_entries_per_day:
        return (
            False,
            f"daily entry cap reached ({used}/{max_new_entries_per_day} submitted+reserved)",
        )
    if dry_run:
        return True, "dry_run: slot available"

    ledger["entries_reserved"] = reserved + 1
    ledger["last_run_id"] = run_id
    _atomic_write_json(path, ledger)
    return True, "reserved"


def release_entry_reservation(
    state_dir: Path,
    strategy_id: str,
    *,
    today: date | None = None,
) -> None:
    """Return one reserved slot (e.g. failed order after reserve)."""
    today = today or date.today()
    path = _ledger_path(state_dir, strategy_id, today)
    ledger = _load_ledger(path, strategy_id, today)
    reserved = int(ledger.get("entries_reserved", 0))
    if reserved > 0:
        ledger["entries_reserved"] = reserved - 1
        _atomic_write_json(path, ledger)


def record_entry_submitted(
    state_dir: Path,
    strategy_id: str,
    run_id: str,
    *,
    today: date | None = None,
    **entry_fields: Any,
) -> None:
    """Commit one reserved slot to submitted (call after order sent)."""
    today = today or date.today()
    path = _ledger_path(state_dir, strategy_id, today)
    ledger = _load_ledger(path, strategy_id, today)
    reserved = int(ledger.get("entries_reserved", 0))
    if reserved > 0:
        ledger["entries_reserved"] = reserved - 1
    ledger["entries_submitted"] = int(ledger.get("entries_submitted", 0)) + 1
    ledger["last_run_id"] = run_id
    entries = ledger.get("entries")
    if not isinstance(entries, list):
        entries = []
    row: dict[str, Any] = {
        "run_id": run_id,
        "ts": datetime.now(NY).isoformat(),
    }
    row.update(entry_fields)
    entries.append(row)
    ledger["entries"] = entries
    _atomic_write_json(path, ledger)
    print(
        f"[LEDGER] entry recorded strategy={strategy_id} "
        f"submitted={ledger['entries_submitted']} run_id={run_id}"
    )


def count_entries_submitted_today(
    state_dir: Path,
    strategy_id: str,
    today: date | None = None,
) -> int:
    today = today or date.today()
    path = _ledger_path(state_dir, strategy_id, today)
    ledger = _load_ledger(path, strategy_id, today)
    return int(ledger.get("entries_submitted", 0))
