from __future__ import annotations

import json
import os
import threading
import time
import uuid
from dataclasses import asdict, dataclass, field
from typing import Any, Dict, List, Optional

from oasyce.services.runtime_identity import normalize_runtime_actor


def _now() -> int:
    return int(time.time())


def _float(value: Any, default: float = 0.0) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


def _round_amount(value: float) -> float:
    return round(float(value or 0.0), 6)


def _contribution_bucket(actor: Dict[str, Any], account_id: str) -> Dict[str, str]:
    principal = dict(actor.get("principal") or {})
    delegate = dict(actor.get("delegate") or {})
    session = dict(actor.get("session") or {})
    principal_id = str(principal.get("id") or account_id or "").strip()
    delegate_id = str(delegate.get("id") or actor.get("device_id") or "").strip()
    agent_id = str(session.get("agent_id") or actor.get("agent_id") or "").strip()
    return {
        "principal_id": principal_id,
        "delegate_id": delegate_id,
        "agent_id": agent_id,
    }


def _init_contribution_bucket(*, principal_id: str, delegate_id: str = "") -> Dict[str, Any]:
    return {
        "principal_id": principal_id,
        "delegate_id": delegate_id,
        "executions": 0,
        "succeeded": 0,
        "failed": 0,
        "spent_oas": 0.0,
        "earned_oas": 0.0,
        "net_oas": 0.0,
        "assets_created": 0,
        "purchases": 0,
        "last_timestamp": 0,
        "agent_counts": {},
    }


def _apply_contribution(
    bucket: Dict[str, Any], entry: PortfolioLedgerEntry, *, agent_id: str = ""
) -> None:
    bucket["executions"] += 1
    bucket["spent_oas"] += entry.debit_oas
    bucket["earned_oas"] += entry.credit_oas
    bucket["net_oas"] += entry.net_oas
    bucket["last_timestamp"] = max(
        int(bucket.get("last_timestamp") or 0), int(entry.timestamp or 0)
    )
    if entry.status == "succeeded":
        bucket["succeeded"] += 1
        if entry.action == "register":
            bucket["assets_created"] += 1
        elif entry.action == "purchase":
            bucket["purchases"] += 1
    elif entry.status == "failed":
        bucket["failed"] += 1
    if agent_id:
        counts = dict(bucket.get("agent_counts") or {})
        counts[agent_id] = int(counts.get(agent_id) or 0) + 1
        bucket["agent_counts"] = counts


def _finalize_contributions(buckets: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
    rows: List[Dict[str, Any]] = []
    for bucket in buckets.values():
        rows.append(
            {
                **bucket,
                "spent_oas": _round_amount(bucket.get("spent_oas", 0.0)),
                "earned_oas": _round_amount(bucket.get("earned_oas", 0.0)),
                "net_oas": _round_amount(bucket.get("net_oas", 0.0)),
                "agent_counts": dict(
                    sorted(
                        (bucket.get("agent_counts") or {}).items(),
                        key=lambda item: (-int(item[1]), str(item[0])),
                    )
                ),
            }
        )
    rows.sort(
        key=lambda row: (
            -int(row.get("executions") or 0),
            -int(row.get("last_timestamp") or 0),
            str(row.get("delegate_id") or row.get("principal_id") or ""),
        )
    )
    return rows


@dataclass
class PortfolioLedgerEntry:
    entry_id: str
    timestamp: int
    action: str
    status: str
    item_id: str
    account_id: str = ""
    asset_id: Optional[str] = None
    file_path: Optional[str] = None
    debit_oas: float = 0.0
    credit_oas: float = 0.0
    net_oas: float = 0.0
    actor: Dict[str, Any] = field(default_factory=dict)
    price_model: Optional[str] = None
    asset_class: Optional[str] = None
    portfolio_policy_reason: Optional[str] = None
    error: Optional[str] = None


class PortfolioLedgerStore:
    """Local operating ledger for economic execution outcomes.

    This is intentionally not a second settlement system. It only records
    product-layer operating outcomes so the AI/runtime can reason about what
    it actually did, what it spent, and what it created.
    """

    def __init__(self, data_dir: Optional[str] = None) -> None:
        if data_dir is None:
            data_dir = os.path.join(os.path.expanduser("~"), ".oasyce")
        self._data_dir = data_dir
        self._path = os.path.join(data_dir, "portfolio_ledger.json")
        self._lock = threading.Lock()
        self._entries: List[PortfolioLedgerEntry] = []
        self._load()

    def record(
        self,
        *,
        action: str,
        status: str,
        item_id: str,
        account_id: str = "",
        asset_id: Optional[str] = None,
        file_path: Optional[str] = None,
        debit_oas: float = 0.0,
        credit_oas: float = 0.0,
        actor: Optional[Dict[str, Any]] = None,
        price_model: Optional[str] = None,
        asset_class: Optional[str] = None,
        portfolio_policy_reason: Optional[str] = None,
        error: Optional[str] = None,
        timestamp: Optional[int] = None,
    ) -> PortfolioLedgerEntry:
        actor_trace = normalize_runtime_actor(actor) or {}
        debit = _round_amount(debit_oas)
        credit = _round_amount(credit_oas)
        entry = PortfolioLedgerEntry(
            entry_id=str(uuid.uuid4())[:12],
            timestamp=int(timestamp or _now()),
            action=str(action or "").strip() or "unknown",
            status=str(status or "").strip() or "unknown",
            item_id=str(item_id or "").strip(),
            account_id=str(account_id or "").strip(),
            asset_id=str(asset_id or "").strip() or None,
            file_path=str(file_path or "").strip() or None,
            debit_oas=debit,
            credit_oas=credit,
            net_oas=_round_amount(credit - debit),
            actor=actor_trace,
            price_model=str(price_model or "").strip() or None,
            asset_class=str(asset_class or "").strip() or None,
            portfolio_policy_reason=str(portfolio_policy_reason or "").strip() or None,
            error=str(error or "").strip() or None,
        )
        with self._lock:
            self._entries.append(entry)
            self._save_locked()
        return PortfolioLedgerEntry(**asdict(entry))

    def summary(self, recent_limit: int = 8) -> Dict[str, Any]:
        with self._lock:
            entries = list(self._entries)
        totals = {
            "entries": len(entries),
            "succeeded": 0,
            "failed": 0,
            "assets_created": 0,
            "purchases": 0,
            "spent_oas": 0.0,
            "earned_oas": 0.0,
            "net_oas": 0.0,
        }
        action_counts: Dict[str, int] = {}
        status_counts: Dict[str, int] = {}
        principal_buckets: Dict[str, Dict[str, Any]] = {}
        delegate_buckets: Dict[str, Dict[str, Any]] = {}
        for entry in entries:
            totals["spent_oas"] += entry.debit_oas
            totals["earned_oas"] += entry.credit_oas
            totals["net_oas"] += entry.net_oas
            action_counts[entry.action] = action_counts.get(entry.action, 0) + 1
            status_counts[entry.status] = status_counts.get(entry.status, 0) + 1
            actor_trace = normalize_runtime_actor(entry.actor) or {}
            contribution = _contribution_bucket(actor_trace, entry.account_id)
            principal_id = contribution["principal_id"]
            delegate_id = contribution["delegate_id"]
            agent_id = contribution["agent_id"]
            if principal_id:
                principal_bucket = principal_buckets.setdefault(
                    principal_id,
                    _init_contribution_bucket(principal_id=principal_id),
                )
                _apply_contribution(principal_bucket, entry, agent_id=agent_id)
            if principal_id and delegate_id:
                delegate_key = f"{principal_id}:{delegate_id}"
                delegate_bucket = delegate_buckets.setdefault(
                    delegate_key,
                    _init_contribution_bucket(principal_id=principal_id, delegate_id=delegate_id),
                )
                _apply_contribution(delegate_bucket, entry, agent_id=agent_id)
            if entry.status == "succeeded":
                totals["succeeded"] += 1
                if entry.action == "register":
                    totals["assets_created"] += 1
                elif entry.action == "purchase":
                    totals["purchases"] += 1
            elif entry.status == "failed":
                totals["failed"] += 1
        totals["spent_oas"] = _round_amount(totals["spent_oas"])
        totals["earned_oas"] = _round_amount(totals["earned_oas"])
        totals["net_oas"] = _round_amount(totals["net_oas"])
        recent = sorted(entries, key=lambda item: item.timestamp, reverse=True)[:recent_limit]
        return {
            "totals": totals,
            "action_counts": action_counts,
            "status_counts": status_counts,
            "contributions": {
                "principals": _finalize_contributions(principal_buckets),
                "delegates": _finalize_contributions(delegate_buckets),
            },
            "recent": [
                {
                    "entry_id": entry.entry_id,
                    "timestamp": entry.timestamp,
                    "action": entry.action,
                    "status": entry.status,
                    "item_id": entry.item_id,
                    "account_id": entry.account_id,
                    "asset_id": entry.asset_id,
                    "file_path": entry.file_path,
                    "debit_oas": entry.debit_oas,
                    "credit_oas": entry.credit_oas,
                    "net_oas": entry.net_oas,
                    "price_model": entry.price_model,
                    "asset_class": entry.asset_class,
                    "portfolio_policy_reason": entry.portfolio_policy_reason,
                    "actor": normalize_runtime_actor(entry.actor),
                    "error": entry.error,
                }
                for entry in recent
            ],
        }

    def _save_locked(self) -> None:
        os.makedirs(self._data_dir, exist_ok=True)
        payload = [asdict(entry) for entry in self._entries]
        tmp = self._path + ".tmp"
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(payload, f, indent=2, ensure_ascii=False)
            f.flush()
            os.fsync(f.fileno())
        os.replace(tmp, self._path)

    def _load(self) -> None:
        if not os.path.exists(self._path):
            return
        try:
            with open(self._path, encoding="utf-8") as f:
                payload = json.load(f)
            if not isinstance(payload, list):
                return
            for value in payload:
                if not isinstance(value, dict):
                    continue
                self._entries.append(PortfolioLedgerEntry(**value))
        except (OSError, json.JSONDecodeError, TypeError, ValueError):
            corrupt = self._path + ".corrupt"
            try:
                os.replace(self._path, corrupt)
            except OSError:
                pass


_STORE: Dict[str, PortfolioLedgerStore] = {}
_STORE_LOCK = threading.Lock()


def get_portfolio_ledger_store(data_dir: Optional[str] = None) -> PortfolioLedgerStore:
    key = os.path.abspath(data_dir or os.path.join(os.path.expanduser("~"), ".oasyce"))
    with _STORE_LOCK:
        if key not in _STORE:
            _STORE[key] = PortfolioLedgerStore(data_dir=key)
        return _STORE[key]
