"""
services/captcha_service.py — CAPTCHA Service
==============================================
Replaces the emoji-based AI captcha with a reliable math CAPTCHA.
Reasons:
  - Emoji grids are ambiguous and frustrating (what counts as a "vehicle"?)
  - OpenAI returns different emoji sets each time — hard to verify
  - Math challenges are unambiguous, instant, and work offline

Flow:
  1. GET  /api/auth/captcha/challenge  → server generates math question, stores answer
  2. POST /api/auth/captcha/verify     → candidate submits answer, server checks it
  3. On success → issues a short-lived captcha_token (used in login)
"""
from __future__ import annotations
import secrets
import random
import operator
from datetime import datetime, timedelta, timezone
from config import settings
from utils import json_db as db

# ── In-memory challenge store ─────────────────────────────────────────────────
# { session_id: { answer, expires_at, used, attempts } }
_challenges: dict[str, dict] = {}

_EXPIRY_MINUTES = 10   # challenge valid for 10 minutes
_MAX_ATTEMPTS   = 5    # allow up to 5 wrong guesses before invalidating


# ── Math challenge bank ───────────────────────────────────────────────────────
# Each entry: (template, answer_fn)
# We use only addition, subtraction, and simple multiplication — solvable in 2 s

def _make_math_challenge() -> dict:
    """Generate a random arithmetic challenge. Returns {question, answer}."""
    level = random.choice(["easy", "easy", "medium"])  # weighted toward easy

    if level == "easy":
        a = random.randint(2, 15)
        b = random.randint(2, 12)
        op = random.choice(["+", "-"])
        if op == "-" and b > a:
            a, b = b, a          # keep result positive
        ans = (a + b) if op == "+" else (a - b)
        q = f"What is {a} {op} {b}?"

    else:  # medium
        variant = random.choice(["mul", "chain"])
        if variant == "mul":
            a = random.randint(2, 9)
            b = random.randint(2, 9)
            ans = a * b
            q = f"What is {a} × {b}?"
        else:
            # e.g. "What is 3 + 4 × 2?"  — evaluate left-to-right for simplicity
            a = random.randint(2, 9)
            b = random.randint(1, 5)
            c = random.randint(1, 4)
            # Present as  a + b + c  so no ambiguity about order of operations
            ans = a + b + c
            q = f"What is {a} + {b} + {c}?"

    return {"question": q, "answer": str(ans), "answer_int": ans}


def create_challenge() -> dict:
    """
    Generate a new math CAPTCHA challenge.
    Returns the question to the client — never the answer.
    """
    challenge_data = _make_math_challenge()
    session_id = secrets.token_urlsafe(24)
    expires_at = datetime.now(timezone.utc) + timedelta(minutes=_EXPIRY_MINUTES)

    _challenges[session_id] = {
        "answer":     challenge_data["answer"],       # string e.g. "9"
        "expires_at": expires_at,
        "used":       False,
        "attempts":   0,
    }

    return {
        "session_id":     session_id,
        "challenge_type": "math",
        "question":       challenge_data["question"],
        # No answer in response
    }


def refresh_challenge(session_id: str) -> dict:
    """
    Generate a fresh challenge, invalidating the old one.
    Called when the user clicks the Refresh button.
    """
    # Remove old session
    _challenges.pop(session_id, None)
    return create_challenge()


def verify_challenge(session_id: str, answer: str) -> dict:
    """
    Verify the candidate's math answer.
    Returns {verified, token, message}.
    """
    # Clean up expired sessions opportunistically
    now = datetime.now(timezone.utc)
    expired = [k for k, v in _challenges.items() if now > v["expires_at"]]
    for k in expired:
        _challenges.pop(k, None)

    challenge = _challenges.get(session_id)
    if not challenge:
        return {
            "verified": False,
            "token":    None,
            "message":  "Challenge expired or not found. Please click Refresh and try again.",
            "refresh":  True,
        }

    if now > challenge["expires_at"]:
        _challenges.pop(session_id, None)
        return {
            "verified": False,
            "token":    None,
            "message":  "Challenge expired. Please click Refresh for a new one.",
            "refresh":  True,
        }

    if challenge["used"]:
        return {
            "verified": False,
            "token":    None,
            "message":  "This challenge was already used. Please refresh for a new one.",
            "refresh":  True,
        }

    # Increment attempt counter
    challenge["attempts"] = challenge.get("attempts", 0) + 1

    if challenge["attempts"] > _MAX_ATTEMPTS:
        _challenges.pop(session_id, None)
        return {
            "verified": False,
            "token":    None,
            "message":  "Too many attempts. Please refresh for a new challenge.",
            "refresh":  True,
        }

    # Compare — strip whitespace, case-insensitive
    submitted = str(answer or "").strip().lower()
    correct   = str(challenge["answer"]).strip().lower()

    if submitted != correct:
        remaining = _MAX_ATTEMPTS - challenge["attempts"]
        return {
            "verified": False,
            "token":    None,
            "message":  f"Incorrect answer. {remaining} attempt(s) remaining.",
            "refresh":  False,
        }

    # ✅ Correct
    challenge["used"] = True
    token = secrets.token_urlsafe(32)

    db.insert(settings.SESSIONS_FILE, {
        "captcha_token": token,
        "session_id":    session_id,
        "verified":      True,
        "created_at":    db.now_iso(),
    })

    return {
        "verified": True,
        "token":    token,
        "message":  "Verification successful ✓",
        "refresh":  False,
    }


def validate_captcha_token(token: str) -> bool:
    """Check a captcha_token issued after successful verification."""
    record = db.find_one(settings.SESSIONS_FILE, captcha_token=token, verified=True)
    return record is not None