import requests
from typing import Type

from pydantic import BaseModel, Field, PrivateAttr
from crewai.tools import BaseTool


class CoingeckoUniversalQuoteToolSchema(BaseModel):
    token_id: str | None = Field(
        default=None,
        description=(
            "Single CoinGecko *token id* to quote (example: 'bitcoin'). "
            "Backwards-compatible input; prefer `token_ids` for one-or-many tokens."
        ),
    )
    token_ids: list[str] | str | None = Field(
        default=None,
        description=(
            "One or more CoinGecko *token ids* to quote. "
            "You can pass either a JSON array (example: ['bitcoin', 'ethereum']) "
            "or a comma-separated string (example: 'bitcoin,ethereum'). "
            "If you also pass `token_id`, it will be included too."
        ),
    )
    amount: float = Field(
        default=1.0,
        description=(
            "How many tokens to value. Applies to each token in `token_ids`. "
            "Must be greater than 0. Example: amount=2.5 returns 2.5 * price."
        ),
    )
    vs_currency: str = Field(
        default="usd",
        description=(
            "Quote currency (CoinGecko `vs_currencies`), typically a fiat code like "
            "'usd' or 'eur'. Default is 'usd'."
        ),
    )

    model_config = {
        "json_schema_extra": {
            "examples": [
                {"token_id": "bitcoin"},
                {"token_ids": ["bitcoin", "ethereum"], "amount": 2.5},
                {"token_ids": "bitcoin,ethereum", "vs_currency": "eur"},
            ]
        }
    }


class CoingeckoUniversalQuoteTool(BaseTool):
    name: str = "coingecko_universal_quote_tool"
    description: str = (
        "Fetch current token prices from the CoinGecko API.\n\n"
        "How to call:\n"
        "- For a single token: pass `token_id='bitcoin'`\n"
        "- For multiple tokens: pass `token_ids=['bitcoin','ethereum']` (or 'bitcoin,ethereum')\n"
        "- Optional: `amount` (applies to each token) and `vs_currency` (default 'usd')\n\n"
        "Returns:\n"
        "- Single token: {token, vs_currency, price, amount, value}\n"
        "- Multiple tokens: {vs_currency, quotes:[...], missing_tokens:[...]}"
    )
    args_schema: Type[BaseModel] = CoingeckoUniversalQuoteToolSchema
    _base_url: str = PrivateAttr(default="https://api.coingecko.com/api/v3")
    
    def __init__(self, **data):
        super().__init__(**data)

    def _run(
        self,
        token_id: str | None = None,
        token_ids: list[str] | str | None = None,
        amount: float = 1.0,
        vs_currency: str = "usd",
        **kwargs,
    ):
        """
        Fetch token prices (one or many) from CoinGecko `simple/price`.

        Args:
            token_id (str, optional): Single CoinGecko token id (e.g., "bitcoin").
            token_ids (list[str] | str, optional): Many CoinGecko token ids.
            amount (float, optional): Quantity to value (applies to each token).
            vs_currency (str, optional): Quote currency (default: "usd").

        Returns:
            dict: Single-token result or a multi-token result bundle.
        """

        # NOTE: CrewAI / frameworks may inject extra arguments (e.g. security_context).
        # We accept **kwargs to stay robust and ignore anything we don't need.

        try:
            tokens: list[str] = []

            # Normalize token_ids (list or comma-separated string).
            if isinstance(token_ids, str):
                tokens.extend([t.strip() for t in token_ids.split(",")])
            elif isinstance(token_ids, list):
                tokens.extend([str(t).strip() for t in token_ids])

            # Backwards-compat: include token_id if provided.
            if token_id:
                tokens.append(str(token_id).strip())

            # Clean + de-duplicate (preserve order).
            cleaned: list[str] = []
            seen: set[str] = set()
            for t in tokens:
                if not t:
                    continue
                tid = t.strip().lower()
                if not tid or tid in seen:
                    continue
                cleaned.append(tid)
                seen.add(tid)

            if not cleaned:
                return {
                    "error": (
                        "Invalid input. Provide `token_id='bitcoin'` or "
                        "`token_ids=['bitcoin','ethereum']` (or 'bitcoin,ethereum')."
                    )
                }

            if amount is None or amount <= 0:
                return {"error": "Invalid input. `amount` must be greater than 0."}

            quote_currency = (vs_currency or "usd").strip().lower()
            endpoint = f"{self._base_url}/simple/price"
            response = requests.get(
                endpoint,
                params={"ids": ",".join(cleaned), "vs_currencies": quote_currency},
                timeout=20,
            )
            response.raise_for_status()  # Raise an error for bad responses (4xx, 5xx)
            data = response.json() or {}

            def build_quote(tid: str) -> dict:
                price = data.get(tid, {}).get(quote_currency)
                value = (price * amount) if isinstance(price, (int, float)) else None
                return {
                    "token": tid,
                    "vs_currency": quote_currency,
                    "price": price,
                    "amount": amount,
                    "value": value,
                }

            if len(cleaned) == 1:
                # Backwards-friendly shape for single-token calls (still a dict, not a list bundle).
                tid = cleaned[0]
                quote = build_quote(tid)
                if quote["price"] is None:
                    return {"error": f"Price data unavailable for token '{tid}'."}
                return quote

            # Multi-token calls always return a bundle so callers can see both quotes + missing tokens.
            quotes = [build_quote(tid) for tid in cleaned]
            missing = [q["token"] for q in quotes if q["price"] is None]
            return {"vs_currency": quote_currency, "quotes": quotes, "missing_tokens": missing}

        except requests.RequestException as e:
            return {"error": f"Error fetching data: {str(e)}"}


if __name__ == "__main__":

    """
    Simple test function for CoingeckoUniversalQuoteTool.
    """
    tool_instance = CoingeckoUniversalQuoteTool()

    # Test with a known token_id
    result = tool_instance.run(token_id="bitcoin", amount=2.5)
    print("Result for bitcoin price query:", result)

    # Test multi-token query
    result_multi = tool_instance.run(token_ids=["bitcoin", "ethereum"], amount=1.0)
    print("Result for multi-token price query:", result_multi)
