"""
Data Wiper Service for safely clearing database data.
Includes safeguards like confirmation phrases and cooldowns.
"""

from datetime import datetime, timedelta
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, func

from app.models import (
    User, Wallet, Transaction, Bet, SpreadBet, SportEvent,
    EventComment, MatchComment, AdminSettings, AdminAuditLog,
    UserStats, Achievement, UserAchievement, LootBox, ActivityFeed, DailyReward
)
from app.schemas.admin import WipePreviewResponse, WipeRequest, WipeResponse
from app.services.audit_service import log_data_wipe


# Cooldown between wipes (5 minutes)
WIPE_COOLDOWN_SECONDS = 300
CONFIRMATION_PHRASE = "CONFIRM WIPE"


class WiperService:
    """Service for wiping database data with safeguards."""

    @staticmethod
    async def get_preview(db: AsyncSession) -> WipePreviewResponse:
        """
        Get a preview of what would be deleted in a wipe operation.
        Also checks cooldown status.
        """
        # Count all entities
        users_count = (await db.execute(select(func.count(User.id)))).scalar() or 0
        admin_users = (await db.execute(
            select(func.count(User.id)).where(User.is_admin == True)
        )).scalar() or 0
        wallets_count = (await db.execute(select(func.count(Wallet.id)))).scalar() or 0
        transactions_count = (await db.execute(select(func.count(Transaction.id)))).scalar() or 0
        bets_count = (await db.execute(select(func.count(Bet.id)))).scalar() or 0
        spread_bets_count = (await db.execute(select(func.count(SpreadBet.id)))).scalar() or 0
        events_count = (await db.execute(select(func.count(SportEvent.id)))).scalar() or 0
        event_comments_count = (await db.execute(select(func.count(EventComment.id)))).scalar() or 0
        match_comments_count = (await db.execute(select(func.count(MatchComment.id)))).scalar() or 0

        # Check last wipe time from audit log
        last_wipe_log = await db.execute(
            select(AdminAuditLog)
            .where(AdminAuditLog.action == "DATA_WIPE")
            .order_by(AdminAuditLog.created_at.desc())
            .limit(1)
        )
        last_wipe = last_wipe_log.scalar_one_or_none()

        cooldown_remaining = 0
        can_wipe = True
        last_wipe_at = None

        if last_wipe:
            last_wipe_at = last_wipe.created_at
            elapsed = (datetime.utcnow() - last_wipe.created_at).total_seconds()
            if elapsed < WIPE_COOLDOWN_SECONDS:
                cooldown_remaining = int(WIPE_COOLDOWN_SECONDS - elapsed)
                can_wipe = False

        return WipePreviewResponse(
            users_count=users_count - admin_users,  # Non-admin users that would be deleted
            wallets_count=wallets_count,
            transactions_count=transactions_count,
            bets_count=bets_count,
            spread_bets_count=spread_bets_count,
            events_count=events_count,
            event_comments_count=event_comments_count,
            match_comments_count=match_comments_count,
            admin_settings_preserved=True,
            admin_users_preserved=True,
            can_wipe=can_wipe,
            cooldown_remaining_seconds=cooldown_remaining,
            last_wipe_at=last_wipe_at,
        )

    @staticmethod
    async def execute_wipe(
        db: AsyncSession,
        admin: User,
        request: WipeRequest,
        ip_address: Optional[str] = None,
    ) -> WipeResponse:
        """
        Execute a database wipe with safeguards.

        Raises:
            ValueError: If confirmation phrase is wrong or cooldown not elapsed
        """
        # Verify confirmation phrase
        if request.confirmation_phrase != CONFIRMATION_PHRASE:
            raise ValueError(f"Invalid confirmation phrase. Must be exactly '{CONFIRMATION_PHRASE}'")

        # Check cooldown
        preview = await WiperService.get_preview(db)
        if not preview.can_wipe:
            raise ValueError(
                f"Wipe cooldown in effect. Please wait {preview.cooldown_remaining_seconds} seconds."
            )

        deleted_counts = {}
        preserved_counts = {}

        # Get admin user IDs to preserve
        admin_user_ids = []
        if request.preserve_admin_users:
            admin_users_result = await db.execute(
                select(User.id).where(User.is_admin == True)
            )
            admin_user_ids = [row[0] for row in admin_users_result.fetchall()]
            preserved_counts["admin_users"] = len(admin_user_ids)

        # Delete in order respecting foreign keys:
        # 1. Comments (no FK dependencies)
        result = await db.execute(delete(MatchComment))
        deleted_counts["match_comments"] = result.rowcount

        result = await db.execute(delete(EventComment))
        deleted_counts["event_comments"] = result.rowcount

        # 2. Gamification data
        result = await db.execute(delete(ActivityFeed))
        deleted_counts["activity_feed"] = result.rowcount

        result = await db.execute(delete(DailyReward))
        deleted_counts["daily_rewards"] = result.rowcount

        result = await db.execute(delete(LootBox))
        deleted_counts["loot_boxes"] = result.rowcount

        result = await db.execute(delete(UserAchievement))
        deleted_counts["user_achievements"] = result.rowcount

        result = await db.execute(delete(UserStats))
        deleted_counts["user_stats"] = result.rowcount

        # 3. Bets
        result = await db.execute(delete(SpreadBet))
        deleted_counts["spread_bets"] = result.rowcount

        result = await db.execute(delete(Bet))
        deleted_counts["bets"] = result.rowcount

        # 4. Events (only if not preserving)
        if not request.preserve_events:
            result = await db.execute(delete(SportEvent))
            deleted_counts["events"] = result.rowcount
        else:
            events_count = (await db.execute(select(func.count(SportEvent.id)))).scalar() or 0
            preserved_counts["events"] = events_count

        # 5. Transactions
        if admin_user_ids:
            result = await db.execute(
                delete(Transaction).where(Transaction.user_id.notin_(admin_user_ids))
            )
        else:
            result = await db.execute(delete(Transaction))
        deleted_counts["transactions"] = result.rowcount

        # 6. Wallets (preserve admin wallets, but reset them)
        if admin_user_ids:
            # Delete non-admin wallets
            result = await db.execute(
                delete(Wallet).where(Wallet.user_id.notin_(admin_user_ids))
            )
            deleted_counts["wallets"] = result.rowcount

            # Reset admin wallets
            from decimal import Decimal
            admin_wallets = await db.execute(
                select(Wallet).where(Wallet.user_id.in_(admin_user_ids))
            )
            for wallet in admin_wallets.scalars():
                wallet.balance = Decimal("1000.00")
                wallet.escrow = Decimal("0.00")
            preserved_counts["admin_wallets"] = len(admin_user_ids)
        else:
            result = await db.execute(delete(Wallet))
            deleted_counts["wallets"] = result.rowcount

        # 7. Users (preserve admins)
        if admin_user_ids:
            result = await db.execute(
                delete(User).where(User.id.notin_(admin_user_ids))
            )
            # Reset admin user stats
            admin_users = await db.execute(
                select(User).where(User.id.in_(admin_user_ids))
            )
            for user in admin_users.scalars():
                user.total_bets = 0
                user.wins = 0
                user.losses = 0
                user.win_rate = 0.0
        else:
            result = await db.execute(delete(User))
        deleted_counts["users"] = result.rowcount

        # Log the wipe before committing
        await log_data_wipe(
            db=db,
            admin=admin,
            deleted_counts=deleted_counts,
            preserved_counts=preserved_counts,
            ip_address=ip_address,
        )

        await db.commit()

        return WipeResponse(
            success=True,
            message="Database wiped successfully",
            deleted_counts=deleted_counts,
            preserved_counts=preserved_counts,
            executed_at=datetime.utcnow(),
            executed_by=admin.username,
        )
