from __future__ import annotations
import asyncio
from dataclasses import dataclass
from enum import Enum
from typing import Awaitable, Callable, Optional, List

import discord
from ..types import GameCtx
from .actions import Action, delete_message
from .strikes import StrikeTable

class Severity(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    KICK = "kick"   # "kick from game" hook (library leaves it to host game)

Predicate = Callable[[discord.Message, GameCtx], bool | Awaitable[bool]]

@dataclass
class Rule:
    name: str
    predicate: Predicate
    severity: Severity = Severity.MEDIUM
    action: Action = None

    # strikes config (optional)
    strikes: int = 0           # if >0, enable strike accumulation
    window_seconds: int = 120  # strike window
    kick_on_strikes: int = 0   # if >0, emit "kick" event when reached

    async def check(self, msg: discord.Message, ctx: GameCtx) -> bool:
        res = self.predicate(msg, ctx)
        if asyncio.iscoroutine(res):
            return bool(await res)
        return bool(res)

class AntiCheat:
    """
    Engine:
    - rules
    - strike system
    - actions
    - emits "kick" via callback (you connect it to your game logic)
    """
    def __init__(self):
        self.rules: List[Rule] = []
        self.enabled = True
        self.strikes = StrikeTable()
        self.on_kick: Optional[Callable[[discord.Message, GameCtx, Rule], Awaitable[None]]] = None

    def rule(
        self,
        name: str,
        severity: Severity = Severity.MEDIUM,
        action: Optional[Action] = None,
        *,
        strikes: int = 0,
        window_seconds: int = 120,
        kick_on_strikes: int = 0,
    ):
        def deco(fn: Predicate):
            self.rules.append(Rule(
                name=name,
                predicate=fn,
                severity=severity,
                action=action or delete_message(),
                strikes=strikes,
                window_seconds=window_seconds,
                kick_on_strikes=kick_on_strikes,
            ))
            return fn
        return deco

    def add_rule(self, rule: Rule):
        self.rules.append(rule)

    async def handle_message(self, msg: discord.Message, ctx: GameCtx) -> Optional[Rule]:
        if not self.enabled:
            return None

        for rule in self.rules:
            try:
                if await rule.check(msg, ctx):
                    if rule.action:
                        await rule.action.runner(msg, ctx)

                    # strikes path
                    if rule.strikes and ctx.player_id:
                        n = self.strikes.add(ctx.guild_id, ctx.channel_id, ctx.game_name, ctx.player_id, rule.window_seconds)
                        # optional: escalate
                        if rule.kick_on_strikes and n >= rule.kick_on_strikes:
                            if self.on_kick:
                                await self.on_kick(msg, ctx, rule)

                    # direct kick severity hook
                    if rule.severity == Severity.KICK and self.on_kick:
                        await self.on_kick(msg, ctx, rule)

                    return rule
            except Exception:
                continue
        return None