"""
Auth domain: magic link authentication, user management, decorators.
"""

import secrets
from datetime import timedelta
from functools import wraps
from pathlib import Path

from quart import Blueprint, flash, g, redirect, render_template, request, session, url_for

from ..core import (
    capture_waitlist_email,
    config,
    csrf_protect,
    execute,
    feature_gate,
    fetch_one,
    is_disposable_email,
    is_flag_enabled,
    utcnow,
    utcnow_iso,
)
from ..i18n import SUPPORTED_LANGS, get_translations

# Blueprint with its own template folder
bp = Blueprint(
    "auth",
    __name__,
    template_folder=str(Path(__file__).parent / "templates"),
    url_prefix="/auth",
)


@bp.before_request
async def pull_auth_lang() -> None:
    """Detect language for auth routes (no URL prefix available).

    Priority: lang cookie → Accept-Language header → fallback 'en'.
    """
    lang = request.cookies.get("lang", "")
    if lang not in SUPPORTED_LANGS:
        accept = request.headers.get("Accept-Language", "")
        lang = accept[:2].lower() if accept else ""
    if lang not in SUPPORTED_LANGS:
        lang = "en"
    g.lang = lang


# =============================================================================
# SQL Queries
# =============================================================================


async def get_user_by_id(user_id: int) -> dict | None:
    """Get user by ID."""
    return await fetch_one("SELECT * FROM users WHERE id = ? AND deleted_at IS NULL", (user_id,))


async def get_user_by_email(email: str) -> dict | None:
    """Get user by email."""
    return await fetch_one(
        "SELECT * FROM users WHERE email = ? AND deleted_at IS NULL", (email.lower(),)
    )


async def create_user(email: str) -> int:
    """Create new user, return ID."""
    now = utcnow_iso()
    return await execute(
        "INSERT INTO users (email, created_at) VALUES (?, ?)", (email.lower(), now)
    )


async def update_user(user_id: int, **fields) -> None:
    """Update user fields."""
    if not fields:
        return
    sets = ", ".join(f"{k} = ?" for k in fields.keys())
    values = list(fields.values()) + [user_id]
    await execute(f"UPDATE users SET {sets} WHERE id = ?", tuple(values))


async def create_auth_token(user_id: int, token: str, minutes: int = None) -> int:
    """Create auth token for user."""
    minutes = minutes or config.MAGIC_LINK_EXPIRY_MINUTES
    expires = utcnow() + timedelta(minutes=minutes)
    return await execute(
        "INSERT INTO auth_tokens (user_id, token, expires_at) VALUES (?, ?, ?)",
        (user_id, token, expires.strftime("%Y-%m-%d %H:%M:%S")),
    )


async def get_valid_token(token: str) -> dict | None:
    """Get token if valid and not expired."""
    return await fetch_one(
        """
        SELECT at.*, u.email 
        FROM auth_tokens at
        JOIN users u ON u.id = at.user_id
        WHERE at.token = ? AND at.expires_at > ? AND at.used_at IS NULL
        """,
        (token, utcnow_iso()),
    )


async def mark_token_used(token_id: int) -> None:
    """Mark token as used."""
    await execute(
        "UPDATE auth_tokens SET used_at = ? WHERE id = ?", (utcnow_iso(), token_id)
    )


# =============================================================================
# Decorators
# =============================================================================


def login_required(f):
    """Require authenticated user."""

    @wraps(f)
    async def decorated(*args, **kwargs):
        if not g.get("user"):
            await flash("Please sign in to continue.", "warning")
            return redirect(url_for("auth.login", next=request.path))
        return await f(*args, **kwargs)

    return decorated


def role_required(*roles):
    """Require user to have at least one of the given roles."""

    def decorator(f):
        @wraps(f)
        async def decorated(*args, **kwargs):
            if not g.get("user"):
                await flash("Please sign in to continue.", "warning")
                return redirect(url_for("auth.login", next=request.path))
            user_roles = g.user.get("roles", [])
            if not any(r in user_roles for r in roles):
                await flash("You don't have permission to access that page.", "error")
                return redirect(url_for("dashboard.index"))
            return await f(*args, **kwargs)

        return decorated

    return decorator


async def grant_role(user_id: int, role: str) -> None:
    """Grant a role to a user (idempotent)."""
    await execute(
        "INSERT OR IGNORE INTO user_roles (user_id, role) VALUES (?, ?)",
        (user_id, role),
    )


async def revoke_role(user_id: int, role: str) -> None:
    """Revoke a role from a user."""
    await execute(
        "DELETE FROM user_roles WHERE user_id = ? AND role = ?",
        (user_id, role),
    )


async def ensure_admin_role(user_id: int, email: str) -> None:
    """Grant admin role if email is in ADMIN_EMAILS."""
    if email.lower() in config.ADMIN_EMAILS:
        await grant_role(user_id, "admin")


def subscription_required(
    plans: list[str] = None,
    allowed: tuple[str, ...] = ("active", "on_trial", "cancelled"),
):
    """Require active subscription, optionally of specific plan(s) and/or statuses.

    Reads from g.subscription (eager-loaded in load_user) — zero extra queries.
    """

    def decorator(f):
        @wraps(f)
        async def decorated(*args, **kwargs):
            if not g.get("user"):
                await flash("Please sign in to continue.", "warning")
                return redirect(url_for("auth.login"))

            sub = g.get("subscription")
            if not sub or sub["status"] not in allowed:
                await flash("Please subscribe to access this feature.", "warning")
                return redirect(url_for("billing.pricing"))

            if plans and sub["plan"] not in plans:
                await flash(f"This feature requires a {' or '.join(plans)} plan.", "warning")
                return redirect(url_for("billing.pricing"))

            return await f(*args, **kwargs)

        return decorated

    return decorator


# =============================================================================
# Routes
# =============================================================================


@bp.route("/login", methods=["GET", "POST"])
@csrf_protect
async def login():
    """Login page - request magic link."""
    if g.get("user"):
        return redirect(url_for("dashboard.index"))

    if request.method == "POST":
        _t = get_translations(g.lang)
        form = await request.form
        email = form.get("email", "").strip().lower()

        if not email or "@" not in email:
            await flash(_t["auth_flash_invalid_email"], "error")
            return redirect(url_for("auth.login"))
        if is_disposable_email(email):
            await flash(_t["auth_flash_disposable_email"], "error")
            return redirect(url_for("auth.login"))

        # Get or create user
        user = await get_user_by_email(email)
        if not user:
            user_id = await create_user(email)
        else:
            user_id = user["id"]

        # Create magic link token
        token = secrets.token_urlsafe(32)
        await create_auth_token(user_id, token)

        # Queue email
        from ..worker import enqueue

        await enqueue("send_magic_link", {"email": email, "token": token, "lang": g.lang})

        await flash(_t["auth_flash_login_sent"], "success")
        return redirect(url_for("auth.magic_link_sent", email=email))

    return await render_template("login.html")


@bp.route("/signup", methods=["GET", "POST"])
@csrf_protect
@feature_gate("payments", "waitlist.html", plan=lambda: request.args.get("plan", "free"))
async def signup():
    """Signup page - same as login but with different messaging."""
    if g.get("user"):
        return redirect(url_for("dashboard.index"))

    # Waitlist POST handling (when payments flag is disabled)
    if not await is_flag_enabled("payments") and request.method == "POST":
        _t = get_translations(g.lang)
        form = await request.form
        email = form.get("email", "").strip().lower()
        plan = form.get("plan", "signup")

        if not email or "@" not in email:
            await flash(_t["auth_flash_invalid_email"], "error")
            return redirect(url_for("auth.signup"))

        await capture_waitlist_email(email, intent=plan)
        return await render_template("waitlist_confirmed.html", email=email)

    # Normal signup flow below
    plan = request.args.get("plan", "free")

    if request.method == "POST":
        _t = get_translations(g.lang)
        form = await request.form
        email = form.get("email", "").strip().lower()
        selected_plan = form.get("plan", "free")

        if not email or "@" not in email:
            await flash(_t["auth_flash_invalid_email"], "error")
            return redirect(url_for("auth.signup", plan=selected_plan))
        if is_disposable_email(email):
            await flash(_t["auth_flash_disposable_email"], "error")
            return redirect(url_for("auth.signup", plan=selected_plan))

        # Check if user exists
        user = await get_user_by_email(email)
        if user:
            await flash(_t["auth_flash_account_exists"], "info")
            return redirect(url_for("auth.login"))

        # Create user
        user_id = await create_user(email)

        # Create magic link token
        token = secrets.token_urlsafe(32)
        await create_auth_token(user_id, token)

        # Queue emails
        from ..worker import enqueue

        await enqueue("send_magic_link", {"email": email, "token": token, "lang": g.lang})
        await enqueue("send_welcome", {"email": email, "lang": g.lang})

        await flash(_t["auth_flash_signup_sent"], "success")
        return redirect(url_for("auth.magic_link_sent", email=email))

    return await render_template("signup.html", plan=plan)


@bp.route("/verify")
async def verify():
    """Verify magic link token."""
    token = request.args.get("token")

    _t = get_translations(g.lang)

    if not token:
        await flash(_t["auth_flash_invalid_token"], "error")
        return redirect(url_for("auth.login"))

    token_data = await get_valid_token(token)

    if not token_data:
        await flash(_t["auth_flash_invalid_token_detail"], "error")
        return redirect(url_for("auth.login"))

    # Mark token as used
    await mark_token_used(token_data["id"])

    # Update last login
    await update_user(token_data["user_id"], last_login_at=utcnow_iso())

    # Set session
    session.permanent = True
    session["user_id"] = token_data["user_id"]

    # Auto-grant admin role if email is in ADMIN_EMAILS
    await ensure_admin_role(token_data["user_id"], token_data["email"])

    await flash(_t["auth_flash_signed_in"], "success")

    # Redirect to intended page or dashboard
    next_url = request.args.get("next", url_for("dashboard.index"))
    return redirect(next_url)


@bp.route("/logout", methods=["POST"])
@csrf_protect
async def logout():
    """Log out user."""
    _t = get_translations(g.lang)
    session.clear()
    await flash(_t["auth_flash_signed_out"], "info")
    return redirect(url_for("public.landing"))


@bp.route("/magic-link-sent")
async def magic_link_sent():
    """Confirmation page after magic link sent."""
    email = request.args.get("email", "")
    return await render_template("magic_link_sent.html", email=email)


@bp.route("/dev-login")
async def dev_login():
    """Instant login for development. Only works in DEBUG mode."""
    if not config.DEBUG:
        return "Not available", 404

    email = request.args.get("email", "dev@localhost")

    user = await get_user_by_email(email)
    if not user:
        user_id = await create_user(email)
    else:
        user_id = user["id"]

    session.permanent = True
    session["user_id"] = user_id

    # Auto-grant admin role if email is in ADMIN_EMAILS
    await ensure_admin_role(user_id, email)

    await flash(f"Dev login as {email}", "success")
    return redirect(url_for("dashboard.index"))


@bp.route("/resend", methods=["POST"])
@csrf_protect
async def resend():
    """Resend magic link."""
    _t = get_translations(g.lang)
    form = await request.form
    email = form.get("email", "").strip().lower()

    if not email:
        await flash(_t["auth_flash_invalid_email"], "error")
        return redirect(url_for("auth.login"))

    user = await get_user_by_email(email)
    if user:
        token = secrets.token_urlsafe(32)
        await create_auth_token(user["id"], token)

        from ..worker import enqueue

        await enqueue("send_magic_link", {"email": email, "token": token, "lang": g.lang})

    # Always show success (don't reveal if email exists)
    await flash(_t["auth_flash_resend_sent"], "success")
    return redirect(url_for("auth.magic_link_sent", email=email))
