import re

from pyramid.view import view_config

from pyramid.httpexceptions import HTTPFound

from ..models.user import get_or_create_user_by_email

from ..models.comment import Comment, get_comment_by_id

from ..models.product import get_product_by_id

from ..lib.mail import send_verification_digits_to_email

from ..lib.mentions import notify_mentioned_users

from . import get_referer_or_home

from urllib.parse import urlencode

import logging

log = logging.getLogger(__name__)


@view_config(route_name="log-out")
def log_out(request):
    """log out the user, redirect to back to referer."""
    request.session["authenticated_user_id"] = None
    request.session["active_cart_id"] = None
    return HTTPFound("/")


@view_config(route_name="join-or-log-in", renderer="join-or-log-in.j2")
def join_or_log_in(request):
    """
    This view handles user registration, verification, and log in.
    It uses "password-less" authentication by sending 6 digit
    OTP (one-time-password) tokens to email addresses to verify both the email
    address & to authenticate the device displaying the challenge input field.
    """
    _email_regex = re.compile(r"^[^@]+@[^@]+\.[^.@]+$")
    raw_otp = request.params.get("raw-otp", "")
    email = request.params.get("email", "")

    if email and _email_regex.match(email) is None:
        email = ""
        request.session.flash(("That email address is invalid.", "error"))

    if request.spam:
        return request.spam

    if request.user and request.user.authenticated:
        request.session.flash(("You are already authenticated!", "info"))
        return HTTPFound(get_referer_or_home(request))

    elif email:
        user = get_or_create_user_by_email(request.dbsession, email)

        if user.throttle_password():
            msg = (
                f"Check email for a 6 digit verification code to log in. {user.email}",
                "info",
            )
        else:
            raw_otp = user.new_password()
            request.dbsession.add(user)
            request.dbsession.flush()
            send_verification_digits_to_email(request, user.email, raw_otp)
            msg = (
                f"Check email for a 6 digit verification code to log in. {user.email}",
                "info",
            )

        request.session.flash(msg)

        # Store 'unauthed_email' in session instead of passing via query param
        request.session["unauthed_email"] = email

        # Redirect to verification challenge without 'unauthed_email' in query
        return HTTPFound("/verification-challenge")

    return {
        "title": "Join or Log In",
    }


@view_config(route_name="verification-challenge", renderer="verification-challenge.j2")
def verification_challenge(request):
    # Retrieve 'unauthed_email' from the session
    unauthed_email = request.session.get("unauthed_email", "")

    if not unauthed_email:
        # If 'unauthed_email' is not in session, redirect to login/join
        request.session.flash(("Email is required for verification.", "error"))
        return HTTPFound("/join-or-log-in")

    user = get_or_create_user_by_email(request.dbsession, unauthed_email)

    if "submit" in request.params:
        raw_otp = request.params.get("raw-otp", "")
        if raw_otp and user.check_password(raw_otp):
            user.verified = True
            name = user.full_name or ""
            msg = (f"Welcome {name}", "success")
            _ = request.session.pop("unauthed_email", None)
            request.session["authenticated_user_id"] = str(user.id)
            request.session.flash(msg)

            # Merge carts (session_cart may be in-memory if no items were added)
            request.active_cart.merge_in_cart(request.session_cart)
            if request.session_cart.dbsession is not None:
                request.dbsession.delete(request.session_cart)
            request.session["active_cart_id"] = str(request.active_cart.id)
            request.dbsession.add(user)
            request.dbsession.add(request.active_cart)
            request.dbsession.flush()

            # Check for pending comment from anonymous comment flow
            pending = request.session.pop("pending_comment", None)
            if pending:
                return _create_pending_comment(request, user, pending)

            return HTTPFound(
                "/cart" if request.active_cart.count > 0 else request.route_url("home")
            )

        else:
            msg = ("Invalid Verification Code", "error")
            request.session.flash(msg)

    return {
        "title": "Please Enter Verification Code",
        "raw_otp": request.params.get("raw-otp", ""),
    }


def _create_pending_comment(request, user, pending):
    """Create a comment that was stored in session before OTP verification."""
    product = get_product_by_id(request.dbsession, pending["product_id"])
    if not product:
        request.session.flash(("Product not found, comment not posted.", "error"))
        return HTTPFound(request.route_url("home"))

    shop = product.shop

    if not shop.comments_enabled:
        request.session.flash(("Comments are disabled for this shop.", "error"))
        return HTTPFound(product.absolute_url(request))

    if shop.comments_require_purchase:
        from ..models.user_product import UserProduct

        user_product = (
            request.dbsession.query(UserProduct)
            .filter(
                UserProduct.user_id == user.id,
                UserProduct.product_id == product.id,
            )
            .first()
        )
        if not user_product:
            request.session.flash(
                ("You must purchase this product to leave a comment.", "error")
            )
            return HTTPFound(product.absolute_url(request))

    comment = Comment()
    comment.product_id = product.id
    comment.user_id = user.id
    comment.set_data(pending["data"])

    if shop.comments_require_approval:
        if shop.is_owner(user) or shop.is_editor(user):
            comment.approved = True
        else:
            comment.approved = False
    else:
        comment.approved = True

    parent_id = pending.get("parent_id")
    if parent_id:
        parent_comment = get_comment_by_id(request.dbsession, parent_id)
        if parent_comment and str(parent_comment.product_id) == str(product.id):
            comment.parent_id = parent_id
            comment.root_id = parent_comment.root_id or parent_comment.id
            comment.recompute_depth()
    else:
        comment.root_id = comment.id
        comment.graph_depth = 0

    request.dbsession.add(comment)
    request.dbsession.flush()

    notify_mentioned_users(request, comment, user)

    if comment.approved:
        request.session.flash(("Comment posted successfully.", "success"))
    else:
        request.session.flash(("Comment submitted for approval.", "success"))

    product_url = product.absolute_url(request)
    return HTTPFound(location=f"{product_url}#comment-{comment.id}")
