from flask import Blueprint, Response, redirect, render_template, request, session
from sqlalchemy import select

from l4d2web.auth import (
    current_user,
    hash_password,
    require_login,
    validate_new_password,
    verify_password,
)
from l4d2web.db import session_scope
from l4d2web.models import User, now_utc
from l4d2web.services.rate_limit import check_rate_limit


bp = Blueprint("profile", __name__)


_ERROR_MESSAGES = {
    "fields_required": "Fill in all three fields.",
    "mismatch": "New password and confirmation do not match.",
    "too_short": "New password must be at least 8 characters.",
    "empty": "New password must not be empty.",
    "wrong_current": "Current password is incorrect.",
}


PROFILE_PW_RATE_LIMIT_WINDOW_SECONDS = 60.0
PROFILE_PW_RATE_LIMIT_MAX_ATTEMPTS = 10
PROFILE_PW_ATTEMPTS_BY_IP: dict[str, list[float]] = {}


def reset_profile_password_rate_limits() -> None:
    PROFILE_PW_ATTEMPTS_BY_IP.clear()


def _redirect_with_error(error_key: str) -> Response:
    return redirect(f"/profile?error={error_key}")


@bp.get("/profile")
@require_login
def profile_page() -> str:
    error_key = request.args.get("error", "")
    success = request.args.get("success") == "1"
    return render_template(
        "profile.html",
        error_message=_ERROR_MESSAGES.get(error_key, ""),
        success=success,
    )


@bp.post("/profile/password")
@require_login
def profile_password_change() -> Response:
    remote_addr = request.remote_addr or "unknown"
    if check_rate_limit(
        PROFILE_PW_ATTEMPTS_BY_IP,
        remote_addr,
        window=PROFILE_PW_RATE_LIMIT_WINDOW_SECONDS,
        max_attempts=PROFILE_PW_RATE_LIMIT_MAX_ATTEMPTS,
    ):
        return Response("too many attempts", status=429)

    current_password = request.form.get("current_password", "")
    new_password = request.form.get("new_password", "")
    confirm_new_password = request.form.get("confirm_new_password", "")

    if not current_password or not new_password or not confirm_new_password:
        return _redirect_with_error("fields_required")
    if new_password != confirm_new_password:
        return _redirect_with_error("mismatch")
    policy_error = validate_new_password(new_password)
    if policy_error is not None:
        error_key = "empty" if policy_error == "password must not be empty" else "too_short"
        return _redirect_with_error(error_key)

    actor = current_user()
    assert actor is not None  # @require_login

    with session_scope() as db:
        user = db.scalar(select(User).where(User.id == actor.id))
        if user is None or not verify_password(current_password, user.password_digest):
            return _redirect_with_error("wrong_current")
        user.password_digest = hash_password(new_password)
        user.password_changed_at = now_utc()
        new_marker = user.password_changed_at.isoformat()

    session["pw_changed_at"] = new_marker

    return redirect("/profile?success=1")
