"""
Auth domain: magic link authentication, user management, decorators.
"""
import secrets
from functools import wraps
from datetime import datetime, timedelta
from pathlib import Path

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

from ..core import config, fetch_one, fetch_all, execute, csrf_protect

# 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)
    )


async def get_user_with_subscription(user_id: int) -> dict | None:
    """Get user with their active subscription info."""
    return await fetch_one(
        """
        SELECT u.*, s.plan, s.status as sub_status, s.current_period_end
        FROM users u
        LEFT JOIN subscriptions s ON s.user_id = u.id AND s.status = 'active'
        WHERE u.id = ? AND u.deleted_at IS NULL
        """,
        (user_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 subscription_required(plans: list[str] = None):
    """Require active subscription, optionally of specific plan(s)."""
    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"))
            
            user = await get_user_with_subscription(g.user["id"])
            if not user or not user.get("plan"):
                await flash("Please subscribe to access this feature.", "warning")
                return redirect(url_for("billing.pricing"))
            
            if plans and user["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
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")
        
        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"]
    
    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("/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
    
    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."""
    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))
