from __future__ import annotations

import json
import re
from typing import Any

import requests

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

_JSON_BLOCK_RE = re.compile(r"```json\s*\n([\s\S]*?)\n\s*```")

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


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

    Parameters
    ----------
    api_key:
        A valid SentiChain API key.
    base_url:
        Root URL for the SentiChain API.  Defaults to the public endpoint.
        Will switch to ``https://api.fundis.ai`` in a future release.
    timeout:
        HTTP request timeout in seconds.
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.sentichain.com",
        timeout: float = 30.0,
    ) -> None:
        if not api_key:
            raise AuthenticationError("api_key must be a non-empty string.")
        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="l3_event_sentiment_reasoning",
        )

        if raw is None:
            return []

        items = self._parse_reasoning_json(raw)
        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", "")),
                    is_pattern=bool(item.get("pattern", False)),
                    pattern_keywords=list(item.get("pattern_keywords") or []),
                )
            )
        return events

    def get_signal(self, ticker: str) -> Signal:
        """Derive a directional signal for *ticker* from its latest events.

        In v1.0.0, the signal is computed from bullish/bearish event counts.
        A future release will consume richer signal data from the pipeline.
        """
        events = self.get_events(ticker)

        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,
                bullish_count=0,
                bearish_count=0,
                total_count=0,
            )

        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,
            bullish_count=bullish,
            bearish_count=bearish,
            total_count=total,
        )

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

        In v1.0.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,
            "api_key": self._api_key,
        }

        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 == 401 or resp.status_code == 403:
            raise AuthenticationError(
                f"Authentication failed (HTTP {resp.status_code}). "
                "Check your SentiChain API key."
            )

        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 or reasoning == "hidden":
            return None

        return str(reasoning)

    @staticmethod
    def _parse_reasoning_json(raw: str) -> list[dict[str, Any]]:
        match = _JSON_BLOCK_RE.search(raw)
        text = match.group(1) if match else raw

        try:
            parsed = json.loads(text)
        except json.JSONDecodeError as exc:
            raise APIError(f"Failed to parse reasoning JSON: {exc}") from exc

        if isinstance(parsed, list):
            return parsed

        raise APIError("Expected a JSON array of events from the API.")
