from datetime import datetime, timedelta, timezone

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from app.database import get_db, release_db_connection
from app.dependencies import require_user_token
from app.models import PasswordResetToken, User, UserRole
from app.schemas import (
    ForgotPasswordRequest,
    LoginRequest,
    Message,
    ResetPasswordRequest,
    Token,
    UserRegisterRequest,
    UserTokenResponse,
    VerifyEmailRequest,
    VerifyOTPRequest,
)
from app.security import hash_password, verify_password
from app.services.auth_service import authenticate, issue_token_for
from app.services.email_otp import create_password_reset_flow, send_otp_email, verify_email_with_otp
from app.services.password_reset_service import find_user_by_email, send_password_reset_otp

router = APIRouter(prefix="/auth", tags=["user-auth"])


@router.post("/register", response_model=UserTokenResponse, status_code=status.HTTP_201_CREATED)
async def user_register(body: UserRegisterRequest, db: Session = Depends(get_db)):
    email = str(body.email)
    _print_user_otp_debug(f"register request received for email={email}")

    if db.query(User).filter(User.email == email).first():
        _print_user_otp_debug(f"register failed for email={email}: email already registered")
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")

    user = User(
        email=email,
        full_name=body.full_name,
        hashed_password=hash_password(body.password),
        role=UserRole.user,
        email_verified=False,
    )
    try:
        db.add(user)
        db.flush()
    except IntegrityError:
        db.rollback()
        _print_user_otp_debug(f"register failed for email={email}: email already registered")
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")

    db.commit()
    db.refresh(user)
    await _send_user_verification_otp(db, user, email=email, source="register")
    return UserTokenResponse(
        access_token=issue_token_for(user),
        email=user.email,
        email_verified=user.email_verified,
    )


@router.post("/login", response_model=UserTokenResponse)
def user_login(body: LoginRequest, db: Session = Depends(get_db)):
    user = authenticate(db, body.email, body.password)
    if not user or user.role != UserRole.user:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid user credentials")
    if not user.is_active:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Inactive account")
    from app.services.member_inactive_followup_service import touch_user_last_active

    touch_user_last_active(db, user, commit=True)
    return UserTokenResponse(
        access_token=issue_token_for(user),
        email=user.email,
        email_verified=bool(user.email_verified),
    )


@router.post("/logout", response_model=Message)
def user_logout(_: User = Depends(require_user_token)):
    return Message(message="Logged out")


_USER_FORGOT_MSG = "If an account exists for this email, a password reset code has been sent."
_USER_FORGOT_SENT_MSG = "Password reset code sent. Check your inbox (and spam folder)."
_USER_VERIFICATION_MSG = "Verification code sent. Check your inbox (and spam folder)."
_OTP_DUPLICATE_WINDOW = timedelta(seconds=60)


def _print_user_otp_debug(message: str) -> None:
    print(f"[USER SEND OTP] {message}", flush=True)


async def _send_user_verification_otp(
    db: Session,
    user: User,
    *,
    email: str,
    source: str,
) -> None:
    otp = create_password_reset_flow(db, user)
    recipient = user.email
    _print_user_otp_debug(f"{source}: generated otp={otp} for email={email}")
    release_db_connection(db)
    sent = await send_otp_email(recipient, otp)
    if not sent:
        _print_user_otp_debug(f"{source}: failed to send otp={otp} for email={email}")
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Could not send verification email. Check SMTP settings on the server.",
        )
    _print_user_otp_debug(f"{source}: sent otp={otp} successfully for email={email}")


def _as_utc(value: datetime) -> datetime:
    if value.tzinfo is None:
        return value.replace(tzinfo=timezone.utc)
    return value.astimezone(timezone.utc)


def _has_recent_active_otp(db: Session, user: User) -> bool:
    token_row = (
        db.query(PasswordResetToken)
        .filter(PasswordResetToken.user_id == user.id, PasswordResetToken.used.is_(False))
        .order_by(PasswordResetToken.created_at.desc())
        .first()
    )
    if not token_row or not token_row.created_at:
        return False

    now = datetime.now(timezone.utc)
    expires_at = _as_utc(token_row.expires_at)
    created_at = _as_utc(token_row.created_at)
    return expires_at > now and created_at >= now - _OTP_DUPLICATE_WINDOW


async def _user_forgot_password_handler(body: ForgotPasswordRequest, db: Session) -> Message:
    user = find_user_by_email(db, str(body.email), role=UserRole.user)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Email is not registered",
        )
    try:
        await send_password_reset_otp(db, user)
    except RuntimeError as exc:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=str(exc),
        ) from exc
    return Message(message=_USER_FORGOT_SENT_MSG)


async def _user_verification_otp_handler(body: ForgotPasswordRequest, db: Session) -> Message:
    email = str(body.email)
    _print_user_otp_debug(f"request received for email={email}")

    user = find_user_by_email(db, email, role=UserRole.user)
    if not user:
        _print_user_otp_debug(f"failed for email={email}: email is not registered")
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Email is not registered",
        )
    if user.email_verified:
        _print_user_otp_debug(
            f"email={email} is already verified; sending password reset OTP instead"
        )
        return await _user_forgot_password_handler(body, db)
    if _has_recent_active_otp(db, user):
        _print_user_otp_debug(
            f"recent active OTP already exists for email={email}; current otp={user.otp}"
        )
        return Message(message=_USER_VERIFICATION_MSG)

    await _send_user_verification_otp(db, user, email=email, source="send-otp")
    return Message(message=_USER_VERIFICATION_MSG)


@router.post("/forgot-password", response_model=Message)
async def user_forgot_password(body: ForgotPasswordRequest, db: Session = Depends(get_db)):
    return await _user_forgot_password_handler(body, db)


@router.post("/send-otp", response_model=Message)
async def user_send_otp(body: ForgotPasswordRequest, db: Session = Depends(get_db)):
    """Send/resend a user OTP without duplicating the registration verification email."""
    return await _user_verification_otp_handler(body, db)


@router.post("/verify-email", response_model=UserTokenResponse)
def user_verify_email(body: VerifyEmailRequest, db: Session = Depends(get_db)):
    """Verify email once; sets users.email_verified = true in the database."""
    try:
        user = verify_email_with_otp(db, email=str(body.email), otp=body.otp.strip())
    except ValueError as e:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
    return UserTokenResponse(
        access_token=issue_token_for(user),
        email=user.email,
        email_verified=True,
    )


@router.post("/reset-password", response_model=Message)
def user_reset_password(body: ResetPasswordRequest, db: Session = Depends(get_db)):
    user = find_user_by_email(db, str(body.email), role=UserRole.user)
    if not user:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request")

    token_row = (
        db.query(PasswordResetToken)
        .filter(PasswordResetToken.user_id == user.id, PasswordResetToken.used.is_(False))
        .order_by(PasswordResetToken.created_at.desc())
        .first()
    )
    if not token_row or not verify_password(body.otp.strip(), token_row.otp_hash):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or expired OTP")

    expires = token_row.expires_at
    if expires.tzinfo is None:
        expires = expires.replace(tzinfo=timezone.utc)
    if expires < datetime.now(timezone.utc):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="OTP expired")

    user.hashed_password = hash_password(body.new_password)
    token_row.used = True
    db.commit()
    return Message(message="Password updated")


@router.post("/verify-otp", response_model=Message)
def user_verify_otp(body: VerifyOTPRequest, db: Session = Depends(get_db)):
    user = find_user_by_email(db, str(body.email), role=UserRole.user)
    if not user:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request")

    if not user.otp or user.otp != body.otp.strip():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="insert the correct otp")

    token_row = (
        db.query(PasswordResetToken)
        .filter(
            PasswordResetToken.user_id == user.id,
            PasswordResetToken.used.is_(False),
        )
        .order_by(PasswordResetToken.created_at.desc())
        .first()
    )
    if not token_row:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="insert the correct otp")

    expires = token_row.expires_at
    if expires.tzinfo is None:
        expires = expires.replace(tzinfo=timezone.utc)
    if expires < datetime.now(timezone.utc):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="OTP expired")

    return Message(message="OTP verified successfully")
