"""
Auth domain: magic link authentication, user management, decorators.
"""
import secrets
from datetime import datetime, 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,
    fetch_one,
    waitlist_gate,
)

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


# =============================================================================
# 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 = datetime.utcnow().isoformat()
    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 = datetime.utcnow() + timedelta(minutes=minutes)
    return await execute(
        "INSERT INTO auth_tokens (user_id, token, expires_at) VALUES (?, ?, ?)",
        (user_id, token, expires.isoformat())
    )


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, datetime.utcnow().isoformat())
    )


async def mark_token_used(token_id: int) -> None:
    """Mark token as used."""
    await execute(
        "UPDATE auth_tokens SET used_at = ? WHERE id = ?",
        (datetime.utcnow().isoformat(), 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":
        form = await request.form
        email = form.get("email", "").strip().lower()

        if not email or "@" not in email:
            await flash("Please enter a valid email address.", "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})

        await flash("Check your email for the sign-in link!", "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
@waitlist_gate("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"))

    plan = request.args.get("plan", "free")

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

        # Waitlist mode: capture email and redirect to confirmed page
        if config.WAITLIST_MODE:
            if not email or "@" not in email:
                await flash("Please enter a valid email address.", "error")
                return redirect(url_for("auth.signup", plan=selected_plan))
            await capture_waitlist_email(
                email,
                intent="signup",
                plan=selected_plan,
                ip_address=request.remote_addr,
            )
            return redirect(url_for("auth.waitlist_confirmed"))

        if not email or "@" not in email:
            await flash("Please enter a valid email address.", "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("Account already exists. Please sign in.", "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})
        await enqueue("send_welcome", {"email": email})

        await flash("Check your email to complete signup!", "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")

    if not token:
        await flash("Invalid or expired link.", "error")
        return redirect(url_for("auth.login"))

    token_data = await get_valid_token(token)

    if not token_data:
        await flash("Invalid or expired link. Please request a new one.", "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=datetime.utcnow().isoformat())

    # 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("Successfully 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."""
    session.clear()
    await flash("You have been 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("/waitlist-confirmed")
async def waitlist_confirmed():
    """Confirmation page after joining the waitlist."""
    return await render_template("waitlist_confirmed.html")


@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")
    # Drop admins straight into the panel
    if email.lower() in config.ADMIN_EMAILS:
        return redirect(url_for("admin.index"))
    return redirect(url_for("dashboard.index"))


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

    if not email:
        await flash("Email address required.", "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})

    # Always show success (don't reveal if email exists)
    await flash("If that email is registered, we've sent a new link.", "success")
    return redirect(url_for("auth.magic_link_sent", email=email))
