from __future__ import annotations

import json
from typing import Any

import requests

from fundis.exceptions import APIError, AuthenticationError, FundisError
from fundis.types import Event, Pattern, Signal

_SUPPORTED_TICKERS = ["BTC", "ETH", "SOL", "XRP", "DOGE", "HYPE"]


class FundisClient:
    """Thin client for querying Fundis intelligence data via the API.

    Parameters
    ----------
    api_key:
        A valid SentiChain API key (used for registration/billing only).
    base_url:
        Root URL for the Fundis intelligence API.
    timeout:
        HTTP request timeout in seconds.
    """

    def __init__(
        self,
        api_key: str = "",
        base_url: str = "https://api.fundis.ai",
        timeout: float = 30.0,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._session = requests.Session()

    def get_events(self, ticker: str) -> list[Event]:
        """Fetch the latest classified events for *ticker*.

        Returns a list of :class:`Event` objects parsed from the most recent
        pipeline chunk.
        """
        ticker = ticker.upper().strip()
        if not ticker:
            raise FundisError("ticker must be a non-empty string.")

        raw = self._fetch_reasoning(ticker=ticker, summary_type="sm_events")

        if raw is None:
            return []

        items = self._parse_json(raw)
        if not isinstance(items, list):
            return []

        events: list[Event] = []
        for item in items:
            events.append(
                Event(
                    timestamp=str(item.get("timestamp", "")),
                    type=str(item.get("event", "asset")).lower(),
                    sentiment=str(item.get("sentiment", "neutral")).lower(),
                    summary=str(item.get("summary", "")),
                )
            )
        return events

    def get_signal(self, ticker: str) -> Signal:
        """Fetch the latest directional signal for *ticker*.

        Returns a :class:`Signal` object with direction, confidence,
        rationale, category summaries, and detected patterns.
        Does not make additional API calls beyond the sm_signal fetch.
        """
        ticker = ticker.upper().strip()
        if not ticker:
            raise FundisError("ticker must be a non-empty string.")

        raw = self._fetch_reasoning(ticker=ticker, summary_type="sm_signal")

        if raw is None:
            return Signal(direction="FLAT", confidence=0.0, rationale="No signal data available.")

        data = self._parse_json(raw)
        if not isinstance(data, dict):
            return Signal(direction="FLAT", confidence=0.0, rationale="Invalid signal data.")

        patterns: list[Pattern] = []
        for p in data.get("patterns", []):
            if isinstance(p, dict):
                patterns.append(
                    Pattern(
                        keywords=p.get("keywords", []),
                        event_indices=p.get("event_indices", []),
                        description=p.get("description", ""),
                    )
                )

        return Signal(
            direction=str(data.get("direction", "FLAT")),
            confidence=float(data.get("confidence", 0.0)),
            rationale=str(data.get("rationale", "")),
            categories=data.get("categories", {}),
            patterns=patterns,
        )

    def list_tickers(self) -> list[str]:
        """Return the list of supported ticker symbols.

        In v1.1.0, the list is static. A future release will query the live
        ticker registry.
        """
        return list(_SUPPORTED_TICKERS)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _fetch_reasoning(self, ticker: str, summary_type: str) -> str | None:
        url = f"{self._base_url}/agent/get_reasoning_last"
        params = {"ticker": ticker, "summary_type": summary_type}

        try:
            resp = self._session.get(url, params=params, timeout=self._timeout)
        except requests.RequestException as exc:
            raise APIError(f"Request failed: {exc}") from exc

        if resp.status_code != 200:
            raise APIError(
                f"Unexpected response from API (HTTP {resp.status_code}).",
                status_code=resp.status_code,
            )

        data: dict[str, Any] = resp.json()
        reasoning = data.get("reasoning")

        if reasoning is None:
            return None

        return str(reasoning)

    @staticmethod
    def _parse_json(raw: str) -> Any:
        try:
            return json.loads(raw)
        except json.JSONDecodeError as exc:
            raise APIError(f"Failed to parse reasoning JSON: {exc}") from exc

    @staticmethod
    def _derive_signal_from_events(events: list[Event]) -> Signal:
        bullish = sum(1 for e in events if e.sentiment == "bullish")
        bearish = sum(1 for e in events if e.sentiment == "bearish")
        total = len(events)

        if total == 0:
            return Signal(direction="FLAT", confidence=0.0, rationale="No events available.")

        if bullish > bearish:
            direction = "LONG"
        elif bearish > bullish:
            direction = "SHORT"
        else:
            direction = "FLAT"

        confidence = round(abs(bullish - bearish) / total, 4)

        return Signal(
            direction=direction,
            confidence=confidence,
            rationale="Derived from event sentiment counts.",
            bullish_count=bullish,
            bearish_count=bearish,
            total_count=total,
        )
