import logging
import os

import httpx
from fastmcp import FastMCP
from howlongtobeatpy import HowLongToBeat, SearchModifiers

logger = logging.getLogger("mcp-game-info")

mcp = FastMCP(name="Game Info")

STEAM_SEARCH_URL = "https://store.steampowered.com/api/storesearch/"
GG_DEALS_PRICES_URL = "https://api.gg.deals/v1/prices/by-steam-app-id/"
GG_DEALS_BUNDLES_URL = "https://api.gg.deals/v1/bundles/by-steam-app-id/"


# --- Helpers ---


def _get_gg_deals_api_key() -> str:
    key = os.environ.get("GG_DEALS_API_KEY")
    if not key:
        raise ValueError(
            "GG_DEALS_API_KEY environment variable is not set. "
            "Get a free API key at https://gg.deals/api/"
        )
    return key


def _validate_steam_ids(steam_app_ids: str) -> str:
    ids = [s.strip() for s in steam_app_ids.split(",") if s.strip()]
    if not ids:
        raise ValueError("No Steam App IDs provided.")
    if len(ids) > 100:
        raise ValueError(f"Maximum 100 Steam App IDs per request, got {len(ids)}.")
    for app_id in ids:
        if not app_id.isdigit():
            raise ValueError(f"Invalid Steam App ID: '{app_id}'. Must be numeric.")
    return ",".join(ids)


async def _http_get(url: str, params: dict | None = None) -> dict:
    headers = {"User-Agent": "mcp-game-info/0.1.0", "Accept": "application/json"}
    async with httpx.AsyncClient(headers=headers) as client:
        response = await client.get(url, params=params, timeout=15.0)
        if response.status_code == 429:
            raise ValueError("Rate limit exceeded. Please wait before retrying.")
        if response.status_code in (401, 403):
            raise ValueError("Authentication failed. Check your API key.")
        response.raise_for_status()
        return response.json()


def _clean_time(value) -> float | None:
    """Normalize HLTB time values: -1, 0, or None become None."""
    if value is None or value <= 0:
        return None
    return round(value, 1)


# --- Tools ---


@mcp.tool()
async def search_game(game_name: str, include_dlc: bool = False) -> list[dict]:
    """Search HowLongToBeat for game completion times.

    Returns up to 5 results with estimated completion times in hours
    for main story, main + extras, completionist, and more.
    """
    modifier = SearchModifiers.NONE if include_dlc else SearchModifiers.HIDE_DLC
    results = await HowLongToBeat().async_search(game_name, search_modifiers=modifier)

    if not results:
        raise ValueError(f"No HowLongToBeat results found for '{game_name}'.")

    results.sort(key=lambda e: e.similarity, reverse=True)

    entries = []
    for entry in results[:5]:
        entries.append({
            "game_name": entry.game_name,
            "game_id": entry.game_id,
            "game_type": entry.game_type,
            "image_url": entry.game_image_url,
            "web_link": entry.game_web_link,
            "similarity": round(entry.similarity, 2),
            "main_story_hours": _clean_time(entry.main_story),
            "main_extra_hours": _clean_time(entry.main_extra),
            "completionist_hours": _clean_time(entry.completionist),
            "all_styles_hours": _clean_time(entry.all_styles),
            "coop_hours": _clean_time(entry.coop_time),
            "multiplayer_hours": _clean_time(entry.mp_time),
            "platforms": entry.profile_platforms,
            "developer": entry.profile_dev,
            "release_year": entry.release_world,
            "review_score": entry.review_score,
        })

    return entries


@mcp.tool()
async def search_steam(term: str) -> list[dict]:
    """Search the Steam Store for games to find their Steam App IDs.

    Use this to get Steam App IDs that can then be passed to
    get_game_prices or get_game_bundles.
    """
    data = await _http_get(STEAM_SEARCH_URL, params={
        "term": term,
        "l": "english",
        "cc": "US",
    })

    items = data.get("items", [])
    if not items:
        raise ValueError(f"No Steam Store results found for '{term}'.")

    results = []
    for item in items[:5]:
        price_info = item.get("price")
        results.append({
            "app_id": item["id"],
            "name": item["name"],
            "price": {
                "currency": price_info.get("currency", "USD"),
                "initial": price_info.get("initial"),
                "final": price_info.get("final"),
            } if price_info else None,
            "platforms": item.get("platforms"),
            "image": item.get("tiny_image"),
        })

    return results


@mcp.tool()
async def get_game_prices(
    steam_app_ids: str,
    region: str = "nl",
) -> dict:
    """Get current and historical game prices from gg.deals.

    Prices depend heavily on the region — you MUST ask the user which country
    or region they want prices for before calling this tool, unless they have
    already told you. Using the wrong region returns prices in the wrong currency.

    Args:
        steam_app_ids: Comma-separated Steam App IDs (e.g. "1145360,292030"). Max 100.
        region: Two-letter region code. Supported: au, be, br, ca, ch, de, dk,
                es, eu, fi, fr, gb, ie, it, nl, no, pl, se, us.
    """
    api_key = _get_gg_deals_api_key()
    clean_ids = _validate_steam_ids(steam_app_ids)

    data = await _http_get(GG_DEALS_PRICES_URL, params={
        "key": api_key,
        "ids": clean_ids,
        "region": region,
    })

    if not data.get("success"):
        raise ValueError(f"gg.deals API error: {data}")

    return data.get("data", {})


@mcp.tool()
async def get_game_bundles(steam_app_ids: str) -> dict:
    """Get bundle information for games from gg.deals.

    Args:
        steam_app_ids: Comma-separated Steam App IDs (e.g. "1145360,292030"). Max 100.
    """
    api_key = _get_gg_deals_api_key()
    clean_ids = _validate_steam_ids(steam_app_ids)

    data = await _http_get(GG_DEALS_BUNDLES_URL, params={
        "key": api_key,
        "ids": clean_ids,
    })

    if not data.get("success"):
        raise ValueError(f"gg.deals API error: {data}")

    return data.get("data", {})


def main():
    mcp.run()


if __name__ == "__main__":
    main()
