import asyncio
import aiosqlite
import aiohttp
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Any, Optional

from .models import Room, RoomState, RaceResult
from .config import Config

logger = logging.getLogger(__name__)


class RoomStore:
    def __init__(self, config: Config):
        self.config = config
        self.rooms: dict[str, Room] = {}
        self._locks: dict[str, asyncio.Lock] = {}
        self.db_path = Path(config.RACE_DB_FILE)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._initialized = False
        self._db: Optional[aiosqlite.Connection] = None

    async def _init_db(self):
        """Initialize database tables asynchronously."""
        if self._initialized:
            return

        db = await self._get_db()
        await db.execute("""
            CREATE TABLE IF NOT EXISTS room_snapshots (
                scope TEXT PRIMARY KEY,
                state TEXT NOT NULL,
                created_at TEXT NOT NULL,
                horses TEXT NOT NULL,
                bets TEXT NOT NULL,
                champion_name TEXT,
                tick_count INTEGER DEFAULT 0
            )
        """)

        await db.commit()
        
        self._initialized = True

    async def ensure_initialized(self):
        """Ensure database is initialized (call before any DB operation)."""
        if not self._initialized:
            await self._init_db()

    async def _get_db(self) -> aiosqlite.Connection:
        """Get or create lazy database connection with WAL mode."""
        if self._db is None:
            self._db = await aiosqlite.connect(self.db_path)
            await self._db.execute("PRAGMA journal_mode=WAL")
            await self._db.commit()
        return self._db

    async def close(self):
        """Close database connection on shutdown."""
        if self._db is not None:
            await self._db.close()
            self._db = None

    def _url(self, path: str) -> str:
        """拼接 /bot/race 端点地址。"""

        return f"{self.config.RACE_API_HOST}/{path.lstrip('/')}"

    def _auth(self) -> dict[str, str]:
        """生成 xapi Bot 鉴权参数。"""

        return {
            "user": self.config.BOT_USER,
            "token": self.config.BOT_TOKEN,
        }

    async def _request(
        self,
        method: str,
        path: str,
        *,
        payload: Optional[dict[str, Any]] = None,
        params: Optional[dict[str, Any]] = None,
    ) -> Optional[dict[str, Any]]:
        """调用 xapi /bot/race，并只向上层暴露 data。"""

        request_url = self._url(path)
        timeout = aiohttp.ClientTimeout(total=10)
        try:
            async with aiohttp.ClientSession() as session:
                if method == "GET":
                    request_params = {**self._auth(), **(params or {})}
                    async with session.get(request_url, params=request_params, timeout=timeout) as resp:
                        return await self._parse_response(resp, path)
                request_payload = {**self._auth(), **(payload or {})}
                if method == "PUT":
                    async with session.put(request_url, json=request_payload, timeout=timeout) as resp:
                        return await self._parse_response(resp, path)
                async with session.post(request_url, json=request_payload, timeout=timeout) as resp:
                    return await self._parse_response(resp, path)
        except aiohttp.ClientError as exc:
            logger.error("race api request failed path=%s error=%s", path, exc)
            return None
        except asyncio.TimeoutError as exc:
            logger.error("race api request timeout path=%s error=%s", path, exc)
            return None

    async def _parse_response(self, resp: aiohttp.ClientResponse, path: str) -> Optional[dict[str, Any]]:
        """解析 xapi 统一响应，失败时返回 None 维持旧调用方失败语义。"""

        if resp.status != 200:
            logger.error("race api bad status path=%s status=%s", path, resp.status)
            return None
        body = await resp.json()
        if body.get("code") != 200:
            logger.error("race api fail path=%s code=%s message=%s", path, body.get("code"), body.get("message"))
            return None
        data = body.get("data")
        return data if isinstance(data, dict) else None

    async def load_rooms(self):
        """Restore active rooms from DB snapshots on startup."""
        await self.ensure_initialized()
        db = await self._get_db()
        cursor = await db.execute(
            "SELECT scope, state, created_at, horses, bets, champion_name, tick_count FROM room_snapshots"
        )
        rows = await cursor.fetchall()
        from .models import Horse, Bet
        for row in rows:
            scope, state_str, created_at, horses_json, bets_json, champion_name, tick_count = row
            # Only restore rooms in WAITING state (not crashed mid-race)
            if state_str != "WAITING":
                await db.execute("DELETE FROM room_snapshots WHERE scope = ?", (scope,))
                continue
            room = Room(scope=scope)
            room.state = RoomState(state_str)
            room.created_at = datetime.fromisoformat(created_at)
            room.champion_name = champion_name or ""
            room.tick_count = tick_count or 0
            # Restore horses
            try:
                horses_data = json.loads(horses_json) if horses_json else {}
            except (json.JSONDecodeError, TypeError):
                horses_data = {}
            for name, h_data in horses_data.items():
                horse = Horse(
                    owner_id=h_data.get("owner_id", ""),
                    name=h_data.get("name", name),
                    index=h_data.get("index", 0),
                )
                horse.position = h_data.get("position", 0.0)
                room.horses[name] = horse
            # Restore bets
            try:
                bets_data = json.loads(bets_json) if bets_json else []
            except (json.JSONDecodeError, TypeError):
                bets_data = []
            for b in bets_data:
                room.bets.append(Bet(
                    user_id=b.get("user_id", ""),
                    horse_name=b.get("horse_name", ""),
                    amount=b.get("amount", 0),
                ))
            room.next_horse_index = max((h.index for h in room.horses.values()), default=0) + 1
            self.rooms[scope] = room
        await db.commit()

    def get_lock(self, scope: str) -> asyncio.Lock:
        """Get or create per-room lock."""
        return self._locks.setdefault(scope, asyncio.Lock())

    def get_room(self, scope: str) -> Optional[Room]:
        """Get room by scope."""
        return self.rooms.get(scope)

    async def create_room(self, scope: str) -> Room:
        """Create new room."""
        room = Room(scope=scope)
        self.rooms[scope] = room
        await self._save_snapshot(room)
        return room

    def delete_room(self, scope: str):
        """Delete room."""
        if scope in self.rooms:
            del self.rooms[scope]

    async def get_last_horse_name(self, user_id: str) -> Optional[str]:
        """从 xapi 读取用户最后使用马名。"""

        data = await self._request("GET", "horse-name", params={"user_id": user_id})
        if data is None:
            return None
        horse_name = data.get("horse_name")
        return str(horse_name) if horse_name else None

    async def set_last_horse_name(self, user_id: str, horse_name: str):
        """将用户最后使用马名写入 xapi。"""

        await self._request(
            "PUT",
            "horse-name",
            payload={"user_id": user_id, "horse_name": horse_name},
        )

    async def _save_snapshot(self, room: Room):
        """Save room snapshot to database."""
        await self.ensure_initialized()
        
        horses_json = json.dumps({
            name: {
                "owner_id": horse.owner_id,
                "name": horse.name,
                "index": horse.index,
                "position": horse.position,
                "state": horse.state.value,
            }
            for name, horse in room.horses.items()
        })

        bets_json = json.dumps([
            {
                "user_id": bet.user_id,
                "horse_name": bet.horse_name,
                "amount": bet.amount,
            }
            for bet in room.bets
        ])

        db = await self._get_db()
        await db.execute("""
            INSERT OR REPLACE INTO room_snapshots
                (scope, state, created_at, horses, bets, champion_name, tick_count)
                VALUES (?, ?, ?, ?, ?, ?, ?)
            """, (
            room.scope,
            room.state.value,
            room.created_at.isoformat(),
            horses_json,
            bets_json,
            room.champion_name,
            room.tick_count,
        ))
        await db.commit()

    async def save_race_result(self, result: RaceResult):
        """将完整赛果写入 xapi。"""

        data = await self._request(
            "POST",
            "history",
            payload={
                "race_id": result.race_id,
                "scope": result.scope,
                "champion_name": result.champion_name,
                "champion_owner": result.champion_owner,
                "participants": result.participants,
                "bet_distribution": result.bet_distribution,
                "duration_ticks": result.duration_ticks,
                "completed_at": result.completed_at.isoformat(),
                "point_changes": result.point_changes,
                "point_change_summaries": result.point_change_summaries,
                "odds_snapshot": result.odds_snapshot,
            },
        )
        if data is None:
            raise RuntimeError(f"赛马赛果写入 xapi 失败: race_id={result.race_id}")


# Module-level singleton instance
room_store = RoomStore(Config())
