"""LSEG bond analytics service — orchestrates data loading and risk scoring."""
from __future__ import annotations

import logging
import threading
from typing import Optional

from app.config import settings
from app.providers.lseg import LsegDataLoader
from app.scoring.bond import compute_bond_risk
from app.scoring.types import BondRiskResult

logger = logging.getLogger(__name__)


class LsegBondService:
    """
    Loads LSEG bond data and computes risk scores.

    Data source priority:
      1. LSEG API (if LSEG_APP_KEY is set) — fetches live data for ISINs in LSEG_ISINS
      2. File fallback (LSEG_DATA_PATH) — reads downloaded Excel/CSV files from disk
    """

    def __init__(self, app_key: Optional[str] = None, isins: Optional[list[str]] = None,
                 issuers: Optional[list[str]] = None, data_path: Optional[str] = None):
        self._app_key = app_key or settings.lseg_app_key
        self._isins = isins or settings.isin_list
        self._issuers = issuers or settings.issuer_list
        self._file_loader = LsegDataLoader(data_path or settings.lseg_data_path)
        self._api_client = None
        self._cache: Optional[list[dict]] = None
        self._loading = False
        self._lock = threading.Lock()

    def _get_api_client(self):
        if self._api_client is None:
            from app.providers.lseg_api import LsegApiClient
            self._api_client = LsegApiClient(
                app_key=self._app_key,
                username=settings.lseg_username,
                password=settings.lseg_password,
            )
        return self._api_client

    def _do_load(self) -> list[dict]:
        if self._app_key and self._isins:
            logger.info("LSEG: fetching %d ISINs via API", len(self._isins))
            raw = self._get_api_client().fetch_bonds(self._isins)
        elif self._app_key and self._issuers:
            logger.info("LSEG: searching bonds for %d issuers", len(self._issuers))
            raw = self._get_api_client().fetch_bonds_for_issuers(self._issuers)
        else:
            logger.info("LSEG: loading from files (no App Key configured)")
            raw = self._file_loader.load_bonds()
        return [self._enrich(b) for b in raw]

    def _background_load(self) -> None:
        try:
            result = self._do_load()
            with self._lock:
                self._cache = result
                self._loading = False
            logger.info("LSEG: bond cache ready (%d bonds)", len(result))
        except Exception:
            with self._lock:
                self._loading = False
            logger.exception("LSEG: bond cache load failed")

    def start_background_load(self) -> None:
        """Kick off a background fetch if one isn't already running."""
        with self._lock:
            if self._cache is not None or self._loading:
                return
            self._loading = True
        threading.Thread(target=self._background_load, daemon=True).start()

    @property
    def is_loading(self) -> bool:
        return self._loading

    def _enrich(self, bond: dict) -> dict:
        result: BondRiskResult = compute_bond_risk(
            isin=bond.get("isin") or "UNKNOWN",
            rating_moodys=bond.get("rating_moodys"),
            rating_sp=bond.get("rating_sp"),
            rating_fitch=bond.get("rating_fitch"),
            z_spread=bond.get("z_spread"),
            asset_swap_spread=bond.get("asset_swap_spread"),
        )
        return {**bond, "risk": result.model_dump()}

    def get_all_bonds(self, force_refresh: bool = False) -> Optional[list[dict]]:
        if force_refresh:
            with self._lock:
                self._cache = None
        with self._lock:
            if self._cache is not None:
                return self._cache
        self.start_background_load()
        return None

    def get_bond(self, isin: str, force_refresh: bool = False) -> Optional[dict]:
        bonds = self.get_all_bonds(force_refresh=force_refresh)
        if bonds is None:
            return None
        target = isin.upper().strip()
        return next((b for b in bonds if (b.get("isin") or "").upper() == target), None)

    def get_portfolio_summary(self, force_refresh: bool = False) -> Optional[dict]:
        bonds = self.get_all_bonds(force_refresh=force_refresh)
        if bonds is None:
            return None
        if not bonds:
            return {
                "total_bonds": 0,
                "avg_composite_score": None,
                "grade_distribution": {},
                "investment_grade_count": 0,
                "high_yield_count": 0,
                "avg_spread_bps": None,
            }

        scores = [b["risk"]["composite_score"] for b in bonds]
        grade_counts: dict[str, int] = {}
        ig_count = 0

        for b in bonds:
            g = b["risk"]["composite_grade"]
            grade_counts[g] = grade_counts.get(g, 0) + 1
            if b["risk"]["is_investment_grade"]:
                ig_count += 1

        spread_vals = [
            b.get("z_spread") or b.get("asset_swap_spread")
            for b in bonds
        ]
        spread_vals = [s for s in spread_vals if s is not None]

        return {
            "total_bonds": len(bonds),
            "avg_composite_score": round(sum(scores) / len(scores), 1),
            "grade_distribution": grade_counts,
            "investment_grade_count": ig_count,
            "high_yield_count": len(bonds) - ig_count,
            "avg_spread_bps": round(sum(spread_vals) / len(spread_vals), 1) if spread_vals else None,
        }
