#!/usr/bin/env python3
"""
Unified Strategy State Migration — one-shot migration from legacy multi-file
strategy state to the single unified_strategy_state.json.

Called once at startup by ensure_unified_state().  Archives old files to
config/backup/legacy_migration_{timestamp}/ before writing the unified file.
"""

from __future__ import annotations

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

from src.unified_strategy_state import (
    SCHEMA_VERSION,
    _unified_state_path,
    _normalize_custom_workspace,
    invalidate_cache,
    save_unified_state,
)


def ensure_unified_state(app_root: Optional[Path] = None) -> Dict[str, Any]:
    """Called at startup. Returns the unified state, migrating if necessary.

    - If unified file exists with current schema_version → load and return
    - If unified file missing or old version → migrate from legacy → return
    - If unified file corrupt → re-migrate from legacy (or archives)
    """
    path = _unified_state_path(app_root)

    # Already exists and valid
    if path.exists():
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
            if isinstance(data, dict) and data.get("schema_version") == SCHEMA_VERSION:
                return data
        except Exception:
            pass  # corrupt — re-migrate

    # Migrate
    return migrate_to_unified_state(app_root)


def migrate_to_unified_state(app_root: Optional[Path] = None) -> Dict[str, Any]:
    """Migrate from legacy files to unified state. Archives old files."""
    import config_manager

    cfg = app_root or config_manager.get_config_dir()
    unified_path = _unified_state_path(app_root)

    # Archive old files
    ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    backup_dir = cfg / "backup" / f"legacy_migration_{ts}"
    backup_dir.mkdir(parents=True, exist_ok=True)

    # ── Gather legacy data ──

    # 1. strategy_market_state.json (primary source)
    market = _read_json(cfg / "strategy_market_state.json", {})
    strategies = market.get("strategies", {}) if isinstance(market.get("strategies", {}), dict) else {}
    runtime_list = market.get("runtime_instances", []) if isinstance(market.get("runtime_instances", []), list) else []
    decision_policy = market.get("decision_policy", {}) if isinstance(market.get("decision_policy", {}), dict) else {}

    # 2. active_instances.json
    active_raw = _read_json(cfg / "active_instances.json", {})

    # 3. effective_params.json (for scheduler)
    logs_cfg = _find_logs_dir(cfg, app_root)
    eff_raw = _read_json(logs_cfg / "effective_params.json", {}) if logs_cfg else {}

    # 4. strategy_custom_state.json
    custom_raw = _read_json(cfg / "strategy_custom_state.json", {})

    # 5. Per-strategy lifecycle.json and manifest.json
    strats_dir = _find_strategies_dir(cfg, app_root)
    lifecycle_data: Dict[str, Any] = {}
    manifest_data: Dict[str, Any] = {}
    if strats_dir and strats_dir.exists():
        for item in strats_dir.iterdir():
            if item.is_dir():
                sid = item.name
                lc = _read_json(item / "lifecycle.json", {})
                if lc:
                    lifecycle_data[sid] = lc
            elif item.suffix == ".json" and item.name.endswith(".manifest.json"):
                manifest = _read_json(item, {})
                if manifest:
                    mid = manifest.get("strategy_id", "") or item.name.replace(".manifest.json", "")
                    manifest_data[mid] = manifest

    # ── Build unified state ──

    # Merge strategy definitions
    unified_strategies: Dict[str, Any] = dict(strategies)

    # Fill in missing fields from manifests and lifecycle
    for sid, manifest in manifest_data.items():
        if sid not in unified_strategies:
            unified_strategies[sid] = {}
        entry = unified_strategies[sid]
        if not entry.get("display_name"):
            entry["display_name"] = manifest.get("name", sid)
        if "is_custom" not in entry:
            entry["is_custom"] = False
        if "base_engine" not in entry:
            entry["base_engine"] = manifest.get("base_strategy_id")

    for sid, lc in lifecycle_data.items():
        if sid not in unified_strategies:
            unified_strategies[sid] = {}
        stage = lc.get("stage", "")
        if stage:
            unified_strategies[sid]["lifecycle_stage"] = stage

    # Merge custom strategy metadata
    for sid, custom_entry in custom_raw.items() if isinstance(custom_raw, dict) else {}:
        if sid not in unified_strategies:
            continue
        if isinstance(custom_entry, dict):
            unified_strategies[sid].setdefault("is_custom", True)

    # Build instances dict from runtime_instances list
    instances: Dict[str, Any] = {}
    for inst in runtime_list:
        if not isinstance(inst, dict):
            continue
        iid = str(inst.get("instance_id", "")).strip()
        if iid:
            instances[iid] = inst

    # Merge active_instances.json overrides
    for inst in (active_raw.get("instances", []) or []):
        if not isinstance(inst, dict):
            continue
        iid = str(inst.get("id", "")).strip()
        sid = str(inst.get("strategy_id", "")).strip()
        if iid and sid and iid not in instances:
            instances[iid] = {
                "instance_id": iid,
                "strategy_id": sid,
                "strategy_name": sid,
                "allocation": float(inst.get("allocation", 1.0) or 1.0),
                "status": "running" if inst.get("enabled", True) else "paused",
                "role": "live_support",
                "config_revision": "active_instances_legacy",
                "deployed_at": datetime.now(timezone.utc).isoformat(),
                "updated_at": datetime.now(timezone.utc).isoformat(),
            }

    # Build scheduler
    scheduler: Dict[str, Any] = {}
    if isinstance(eff_raw, dict):
        s = eff_raw.get("runtime_scheduler", {})
        if isinstance(s, dict) and s:
            scheduler = dict(s)

    if not scheduler:
        # Build from instances
        from src.unified_strategy_state import _build_scheduler_from_instances
        scheduler = _build_scheduler_from_instances(instances)

    # ── Write unified state ──
    unified = {
        "schema_version": SCHEMA_VERSION,
        "strategies": unified_strategies,
        "instances": instances,
        "scheduler": scheduler,
        "decision_policy": decision_policy,
        "custom_workspace": _normalize_custom_workspace(
            custom_raw if isinstance(custom_raw, dict) else {}
        ),
        "updated_at": datetime.now(timezone.utc).isoformat(),
        "last_migration": datetime.now(timezone.utc).isoformat(),
    }

    # Archive legacy files before writing unified
    _archive_file(cfg / "strategy_market_state.json", backup_dir)
    _archive_file(cfg / "active_instances.json", backup_dir)
    _archive_file(cfg / "strategy_custom_state.json", backup_dir)
    if logs_cfg:
        _archive_file(logs_cfg / "effective_params.json", backup_dir)

    ok = save_unified_state(unified, app_root)
    if not ok:
        raise RuntimeError("Failed to write unified_strategy_state.json")

    invalidate_cache()
    return unified


# ── Helpers ─────────────────────────────────────────────────────────


def _read_json(path: Path, default: Any = None) -> Any:
    if not path.exists():
        return default
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return default


def _archive_file(src: Path, backup_dir: Path) -> None:
    if not src.exists():
        return
    try:
        shutil.copy2(str(src), str(backup_dir / src.name))
    except Exception:
        pass


def _find_logs_dir(cfg: Path, app_root: Optional[Path] = None) -> Optional[Path]:
    """Find the logs directory relative to config."""
    candidates = [
        cfg.parent / "logs",
        app_root / "logs" if app_root else None,
    ]
    for c in candidates:
        if c and c.exists():
            return c
    return None


def _find_strategies_dir(cfg: Path, app_root: Optional[Path] = None) -> Optional[Path]:
    """Find the strategies directory."""
    candidates = [
        cfg.parent / "strategies",
        app_root / "strategies" if app_root else None,
    ]
    for c in candidates:
        if c and c.exists():
            return c
    # Also check project root strategies/
    for p in cfg.parents:
        s = p / "strategies"
        if s.exists():
            return s
    return None
