import asyncio
import aiosqlite
import json
from datetime import datetime
from pathlib import Path
from typing import Optional

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


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.execute("""
            CREATE TABLE IF NOT EXISTS race_history (
                race_id TEXT PRIMARY KEY,
                scope TEXT NOT NULL,
                champion_name TEXT NOT NULL,
                champion_owner TEXT NOT NULL,
                participants TEXT NOT NULL,
                bet_distribution TEXT NOT NULL,
                duration_ticks INTEGER NOT NULL,
                completed_at TEXT NOT NULL,
                point_changes TEXT DEFAULT '{}',
                point_change_summaries TEXT DEFAULT '{}',
                odds_snapshot TEXT DEFAULT '{}'
            )
        """)

        await db.execute("""
            CREATE TABLE IF NOT EXISTS user_horse_names (
                user_id TEXT PRIMARY KEY,
                horse_name TEXT NOT NULL
            )
        """)
        
        # Add missing columns if they don't exist (for existing databases)
        try:
            await db.execute("SELECT point_changes FROM race_history LIMIT 1")
        except aiosqlite.OperationalError:
            await db.execute("ALTER TABLE race_history ADD COLUMN point_changes TEXT DEFAULT '{}'")
            
        try:
            await db.execute("SELECT point_change_summaries FROM race_history LIMIT 1")
        except aiosqlite.OperationalError:
            await db.execute("ALTER TABLE race_history ADD COLUMN point_change_summaries TEXT DEFAULT '{}'")
            
        try:
            await db.execute("SELECT odds_snapshot FROM race_history LIMIT 1")
        except aiosqlite.OperationalError:
            await db.execute("ALTER TABLE race_history ADD COLUMN odds_snapshot TEXT DEFAULT '{}'")
        
        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

    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]:
        await self.ensure_initialized()
        db = await self._get_db()
        cursor = await db.execute(
            "SELECT horse_name FROM user_horse_names WHERE user_id = ?", 
            (user_id,)
        )
        row = await cursor.fetchone()
        return row[0] if row else None

    async def set_last_horse_name(self, user_id: str, horse_name: str):
        await self.ensure_initialized()
        db = await self._get_db()
        await db.execute(
            "INSERT OR REPLACE INTO user_horse_names (user_id, horse_name) VALUES (?, ?)",
            (user_id, horse_name),
        )
        await db.commit()

    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):
        """Save race result to history."""
        await self.ensure_initialized()
        
        db = await self._get_db()
        await db.execute("""
            INSERT INTO race_history
                (race_id, scope, champion_name, champion_owner, participants, 
                 bet_distribution, duration_ticks, completed_at,
                 point_changes, point_change_summaries, odds_snapshot)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
            result.race_id,
            result.scope,
            result.champion_name,
            result.champion_owner,
            json.dumps(result.participants),
            json.dumps(result.bet_distribution),
            result.duration_ticks,
            result.completed_at.isoformat(),
            json.dumps(getattr(result, 'point_changes', {})),
            json.dumps(getattr(result, 'point_change_summaries', {})),
            json.dumps(getattr(result, 'odds_snapshot', {})),
        ))
        await db.commit()


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