import httpx
import asyncio
import json
import re
from enum import Enum
from typing import Sequence
from pydantic import BaseModel, Field
from bs4 import BeautifulSoup
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
from mcp.shared.exceptions import McpError
from mcp.types import ErrorData, INVALID_PARAMS

import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

# ============================================================
# 🧩 ENUM / INPUT SCHEMA
# ============================================================

class WSTools(str, Enum):
    GET_SERIES = "get_series"
    GET_SERIES_COUNTER = "get_series_counter"


class WSSearchInput(BaseModel):
    keyword: str = Field(description="搜尋關鍵字（僅支援日文或卡號）")
    title_number: str = Field(default="", description="系列代號（例如 HOL、BD、KGL 等）")

# ============================================================
# 🧠 Parser：強化 HTML 解析器
# ============================================================

def parse_ws_html(html: str) -> list[dict]:
    """最強強化版 Weiß Schwarz 解析器：含圖示欄位（色、魂、觸發）"""
    soup = BeautifulSoup(html, "html.parser")
    table = soup.find("table", class_="search-result-table")
    if not table:
        print("⚠️ 沒找到卡片表格結構")
        return []

    rows = table.find_all("tr")
    results = []

    for row in rows:
        cols = row.find_all("td")
        if not cols:
            continue

        img_tag = row.find("img")
        img_url = None
        if img_tag and img_tag.get("src"):
            src = img_tag["src"]
            img_url = src if src.startswith("http") else f"https://ws-tcg.com{src}"

        text = row.get_text(" ", strip=True)
        name_match = re.match(r"^(.*?)\s*\(\s*([A-Z0-9/]+-[A-Z0-9]+)\s*\)", text)
        if name_match:
            card_name = name_match.group(1).strip()
            card_number = name_match.group(2).strip()
        else:
            card_name = text.split(" ")[0].strip()
            card_number = None

        units = row.select("span.unit")
        color = power = soul = cost = rarity = None
        trigger = []
        type_ = None
        level = None

        for unit in units:
            label = unit.get_text(strip=True)
            imgs = unit.find_all("img")
            gif_names = [re.search(r"/([a-zA-Z0-9_]+)\.gif", img.get("src", "")).group(1)
                         for img in imgs if re.search(r"/([a-zA-Z0-9_]+)\.gif", img.get("src", ""))]

            if "色" in label:
                color = gif_names[0] if gif_names else None
            elif "レベル" in label:
                m = re.search(r"(\d+)", label)
                level = int(m.group(1)) if m else None
            elif "パワー" in label:
                m = re.search(r"(\d+)", label)
                power = int(m.group(1)) if m else None
            elif "ソウル" in label:
                soul = len(gif_names) if gif_names else None
            elif "コスト" in label:
                m = re.search(r"(\d+)", label)
                cost = int(m.group(1)) if m else None
            elif "レアリティ" in label:
                rarity = label.replace("レアリティ：", "").strip() or None
            elif "トリガー" in label:
                trigger = gif_names or []
            elif "種類" in label:
                type_ = label.replace("種類：", "").strip() or None

        info_text = row.get_text(" ", strip=True)
        traits_match = re.search(r"特徴：([^フレーバー]*)", info_text)
        traits = [t.strip() for t in traits_match.group(1).split("・")] if traits_match else []
        flavor_match = re.search(r"フレーバー：([^【]*)", info_text)
        flavor = flavor_match.group(1).strip() if flavor_match else None
        effect_match = re.search(r"(【[^】]+】.*)", info_text)
        effect = effect_match.group(1).strip() if effect_match else None

        results.append({
            "card_name": card_name,
            "card_number": card_number,
            "type": type_,
            "level": level,
            "color": color,
            "power": power,
            "soul": soul,
            "cost": cost,
            "rarity": rarity,
            "trigger": trigger,
            "traits": traits,
            "flavor": flavor,
            "effect": effect,
            "image": img_url,
        })

    return results

# ============================================================
# 🚀 核心邏輯：自動分頁 + 併發
# ============================================================

async def fetch_all_pages(payload: dict, counter_mode: bool = False) -> list[dict]:
    """自動翻頁併發爬取，保持順序"""
    base_url = "https://ws-tcg.com/cardlist/search"
    all_cards = []

    async with httpx.AsyncClient() as client:
        # 抓首頁
        resp = await client.post(base_url, data=payload, timeout=30)
        html = resp.text
        soup = BeautifulSoup(html, "html.parser")
        all_cards.extend(parse_ws_html(html))

        # 檢查分頁
        pager = soup.find("p", class_="pager")
        if not pager:
            return all_cards

        # 找出最大頁碼
        pages = []
        for span in pager.find_all("span"):
            a = span.find("a")
            if a and "page=" in a.get("href", ""):
                try:
                    num = int(re.search(r"page=(\d+)", a["href"]).group(1))
                    pages.append(num)
                except:
                    pass
        max_page = max(pages) if pages else 1

        print(f"📑 偵測到 {max_page} 頁，啟動併發抓取中…")

        # 限制同時併發數
        semaphore = asyncio.Semaphore(10)

        async def fetch_page(n):
            async with semaphore:
                try:
                    url = f"{base_url}?page={n}"
                    r = await client.post(url, data=payload, timeout=30)
                    cards = parse_ws_html(r.text)
                    print(f"✅ 第 {n}/{max_page} 頁 → {len(cards)} 張")
                    return (n, cards)
                except Exception as e:
                    print(f"⚠️ 第 {n} 頁錯誤：{e}")
                    return (n, [])

        tasks = [fetch_page(i) for i in range(2, max_page + 1)]
        results = await asyncio.gather(*tasks)

        # 排序合併
        results.sort(key=lambda x: x[0])
        for _, cards in results:
            all_cards.extend(cards)

    print(f"🎉 抓取完成，共 {len(all_cards)} 張卡")
    return all_cards

# ============================================================
# 🧩 MCP 伺服器註冊
# ============================================================

async def serve() -> None:
    server = Server("mcp-ws")

    @server.list_tools()
    async def list_tools() -> list[Tool]:
        return [
            Tool(name=WSTools.GET_SERIES.value, description="查詢 Weiß Schwarz(WS) 卡片（一般搜尋）", inputSchema=WSSearchInput.model_json_schema()),
            Tool(name=WSTools.GET_SERIES_COUNTER.value, description="查詢 Weiß Schwarz(WS) 卡片（助太刀或反擊事件）", inputSchema=WSSearchInput.model_json_schema()),
        ]

    @server.call_tool()
    async def call_tool(name: str, arguments: dict) -> Sequence[TextContent]:
        try:
            args = WSSearchInput(**arguments)
        except Exception as e:
            raise McpError(ErrorData(code=INVALID_PARAMS, message=f"參數錯誤：{str(e)}"))

        base_payload = {
            "_method": "POST",
            "expansion_select": "",
            "title_number_select": "",
            "cmd": "search",
            "keyword": args.keyword,
            "keyword_or": "",
            "keyword_not": "",
            "keyword_cardname[0]": "",
            "keyword_cardname[1]": "",
            "keyword_feature[0]": "",
            "keyword_feature[1]": "",
            "keyword_text[0]": "",
            "keyword_text[1]": "",
            "keyword_cardnumber[0]": "",
            "keyword_cardnumber[1]": "",
            "side": "",
            "title_number": args.title_number,
            "expansion_category": "",
            "expansion": "",
            "card_kind": "",
            "level_s": "",
            "level_e": "",
            "power_s": "",
            "power_e": "",
            "color": "",
            "soul_s": "",
            "soul_e": "",
            "cost_s": "",
            "cost_e": "",
            "trigger": "",
            "option_counter": "1" if name == WSTools.GET_SERIES_COUNTER.value else "0",
            "option_clock": "0",
            "show_page_count": "25",
            "show_small": "0",
            "parallel": "0",
            "expansion_filter": "",
            "button": "カードを検索する",
        }

        cards = await fetch_all_pages(base_payload, counter_mode=(name == WSTools.GET_SERIES_COUNTER.value))
        return [TextContent(type="text", text=json.dumps(cards, ensure_ascii=False, indent=2))]

    options = server.create_initialization_options()
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, options)

# ============================================================
# 🧨 啟動入口
# ============================================================

if __name__ == "__main__":
    asyncio.run(serve())
