from __future__ import annotations

import json
import threading
import time
from pathlib import Path
from typing import Any

import config_manager
from strategy_registry.registry import get_strategy_registry
from strategy_sdk.types import StrategyHealth, StrategyInitContext


def _effective_params_file() -> Path:
    return config_manager.get_logs_dir() / "effective_params.json"


def _runtime_status_file() -> Path:
    return config_manager.get_logs_dir() / "strategy_runtime_status.json"


def _load_runtime_scheduler_snapshot() -> dict[str, Any]:
    path = config_manager.get_logs_dir() / "runtime_scheduler_snapshot.json"
    if not path.exists():
        return {}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    if not isinstance(payload, dict):
        return {}
    if (
        payload.get("active_instance_ids")
        or payload.get("active_strategy_ids")
        or payload.get("selected_strategy_id")
        or payload.get("selected_instance_id")
    ):
        return payload
    return {}


def _load_effective_payload() -> dict[str, Any]:
    return config_manager.load_effective_runtime_config()


def _config_revision_from_payload(payload: dict[str, Any]) -> str:
    for key in ("config_revision", "revision", "synced_at", "generated_at"):
        value = payload.get(key)
        if value:
            return str(value)
    path = _effective_params_file()
    if path.exists():
        try:
            return f"mtime-{int(path.stat().st_mtime)}"
        except OSError:
            return "manual"
    return "manual"


def _runtime_scheduler_from_payload(payload: dict[str, Any]) -> dict[str, Any]:
    snapshot = _load_runtime_scheduler_snapshot()
    if snapshot:
        return snapshot
    scheduler = payload.get("runtime_scheduler", {})
    if isinstance(scheduler, dict) and (
        scheduler.get("active_instance_ids")
        or scheduler.get("active_strategy_ids")
        or scheduler.get("selected_strategy_id")
        or scheduler.get("selected_instance_id")
    ):
        return scheduler
    try:
        from src.unified_strategy_state import load_unified_state

        unified = load_unified_state(use_cache=False)
        fallback = unified.get("scheduler", {}) if isinstance(unified.get("scheduler", {}), dict) else {}
        if fallback.get("active_instance_ids") or fallback.get("active_strategy_ids"):
            return fallback
    except Exception:
        pass
    try:
        active_path = config_manager.get_config_dir() / "active_instances.json"
        active = json.loads(active_path.read_text(encoding="utf-8")) if active_path.exists() else {}
        raw_instances = active.get("instances", []) if isinstance(active.get("instances", []), list) else []
        enabled = [item for item in raw_instances if isinstance(item, dict) and bool(item.get("enabled", True))]
        if enabled:
            active_instance_ids = [str(item.get("id", "")).strip() for item in enabled if str(item.get("id", "")).strip()]
            active_strategy_ids: list[str] = []
            strategy_instance_map: dict[str, list[str]] = {}
            instance_weights: dict[str, float] = {}
            total_alloc = 0.0
            for item in enabled:
                strategy_id = str(item.get("strategy_id", "")).strip()
                instance_id = str(item.get("id", "")).strip()
                allocation = max(0.0, float(item.get("allocation", 0.0) or 0.0))
                if not strategy_id or not instance_id:
                    continue
                if strategy_id not in active_strategy_ids:
                    active_strategy_ids.append(strategy_id)
                strategy_instance_map.setdefault(strategy_id, []).append(instance_id)
                instance_weights[instance_id] = allocation
                total_alloc += allocation
            if active_instance_ids:
                if total_alloc > 0:
                    instance_weights = {
                        instance_id: round(weight / total_alloc, 4)
                        for instance_id, weight in instance_weights.items()
                    }
                else:
                    equal_weight = round(1.0 / len(active_instance_ids), 4)
                    instance_weights = {instance_id: equal_weight for instance_id in active_instance_ids}
                selected_instance_id = max(
                    instance_weights.items(),
                    key=lambda item: (float(item[1]), item[0]),
                )[0]
                selected_strategy_id = next(
                    (
                        strategy_id
                        for strategy_id, instance_ids in strategy_instance_map.items()
                        if selected_instance_id in instance_ids
                    ),
                    "",
                )
                strategy_weights = {
                    strategy_id: round(
                        sum(instance_weights.get(instance_id, 0.0) for instance_id in instance_ids),
                        4,
                    )
                    for strategy_id, instance_ids in strategy_instance_map.items()
                }
                return {
                    "mode": "single_active" if len(active_instance_ids) == 1 else "multi_active",
                    "arbiter": "allocation-weighted",
                    "reason": "runtime_status_recovered_from_active_instances",
                    "active_instance_ids": active_instance_ids,
                    "active_strategy_ids": active_strategy_ids,
                    "instance_weights": instance_weights,
                    "strategy_weights": strategy_weights,
                    "strategy_instance_map": strategy_instance_map,
                    "selected_instance_id": selected_instance_id,
                    "selected_strategy_id": selected_strategy_id,
                }
    except Exception:
        pass
    return scheduler if isinstance(scheduler, dict) else {}


def _active_runtime_view(payload: dict[str, Any], scheduler: dict[str, Any], config_revision: str) -> dict[str, Any]:
    active_strategy_ids = [
        str(item)
        for item in scheduler.get("active_strategy_ids", [])
        if str(item).strip()
    ] if isinstance(scheduler.get("active_strategy_ids", []), list) else []
    selected_strategy_id = str(scheduler.get("selected_strategy_id", "") or "")
    active_instance_source = scheduler.get("active_instance_ids", scheduler.get("active_instances", []))
    active_instance_ids = [
        str(item)
        for item in active_instance_source
        if str(item).strip()
    ] if isinstance(active_instance_source, list) else []
    if selected_strategy_id and selected_strategy_id not in active_strategy_ids:
        active_strategy_ids.append(selected_strategy_id)
    if not active_strategy_ids:
        active_strategy_ids = [str(payload.get("strategy_type", "ema_macd") or "ema_macd")]
    active_strategy_id = (
        selected_strategy_id
        or (active_strategy_ids[0] if active_strategy_ids else "")
        or str(payload.get("strategy_type", "ema_macd") or "ema_macd")
    )
    return {
        "strategy_id": active_strategy_id,
        "strategy_ids": active_strategy_ids,
        "instance_ids": active_instance_ids,
        "mode": str(scheduler.get("mode", "single_active") or "single_active"),
        "arbiter": str(scheduler.get("arbiter", "allocation-weighted") or "allocation-weighted"),
        "reason": str(scheduler.get("reason", "runtime_scheduler") or "runtime_scheduler"),
        "selected_strategy_id": str(selected_strategy_id or active_strategy_id),
        "selected_instance_id": str(scheduler.get("selected_instance_id", "") or ""),
        "config_revision": config_revision,
        "effective_params_path": str(_effective_params_file()),
    }


class StrategyRuntime:
    def __init__(self):
        self._registry = get_strategy_registry()
        self._instances: dict[str, Any] = {}
        self._health: dict[str, StrategyHealth] = {}
        self._lock = threading.RLock()

    def _write_status_snapshot(self) -> None:
        payload = _load_effective_payload()
        config_revision = _config_revision_from_payload(payload)
        scheduler = _runtime_scheduler_from_payload(payload)
        active_strategy_ids = [
            str(item)
            for item in scheduler.get("active_strategy_ids", [])
            if str(item).strip()
        ] if isinstance(scheduler.get("active_strategy_ids", []), list) else []
        selected_strategy_id = str(scheduler.get("selected_strategy_id", "") or "").strip()
        if selected_strategy_id and selected_strategy_id not in active_strategy_ids:
            active_strategy_ids.append(selected_strategy_id)
        loaded_ids = list(dict.fromkeys([*active_strategy_ids, *self._instances.keys()]))
        health_payload = {key: vars(value) for key, value in self._health.items()}
        for strategy_id in active_strategy_ids:
            if strategy_id in health_payload:
                continue
            strategy_version = "unknown"
            try:
                strategy_version = str(self._registry.get_spec(strategy_id).version or "unknown")
            except Exception:
                pass
            health_payload[strategy_id] = vars(
                StrategyHealth(
                    strategy_id=strategy_id,
                    loaded=True,
                    healthy=True,
                    last_error=None,
                    last_run_at_ms=None,
                    last_latency_ms=None,
                    consecutive_failures=0,
                    config_revision=config_revision,
                    strategy_version=strategy_version,
                )
            )
        status = {
            "ts_ms": int(time.time() * 1000),
            "active": _active_runtime_view(payload, scheduler, config_revision),
            "runtime_scheduler": scheduler,
            "registered": [manifest.strategy_id for manifest in self._registry.list_manifests()],
            "manifests": [vars(manifest) for manifest in self._registry.list_manifests()],
            "loaded": loaded_ids,
            "health": health_payload,
        }
        try:
            path = _runtime_status_file()
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(json.dumps(status, ensure_ascii=True, indent=2), encoding="utf-8")
        except Exception:
            pass

    def _build_context(self, strategy_id: str) -> StrategyInitContext:
        payload = _load_effective_payload()
        return StrategyInitContext(
            strategy_id=strategy_id,
            strategy_version=self._health.get(strategy_id, StrategyHealth(strategy_id=strategy_id)).strategy_version,
            config_revision=_config_revision_from_payload(payload),
            symbol=str(payload.get("symbol", "XAUUSD") or "XAUUSD"),
            timeframe=str(payload.get("timeframe", "M5") or "M5"),
            mode="live",
            config=payload.get("params", {}) if isinstance(payload.get("params", {}), dict) else {},
            shared_features={},
            env={"source": "effective_params"},
        )

    def get_legacy_compatible_generator(
        self,
        strategy_id: str | None,
        params: dict[str, Any] | None = None,
        force_reload: bool = False,
    ):
        resolved = self._registry.resolve(strategy_id)
        with self._lock:
            if force_reload or resolved not in self._instances:
                adapter = self._registry.instantiate_legacy(resolved, params=params)
                ctx = self._build_context(resolved)
                adapter.initialize(ctx)
                self._instances[resolved] = adapter
                self._health[resolved] = StrategyHealth(
                    strategy_id=resolved,
                    loaded=True,
                    healthy=True,
                    last_error=None,
                    last_run_at_ms=None,
                    last_latency_ms=None,
                    consecutive_failures=0,
                    config_revision=ctx.config_revision,
                    strategy_version=adapter.manifest().version,
                )
            self._write_status_snapshot()
            return self._instances[resolved]

    def mark_execution(
        self,
        strategy_id: str,
        started_at_ms: int,
        error: Exception | None = None,
    ) -> None:
        resolved = self._registry.resolve(strategy_id)
        with self._lock:
            health = self._health.get(resolved)
            if health is None:
                return
            health.last_run_at_ms = int(time.time() * 1000)
            health.last_latency_ms = max(0.0, health.last_run_at_ms - started_at_ms)
            if error is None:
                health.healthy = True
                health.last_error = None
                health.consecutive_failures = 0
            else:
                health.healthy = False
                health.last_error = str(error)
                health.consecutive_failures += 1
            self._write_status_snapshot()

    def get_health(self, strategy_id: str | None = None) -> dict[str, Any]:
        with self._lock:
            if strategy_id is None:
                return {key: vars(value) for key, value in self._health.items()}
            resolved = self._registry.resolve(strategy_id)
            value = self._health.get(resolved)
            return vars(value) if value is not None else {}

    def get_status(self, strategy_id: str | None = None) -> dict[str, Any]:
        if strategy_id is None:
            status = {
                "registered": [m.strategy_id for m in self._registry.list_manifests()],
                "manifests": [vars(m) for m in self._registry.list_manifests()],
                "loaded": list(self._instances.keys()),
                "health": self.get_health(),
            }
            self._write_status_snapshot()
            return status
        resolved = self._registry.resolve(strategy_id)
        adapter = self._instances.get(resolved)
        status = {
            "strategy_id": resolved,
            "loaded": adapter is not None,
            "manifest": (vars(adapter.manifest()) if adapter else None),
            "health": self.get_health(resolved),
        }
        self._write_status_snapshot()
        return status

    def reset(self, strategy_id: str | None = None):
        with self._lock:
            if strategy_id is None:
                old = list(self._instances.keys())
                self._instances.clear()
                self._health.clear()
                self._write_status_snapshot()
                return old
            resolved = self._registry.resolve(strategy_id)
            old = self._instances.pop(resolved, None)
            self._health.pop(resolved, None)
            self._write_status_snapshot()
            return old


_DEFAULT_RUNTIME: StrategyRuntime | None = None


def get_strategy_runtime() -> StrategyRuntime:
    global _DEFAULT_RUNTIME
    if _DEFAULT_RUNTIME is None:
        _DEFAULT_RUNTIME = StrategyRuntime()
    return _DEFAULT_RUNTIME
