"""Authentication service for user registration, login, and verification."""
import random
from flask import current_app
from app import db
from app.models import User, UserRole, EmailVerification, PasswordReset
from app.utils import validate_email, validate_password, validate_username, validate_name
from app.services.token_service import generate_access_token, generate_refresh_token
from app.services.email_service import send_verification_email, send_password_reset_email


def register_user(first_name, last_name, username, email, password):
    """
    Register a new user and send verification email.
    
    Returns:
        dict: {'success': bool, 'message': str, 'data': dict/None}
    """
    # Validate inputs
    is_valid, error = validate_name(first_name, "First name")
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}
    
    is_valid, error = validate_name(last_name, "Last name")
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}
    
    is_valid, error = validate_username(username)
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}
    
    is_valid, error = validate_email(email)
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}
    
    is_valid, error = validate_password(password)
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}
    
    # Check if user already exists
    if User.query.filter_by(email=email).first():
        return {'success': False, 'message': 'Email already registered', 'data': None}
    
    if User.query.filter_by(username=username).first():
        return {'success': False, 'message': 'Username already taken', 'data': None}
    
    try:
        # Create user (unverified)
        user = User(
            first_name=first_name,
            last_name=last_name,
            username=username,
            email=email,
            is_email_verified=False
        )
        user.set_password(password)
        
        db.session.add(user)
        db.session.commit()
        
        # Generate and send verification code
        code = str(random.randint(100000, 999999))
        verification = EmailVerification(
            email=email,
            code=code,
            expiry_minutes=current_app.config['VERIFICATION_CODE_EXPIRY']
        )
        
        db.session.add(verification)
        db.session.commit()
        
        # Send email
        email_sent = send_verification_email(email, code)

        if not email_sent:
            current_app.logger.warning(f"Verification email failed for {email}, but registration completed")
            # Email failed but user is registered - they can request resend

        return {
            'success': True,
            'message': 'Registration successful. Please check your email for verification code.',
            'data': {'email': email}
        }
        
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Registration error: {str(e)}")
        return {'success': False, 'message': 'Registration failed. Please try again.', 'data': None}


def verify_email(email, code):
    """
    Verify user email with code and return JWT tokens.
    
    Returns:
        dict: {'success': bool, 'message': str, 'data': dict/None}
    """
    user = User.query.filter_by(email=email).first()
    if not user:
        return {'success': False, 'message': 'User not found', 'data': None}
    
    if user.is_email_verified:
        return {'success': False, 'message': 'Email already verified', 'data': None}
    
    # Get latest valid verification code
    verification = EmailVerification.query.filter_by(
        email=email,
        is_used=False
    ).order_by(EmailVerification.created_at.desc()).first()
    
    if not verification:
        return {'success': False, 'message': 'No verification code found. Please request a new one.', 'data': None}
    
    if verification.is_expired():
        return {'success': False, 'message': 'Verification code has expired. Please request a new one.', 'data': None}
    
    if verification.attempts >= current_app.config['MAX_VERIFICATION_ATTEMPTS']:
        return {'success': False, 'message': 'Too many failed attempts. Please request a new code.', 'data': None}
    
    # Check code
    if verification.code != code:
        verification.increment_attempts()
        db.session.commit()
        remaining = current_app.config['MAX_VERIFICATION_ATTEMPTS'] - verification.attempts
        return {
            'success': False,
            'message': f'Invalid verification code. {remaining} attempts remaining.',
            'data': None
        }
    
    try:
        # Mark as verified
        user.is_email_verified = True
        verification.mark_as_used()
        db.session.commit()
        
        # Generate tokens
        access_token = generate_access_token(user.id, user.role.value)
        refresh_token = generate_refresh_token(user.id)
        
        return {
            'success': True,
            'message': 'Email verified successfully',
            'data': {
                'access_token': access_token,
                'refresh_token': refresh_token,
                'user': user.to_dict_auth()
            }
        }
        
    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Email verification error: {str(e)}")
        return {'success': False, 'message': 'Verification failed. Please try again.', 'data': None}


def login_user(email_or_username, password):
    """
    Login user with email/username and password.
    
    Returns:
        dict: {'success': bool, 'message': str, 'data': dict/None}
    """
    # Find user by email or username
    user = User.query.filter(
        (User.email == email_or_username) | (User.username == email_or_username)
    ).first()
    
    if not user or not user.check_password(password):
        return {'success': False, 'message': 'Invalid credentials', 'data': None}
    
    if not user.is_email_verified:
        return {'success': False, 'message': 'Please verify your email before logging in', 'data': None}
    
    if not user.is_active:
        return {'success': False, 'message': 'Account is deactivated', 'data': None}
    
    # Generate tokens
    access_token = generate_access_token(user.id, user.role.value)
    refresh_token = generate_refresh_token(user.id)

    return {
        'success': True,
        'message': 'Login successful',
        'data': {
            'access_token': access_token,
            'refresh_token': refresh_token,
            'user': user.to_dict_auth()
        }
    }


def request_password_reset(email):
    """
    Request a password reset code to be sent to user's email.

    Returns:
        dict: {'success': bool, 'message': str, 'data': dict/None}
    """
    # Validate email format
    is_valid, error = validate_email(email)
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}

    # Find user by email
    user = User.query.filter_by(email=email).first()

    # Security: Always return success message to prevent email enumeration
    # Even if user doesn't exist, we say code was sent
    if not user:
        current_app.logger.info(f"Password reset requested for non-existent email: {email}")
        return {
            'success': True,
            'message': 'If an account exists with this email, a reset code has been sent.',
            'data': {'email': email}
        }

    if not user.is_email_verified:
        current_app.logger.info(f"Password reset requested for unverified email: {email}")
        return {
            'success': True,
            'message': 'If an account exists with this email, a reset code has been sent.',
            'data': {'email': email}
        }

    if not user.is_active:
        current_app.logger.info(f"Password reset requested for deactivated account: {email}")
        return {
            'success': True,
            'message': 'If an account exists with this email, a reset code has been sent.',
            'data': {'email': email}
        }

    try:
        # Invalidate any existing unused reset codes for this email
        existing_resets = PasswordReset.query.filter_by(
            email=email,
            is_used=False
        ).all()
        for reset in existing_resets:
            reset.mark_as_used()

        # Generate new 6-digit code
        code = str(random.randint(100000, 999999))

        # Create password reset record (15 min expiry for security)
        password_reset = PasswordReset(
            email=email,
            code=code,
            expiry_minutes=current_app.config.get('PASSWORD_RESET_CODE_EXPIRY', 15)
        )

        db.session.add(password_reset)
        db.session.commit()

        # Send email
        email_sent = send_password_reset_email(email, code)

        if not email_sent:
            current_app.logger.warning(f"Password reset email failed for {email}")

        return {
            'success': True,
            'message': 'If an account exists with this email, a reset code has been sent.',
            'data': {'email': email}
        }

    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Password reset request error: {str(e)}")
        return {'success': False, 'message': 'Failed to process request. Please try again.', 'data': None}


def verify_reset_code(email, code):
    """
    Verify password reset code and return a token for password reset.

    Returns:
        dict: {'success': bool, 'message': str, 'data': dict/None}
    """
    user = User.query.filter_by(email=email).first()
    if not user:
        return {'success': False, 'message': 'Invalid request', 'data': None}

    # Get latest valid reset code
    reset = PasswordReset.query.filter_by(
        email=email,
        is_used=False
    ).order_by(PasswordReset.created_at.desc()).first()

    if not reset:
        return {'success': False, 'message': 'No reset code found. Please request a new one.', 'data': None}

    if reset.is_expired():
        return {'success': False, 'message': 'Reset code has expired. Please request a new one.', 'data': None}

    if reset.attempts >= 5:
        return {'success': False, 'message': 'Too many failed attempts. Please request a new code.', 'data': None}

    # Check code
    if reset.code != code:
        reset.increment_attempts()
        db.session.commit()
        remaining = 5 - reset.attempts
        return {
            'success': False,
            'message': f'Invalid reset code. {remaining} attempts remaining.',
            'data': None
        }

    # Code is valid - return the token for the reset step
    return {
        'success': True,
        'message': 'Code verified successfully',
        'data': {
            'reset_token': reset.token,
            'email': email
        }
    }


def reset_password(email, reset_token, new_password):
    """
    Reset user password using verified reset token.

    Returns:
        dict: {'success': bool, 'message': str, 'data': dict/None}
    """
    # Validate new password
    is_valid, error = validate_password(new_password)
    if not is_valid:
        return {'success': False, 'message': error, 'data': None}

    # Find user
    user = User.query.filter_by(email=email).first()
    if not user:
        return {'success': False, 'message': 'Invalid request', 'data': None}

    # Find and validate reset token
    reset = PasswordReset.query.filter_by(
        email=email,
        token=reset_token,
        is_used=False
    ).first()

    if not reset:
        return {'success': False, 'message': 'Invalid or expired reset token', 'data': None}

    if reset.is_expired():
        return {'success': False, 'message': 'Reset token has expired. Please start over.', 'data': None}

    try:
        # Update password
        user.set_password(new_password)

        # Mark reset as used
        reset.mark_as_used()

        db.session.commit()

        current_app.logger.info(f"Password reset successful for user: {email}")

        return {
            'success': True,
            'message': 'Password reset successful. You can now login with your new password.',
            'data': None
        }

    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"Password reset error: {str(e)}")
        return {'success': False, 'message': 'Password reset failed. Please try again.', 'data': None}