"""
Simulation Service for running automated activity in the background.
This service manages starting/stopping simulated user activity.
"""

import asyncio
import random
from decimal import Decimal
from datetime import datetime
from typing import Optional
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from app.database import async_session
from app.models import (
    User, Wallet, SportEvent, SpreadBet, EventComment, MatchComment,
    EventStatus, SpreadBetStatus, TeamSide, Transaction, TransactionType, TransactionStatus
)
from app.schemas.admin import SimulationConfig, SimulationStatusResponse
from app.utils.security import get_password_hash


# Sample data for simulation
FIRST_NAMES = [
    "James", "Emma", "Liam", "Olivia", "Noah", "Ava", "Oliver", "Sophia",
    "Elijah", "Isabella", "Lucas", "Mia", "Mason", "Charlotte", "Ethan",
]

LAST_NAMES = [
    "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
    "Davis", "Rodriguez", "Martinez",
]

EVENT_COMMENTS = [
    "This is going to be a great game!",
    "Home team looking strong this season",
    "I'm betting on the underdog here",
    "What do you all think about the spread?",
    "The odds seem off to me",
    "Sharp money coming in on the home side",
    "Home field advantage is huge here",
]

MATCH_COMMENTS = [
    "Good luck!",
    "May the best bettor win",
    "I'm feeling confident about this one",
    "Nice bet, looking forward to the game",
    "GL HF",
]


class SimulationManager:
    """
    Singleton manager for controlling simulation state.
    Runs simulation in a background asyncio task.
    """
    _instance: Optional["SimulationManager"] = None
    _task: Optional[asyncio.Task] = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self._initialized = True
        self._running = False
        self._started_at: Optional[datetime] = None
        self._started_by: Optional[str] = None
        self._iterations = 0
        self._config: Optional[SimulationConfig] = None
        self._last_activity: Optional[str] = None
        self._stop_event = asyncio.Event()

    @property
    def is_running(self) -> bool:
        return self._running

    def get_status(self) -> SimulationStatusResponse:
        return SimulationStatusResponse(
            is_running=self._running,
            started_at=self._started_at,
            started_by=self._started_by,
            iterations_completed=self._iterations,
            config=self._config,
            last_activity=self._last_activity,
        )

    async def start(self, admin_username: str, config: Optional[SimulationConfig] = None) -> bool:
        """Start the simulation in a background task."""
        if self._running:
            return False

        self._config = config or SimulationConfig()
        self._running = True
        self._started_at = datetime.utcnow()
        self._started_by = admin_username
        self._iterations = 0
        self._stop_event.clear()

        # Start background task
        SimulationManager._task = asyncio.create_task(self._run_simulation())
        return True

    async def stop(self) -> tuple[int, float]:
        """Stop the simulation and return stats."""
        if not self._running:
            return 0, 0.0

        self._stop_event.set()
        if SimulationManager._task:
            try:
                await asyncio.wait_for(SimulationManager._task, timeout=5.0)
            except asyncio.TimeoutError:
                SimulationManager._task.cancel()

        iterations = self._iterations
        duration = (datetime.utcnow() - self._started_at).total_seconds() if self._started_at else 0

        self._running = False
        self._started_at = None
        self._started_by = None
        self._iterations = 0
        self._config = None
        SimulationManager._task = None

        return iterations, duration

    async def _run_simulation(self):
        """Main simulation loop."""
        while not self._stop_event.is_set():
            try:
                async with async_session() as db:
                    # Get existing users and events
                    users_result = await db.execute(select(User).where(User.is_admin == False))
                    users = list(users_result.scalars().all())

                    events_result = await db.execute(
                        select(SportEvent).where(SportEvent.status == EventStatus.UPCOMING)
                    )
                    events = list(events_result.scalars().all())

                    if not events:
                        self._last_activity = "No upcoming events - waiting..."
                        await asyncio.sleep(self._config.delay_seconds)
                        continue

                    # Perform random actions based on config
                    for _ in range(self._config.actions_per_iteration):
                        if self._stop_event.is_set():
                            break

                        action = self._pick_action()
                        try:
                            if action == "create_user" and self._config.create_users:
                                await self._create_user(db)
                                # Refresh users list
                                users_result = await db.execute(select(User).where(User.is_admin == False))
                                users = list(users_result.scalars().all())
                            elif action == "create_bet" and self._config.create_bets and users and events:
                                await self._create_bet(db, users, events)
                            elif action == "take_bet" and self._config.take_bets and users:
                                await self._take_bet(db, users)
                            elif action == "add_comment" and self._config.add_comments and users and events:
                                await self._add_comment(db, users, events)
                            elif action == "cancel_bet" and self._config.cancel_bets:
                                await self._cancel_bet(db)
                        except Exception as e:
                            self._last_activity = f"Error: {str(e)[:50]}"

                self._iterations += 1
                await asyncio.sleep(self._config.delay_seconds)

            except Exception as e:
                self._last_activity = f"Loop error: {str(e)[:50]}"
                await asyncio.sleep(self._config.delay_seconds)

    def _pick_action(self) -> str:
        """Pick a random action based on weights."""
        actions = [
            ("create_user", 0.15),
            ("create_bet", 0.35),
            ("take_bet", 0.25),
            ("add_comment", 0.20),
            ("cancel_bet", 0.05),
        ]
        rand = random.random()
        cumulative = 0
        for action, weight in actions:
            cumulative += weight
            if rand <= cumulative:
                return action
        return "create_bet"

    async def _create_user(self, db):
        """Create a random user."""
        first = random.choice(FIRST_NAMES)
        last = random.choice(LAST_NAMES)
        suffix = random.randint(100, 9999)
        username = f"{first.lower()}{last.lower()}{suffix}"
        email = f"{username}@example.com"

        # Check if exists
        existing = await db.execute(select(User).where(User.username == username))
        if existing.scalar_one_or_none():
            return

        user = User(
            email=email,
            username=username,
            password_hash=get_password_hash("password123"),
            display_name=f"{first} {last}",
        )
        db.add(user)
        await db.flush()

        wallet = Wallet(
            user_id=user.id,
            balance=Decimal(str(random.randint(500, 5000))),
            escrow=Decimal("0.00"),
        )
        db.add(wallet)
        await db.commit()

        self._last_activity = f"Created user: {username}"

    async def _create_bet(self, db, users: list[User], events: list[SportEvent]):
        """Create a random bet."""
        # Find users with balance
        users_with_balance = []
        for user in users:
            wallet_result = await db.execute(select(Wallet).where(Wallet.user_id == user.id))
            wallet = wallet_result.scalar_one_or_none()
            if wallet and wallet.balance >= Decimal("10"):
                users_with_balance.append((user, wallet))

        if not users_with_balance:
            return

        user, wallet = random.choice(users_with_balance)
        event = random.choice(events)

        spread = round(random.uniform(event.min_spread, event.max_spread) * 2) / 2
        if spread % 1 == 0:
            spread += 0.5

        max_stake = min(float(wallet.balance) * 0.5, 500)
        stake = Decimal(str(round(random.uniform(10, max(10, max_stake)), 2)))
        team = random.choice([TeamSide.HOME, TeamSide.AWAY])

        bet = SpreadBet(
            event_id=event.id,
            spread=spread,
            team=team,
            creator_id=user.id,
            stake_amount=stake,
            house_commission_percent=Decimal("10.00"),
            status=SpreadBetStatus.OPEN,
        )
        db.add(bet)
        await db.commit()

        team_name = event.home_team if team == TeamSide.HOME else event.away_team
        self._last_activity = f"{user.username} created ${stake} bet on {team_name}"

    async def _take_bet(self, db, users: list[User]):
        """Take a random open bet."""
        result = await db.execute(
            select(SpreadBet)
            .options(selectinload(SpreadBet.event), selectinload(SpreadBet.creator))
            .where(SpreadBet.status == SpreadBetStatus.OPEN)
        )
        open_bets = result.scalars().all()

        if not open_bets:
            return

        bet = random.choice(open_bets)

        # Find eligible takers
        eligible = []
        for user in users:
            if user.id == bet.creator_id:
                continue
            wallet_result = await db.execute(select(Wallet).where(Wallet.user_id == user.id))
            wallet = wallet_result.scalar_one_or_none()
            if wallet and wallet.balance >= bet.stake_amount:
                eligible.append((user, wallet))

        if not eligible:
            return

        taker, taker_wallet = random.choice(eligible)

        # Get creator wallet
        creator_wallet_result = await db.execute(
            select(Wallet).where(Wallet.user_id == bet.creator_id)
        )
        creator_wallet = creator_wallet_result.scalar_one_or_none()

        if not creator_wallet or creator_wallet.balance < bet.stake_amount:
            return

        # Lock funds
        creator_wallet.balance -= bet.stake_amount
        creator_wallet.escrow += bet.stake_amount
        taker_wallet.balance -= bet.stake_amount
        taker_wallet.escrow += bet.stake_amount

        # Create transactions
        creator_tx = Transaction(
            user_id=bet.creator_id,
            wallet_id=creator_wallet.id,
            type=TransactionType.ESCROW_LOCK,
            amount=-bet.stake_amount,
            balance_after=creator_wallet.balance,
            reference_id=bet.id,
            description=f"Escrow lock for spread bet #{bet.id}",
            status=TransactionStatus.COMPLETED,
        )
        taker_tx = Transaction(
            user_id=taker.id,
            wallet_id=taker_wallet.id,
            type=TransactionType.ESCROW_LOCK,
            amount=-bet.stake_amount,
            balance_after=taker_wallet.balance,
            reference_id=bet.id,
            description=f"Escrow lock for spread bet #{bet.id}",
            status=TransactionStatus.COMPLETED,
        )
        db.add(creator_tx)
        db.add(taker_tx)

        bet.taker_id = taker.id
        bet.status = SpreadBetStatus.MATCHED
        bet.matched_at = datetime.utcnow()

        await db.commit()
        self._last_activity = f"{taker.username} took ${bet.stake_amount} bet"

    async def _add_comment(self, db, users: list[User], events: list[SportEvent]):
        """Add a random comment."""
        user = random.choice(users)
        event = random.choice(events)
        content = random.choice(EVENT_COMMENTS)

        comment = EventComment(
            event_id=event.id,
            user_id=user.id,
            content=content,
        )
        db.add(comment)
        await db.commit()
        self._last_activity = f"{user.username} commented on {event.home_team} vs {event.away_team}"

    async def _cancel_bet(self, db):
        """Cancel a random open bet."""
        if random.random() > 0.2:  # Only 20% chance
            return

        result = await db.execute(
            select(SpreadBet)
            .options(selectinload(SpreadBet.creator))
            .where(SpreadBet.status == SpreadBetStatus.OPEN)
        )
        open_bets = result.scalars().all()

        if not open_bets:
            return

        bet = random.choice(open_bets)
        bet.status = SpreadBetStatus.CANCELLED
        await db.commit()
        self._last_activity = f"{bet.creator.username} cancelled ${bet.stake_amount} bet"


# Global instance
simulation_manager = SimulationManager()
