"""Multi-fund orchestration: manage multiple FundManager instances."""

from __future__ import annotations

import logging
import threading
from typing import Any

logger = logging.getLogger("horizon.fund.multi_fund")


class FundCluster:
    """Manages multiple FundManager instances as a cluster.

    Provides aggregate views across funds, coordinated start/stop,
    and cross-fund risk monitoring.
    """

    def __init__(self) -> None:
        self._funds: dict[str, Any] = {}  # name -> FundManager
        self._lock = threading.Lock()

    def add_fund(self, name: str, fund_manager: Any) -> None:
        """Add a fund to the cluster.

        Args:
            name: Unique fund name.
            fund_manager: FundManager instance.

        Raises:
            ValueError: If name already exists.
        """
        with self._lock:
            if name in self._funds:
                raise ValueError(f"Fund '{name}' already exists in cluster")
            self._funds[name] = fund_manager
        logger.info("Added fund '%s' to cluster", name)

    def remove_fund(self, name: str, stop: bool = True) -> None:
        """Remove a fund from the cluster.

        Args:
            name: Fund name.
            stop: Whether to stop the fund before removing.
        """
        with self._lock:
            fm = self._funds.pop(name, None)
        if fm is None:
            raise ValueError(f"Fund '{name}' not in cluster")
        if stop:
            try:
                fm.stop()
            except Exception as e:
                logger.warning("Error stopping fund '%s': %s", name, e)
        logger.info("Removed fund '%s' from cluster", name)

    def fund(self, name: str) -> Any:
        """Get a FundManager by name."""
        with self._lock:
            fm = self._funds.get(name)
        if fm is None:
            raise ValueError(f"Fund '{name}' not in cluster")
        return fm

    @property
    def fund_names(self) -> list[str]:
        with self._lock:
            return list(self._funds.keys())

    @property
    def fund_count(self) -> int:
        with self._lock:
            return len(self._funds)

    def start_all(self) -> dict[str, bool]:
        """Start all funds. Returns {name: success}."""
        with self._lock:
            funds = dict(self._funds)

        results: dict[str, bool] = {}
        for name, fm in funds.items():
            try:
                fm.start()
                results[name] = True
            except Exception as e:
                logger.error("Failed to start fund '%s': %s", name, e)
                results[name] = False
        return results

    def stop_all(self) -> dict[str, bool]:
        """Stop all funds. Returns {name: success}."""
        with self._lock:
            funds = dict(self._funds)

        results: dict[str, bool] = {}
        for name, fm in funds.items():
            try:
                fm.stop()
                results[name] = True
            except Exception as e:
                logger.error("Failed to stop fund '%s': %s", name, e)
                results[name] = False
        return results

    def aggregate_status(self) -> dict[str, Any]:
        """Aggregate status across all funds."""
        with self._lock:
            funds = dict(self._funds)

        total_nav = 0.0
        total_strategies = 0
        per_fund: dict[str, Any] = {}

        for name, fm in funds.items():
            try:
                status = fm.status()
                nav = status.get("nav", 0.0)
                total_nav += nav
                total_strategies += status.get("running_strategies", 0)
                per_fund[name] = {
                    "nav": round(nav, 2),
                    "drawdown_pct": round(status.get("drawdown_pct", 0.0), 2),
                    "running_strategies": status.get("running_strategies", 0),
                    "kill_switch": status.get("kill_switch", False),
                }
            except Exception as e:
                per_fund[name] = {"error": str(e)}

        return {
            "total_nav": round(total_nav, 2),
            "fund_count": len(funds),
            "total_strategies": total_strategies,
            "per_fund": per_fund,
        }

    def aggregate_risk(self) -> dict[str, Any]:
        """Aggregate risk metrics across all funds."""
        with self._lock:
            funds = dict(self._funds)

        fund_drawdowns: dict[str, float] = {}
        max_drawdown = 0.0

        for name, fm in funds.items():
            try:
                status = fm.status()
                dd = status.get("drawdown_pct", 0.0)
                fund_drawdowns[name] = round(dd, 2)
                max_drawdown = max(max_drawdown, dd)
            except Exception:
                fund_drawdowns[name] = 0.0

        # Weighted average drawdown
        total_nav = 0.0
        weighted_dd = 0.0
        for name, fm in funds.items():
            try:
                status = fm.status()
                nav = status.get("nav", 0.0)
                dd = status.get("drawdown_pct", 0.0)
                total_nav += nav
                weighted_dd += nav * dd
            except Exception:
                pass

        avg_drawdown = weighted_dd / total_nav if total_nav > 0 else 0.0

        return {
            "weighted_avg_drawdown_pct": round(avg_drawdown, 2),
            "max_single_fund_drawdown_pct": round(max_drawdown, 2),
            "fund_drawdowns": fund_drawdowns,
        }

    def rebalance_across_funds(self, target_weights: dict[str, float]) -> dict[str, Any]:
        """Rebalance capital across funds.

        Args:
            target_weights: {fund_name: weight} where weights sum to ~1.0.

        Returns:
            Dict with rebalance results per fund.
        """
        weight_sum = sum(target_weights.values())
        if abs(weight_sum - 1.0) > 0.05:
            raise ValueError(f"Target weights must sum to ~1.0, got {weight_sum:.3f}")

        with self._lock:
            funds = dict(self._funds)

        # Compute total NAV
        total_nav = 0.0
        for name, fm in funds.items():
            try:
                status = fm.status()
                total_nav += status.get("nav", 0.0)
            except Exception:
                pass

        if total_nav <= 0:
            return {"error": "Total NAV is zero, cannot rebalance"}

        results: dict[str, Any] = {}
        for name, weight in target_weights.items():
            if name not in funds:
                results[name] = {"error": f"Fund '{name}' not in cluster"}
                continue

            target_capital = total_nav * weight
            try:
                fm = funds[name]
                status = fm.status()
                current_nav = status.get("nav", 0.0)
                results[name] = {
                    "target_capital": round(target_capital, 2),
                    "current_nav": round(current_nav, 2),
                    "adjustment": round(target_capital - current_nav, 2),
                }
            except Exception as e:
                results[name] = {"error": str(e)}

        return {
            "total_nav": round(total_nav, 2),
            "target_weights": target_weights,
            "per_fund": results,
        }

    def close_all(self) -> None:
        """Stop and close all funds."""
        with self._lock:
            funds = dict(self._funds)

        for name, fm in funds.items():
            try:
                fm.close()
            except Exception as e:
                logger.warning("Error closing fund '%s': %s", name, e)

        with self._lock:
            self._funds.clear()
