# server/functions.py

from functools import wraps
from flask_login import current_user
from flask import jsonify, request
from models import (
    EnglishRegistrationReceipt, EnglishStudent, 
    PrivateStudent, PrivateSession, PrivateEnrollmentReceipt, EnglishSession
)
import re
from sqlalchemy import func
from sqlalchemy.exc import IntegrityError
import time
from gate_pass_file import colors


def requires_roles(system_type, *roles):
    """
    Decorator to check if current user has required roles for a specific system.
    
    Args:
        system_type: 'english' or 'private'
        *roles: Role names to check
    """
    def decorator(f):
        @wraps(f)
        def wrapped(*args, **kwargs):
            if not current_user.is_authenticated:
                return jsonify({'error': 'Authentication required.'}), 401
            
            # Get roles based on system type
            if system_type == 'english':
                user_roles = [role.name for role in current_user.english_roles]
            elif system_type == 'private':
                user_roles = [role.name for role in current_user.private_roles]
            else:
                return jsonify({'error': f'Invalid system_type: {system_type}. Must be "english" or "private".'}), 500
            
            if not any(role in user_roles for role in roles):
                role_list = ', '.join(roles)
                return jsonify({'error': f'Requires {system_type} role(s): `{role_list}`'}), 403
            return f(*args, **kwargs)
        return wrapped
    return decorator

def require_password_changed(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if current_user.is_authenticated and current_user.must_change_password:
            # Allow only the change-password endpoint
            if request.endpoint != 'change_password':
                return jsonify({'error': 'You must change your password first.'}), 403
        return f(*args, **kwargs)
    return decorated_function

def get_student_by_search(system_type, query, include_deleted=False):
    """Search students by name, phone, or email (case-insensitive)"""
    if not query:
        return []
    
    search_term = f"%{query}%"
    if system_type == "english":
        students_query = EnglishStudent.query.filter(
            (func.lower(EnglishStudent.name).like(func.lower(search_term))) |
            (func.lower(EnglishStudent.phone).like(func.lower(search_term))) |
            (func.lower(EnglishStudent.email).like(func.lower(search_term)))
        )
        if not include_deleted:
            students_query = students_query.filter_by(deleted=False)
        students = students_query.all()

    elif system_type == "private":    
        students_query = PrivateStudent.query.filter(
            (func.lower(PrivateStudent.name).like(func.lower(search_term))) |
            (func.lower(PrivateStudent.phone).like(func.lower(search_term))) |
            (func.lower(PrivateStudent.email).like(func.lower(search_term)))
        )
        if not include_deleted:
            students_query = students_query.filter_by(deleted=False)
        students = students_query.all()
        
    else:
        students = []
        
    return students

def generate_receipt_number():
    """Generate unique receipt number"""
    import uuid
    return f"RCP-{uuid.uuid4().hex[:8].upper()}"

def generate_english_registration_receipt_number(class_code):
    """Generate sequential receipt number with format: RCP-0001NC, RCP-0001PY for payment-only"""

    # For payment-only receipts, use 'PY' suffix instead of class_code
    suffix = class_code if class_code else 'PY'

    # Get the last receipt for this suffix
    last_receipt = EnglishRegistrationReceipt.query.filter(
        EnglishRegistrationReceipt.receipt_number.like(f'RCP-%{suffix}')
    ).order_by(EnglishRegistrationReceipt.created_at.desc()).first()
    
    if last_receipt and last_receipt.receipt_number:
        # Extract the numeric part from the last receipt number
        # Format: RCP-0001NC -> numeric part is 0001
        match = re.match(r'^RCP-(\d+)([A-Z]+)$', last_receipt.receipt_number)
        if match:
            last_number = int(match.group(1))
            new_number = last_number + 1
        else:
            new_number = 1
    else:
        new_number = 1
    
    # Format with 4 digits (0001, 0002, etc.)
    formatted_number = f"RCP-{new_number:04d}{class_code}"
    
    return formatted_number

def build_english_registration_receipt(
    class_code,
    student_id,
    level_name,
    level_order,
    session_id,
    form_fee_amount,
    registration_fee_amount,
    actual_discount,
    discount_reason,
    total_amount,
    payment_method,
    cashier_id,
    receipt_type,
    additional_amount,
    additional_reason,
    charge_form_fee=True,
    charge_registration_fee=True,
    payment_splits=None, 
):
    """Build receipt object with retry-safe receipt number generation"""
    
    # Determine actual receipt type
    actual_receipt_type = receipt_type
    if not class_code or class_code == '':
        actual_receipt_type = 'payment_only'
    
    receipt_number = generate_receipt_number()

    # Determine pass number for enrollment/class_change only
    pass_number = None
    if receipt_type in ('enrollment', 'class_change'):
        pass_number = get_next_english_pass_number(session_id)

    receipt = EnglishRegistrationReceipt(
        receipt_number=receipt_number,
        student_id=student_id,
        class_code=class_code if class_code else None,  # Store as None for payment-only
        level_name=level_name,
        level_order=level_order,
        session_id=session_id,
        form_fee_amount=form_fee_amount,
        registration_fee_amount=registration_fee_amount,
        discount_amount=actual_discount,
        discount_reason=discount_reason,
        total_amount=total_amount,
        payment_method=payment_method,
        cashier_id=cashier_id,
        receipt_type=actual_receipt_type,
        charge_form_fee=charge_form_fee,
        charge_registration_fee=charge_registration_fee,
        pass_number=pass_number,
        additional_amount=additional_amount,
        additional_reason=additional_reason,
        payment_splits=payment_splits
    )

    return receipt



def get_next_pass_number(session_id):
    """Return the next sequential pass number for a session (1-based)."""
    last_receipt = PrivateEnrollmentReceipt.query.filter_by(session_id=session_id)\
        .order_by(PrivateEnrollmentReceipt.pass_number.desc()).first()
    return (last_receipt.pass_number + 1) if last_receipt else 1

def get_next_english_pass_number(session_id):
    """Return next sequential pass number (1-based) for a session."""
    last_receipt = EnglishRegistrationReceipt.query.filter(
        EnglishRegistrationReceipt.session_id == session_id,
        EnglishRegistrationReceipt.pass_number.isnot(None)
    ).order_by(EnglishRegistrationReceipt.pass_number.desc()).first()
    return (last_receipt.pass_number + 1) if last_receipt else 1

# def get_next_color_index():
#     last_session = PrivateSession.query.order_by(PrivateSession.id.desc()).first()

#     if not last_session or not last_session.pass_color:
#         return 0

#     last_index = ord(last_session.pass_color) - ord('a')
#     return (last_index + 1) % len(colors)

def get_next_color_index():
    last_session = (
        PrivateSession.query
        .order_by(PrivateSession.created_at.desc())
        .first()
    )

    if not last_session or not last_session.pass_color:
        return 0

    return int(last_session.pass_color) + 1

def get_next_color_index_english():
    last_session = (
        EnglishSession.query
        .order_by(EnglishSession.created_at.desc())
        .first()
    )

    if not last_session or not last_session.pass_color:
        return 0

    return int(last_session.pass_color) + 1

def format_session_label(session_obj):
    if not session_obj:
        return ''
    label = session_obj.name
    if session_obj.time_info and session_obj.time_info.time_text:
        label += f' • {session_obj.time_info.time_text}'
    return label

def is_valid_email(email: str) -> bool:
    EMAIL_REGEX = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
    if not email:
        return False
    return re.match(EMAIL_REGEX, email) is not None


private_school_classes_to_code = {
    'Pre-Nursery': 'pre_nursery',
    'Nursery 1': 'nursery_1',
    'Nursery 2': 'nursery_2',
    'Primary 1': 'primary_1',
    'Primary 2': 'primary_2',
    'Primary 3': 'primary_3',
    'Primary 4': 'primary_4',
    'Primary 5': 'primary_5',
    'JSS 1': 'jss_1',
    'JSS 2': 'jss_2',
    'JSS 3': 'jss_3',
    'SSS 1': 'sss_1',
    'SSS 2': 'sss_2',
    'SSS 3': 'sss_3'
}

private_school_code_to_display = {v: k for k, v in private_school_classes_to_code.items()}


SHORT_CODE_TO_CLASS_CODE = {
    'PN': 'pre_nursery',
    'N1': 'nursery_1',
    'N2': 'nursery_2',
    'P1': 'primary_1',
    'P2': 'primary_2',
    'P3': 'primary_3',
    'P4': 'primary_4',
    'P5': 'primary_5',
    'JSS1': 'jss_1',
    'JSS2': 'jss_2',
    'JSS3': 'jss_3',
    'SSS1': 'sss_1',
    'SSS2': 'sss_2',
    'SSS3': 'sss_3'
}

# Reverse mapping for display
CLASS_CODE_TO_SHORT = {v: k for k, v in SHORT_CODE_TO_CLASS_CODE.items()}


def get_class_name_from_code(code):
    """Helper: given class code (e.g., 'nursery_1'), return display name"""
    # This is a reverse lookup from your private_school_classes_to_code dict
    private_school_classes_to_code = {
        'Pre-Nursery': 'pre_nursery',
        'Nursery 1': 'nursery_1',
        'Nursery 2': 'nursery_2',
        'Primary 1': 'primary_1',
        'Primary 2': 'primary_2',
        'Primary 3': 'primary_3',
        'Primary 4': 'primary_4',
        'Primary 5': 'primary_5',
        'JSS 1': 'jss_1',
        'JSS 2': 'jss_2',
        'JSS 3': 'jss_3',
        'SSS 1': 'sss_1',
        'SSS 2': 'sss_2',
        'SSS 3': 'sss_3'
    }
    reverse_map = {v: k for k, v in private_school_classes_to_code.items()}
    return reverse_map.get(code, code)


def get_class_display_name_from_code(code):
    """Given class code (e.g., 'nursery_1'), return display name (e.g., 'Nursery 1')"""
    class_name_map = {
        'pre_nursery': 'Pre-Nursery',
        'nursery_1': 'Nursery 1',
        'nursery_2': 'Nursery 2',
        'primary_1': 'Primary 1',
        'primary_2': 'Primary 2',
        'primary_3': 'Primary 3',
        'primary_4': 'Primary 4',
        'primary_5': 'Primary 5',
        'jss_1': 'JSS 1',
        'jss_2': 'JSS 2',
        'jss_3': 'JSS 3',
        'sss_1': 'SSS 1',
        'sss_2': 'SSS 2',
        'sss_3': 'SSS 3'
    }
    return class_name_map.get(code, code)


def get_class_codes_for_group(group_name):
    """Return list of class codes for group (Nursery, Primary, JSS, SSS)."""
    group_mapping = {
        'Nursery': ['Pre-Nursery', 'Nursery 1', 'Nursery 2'],
        'Primary': ['Primary 1', 'Primary 2', 'Primary 3', 'Primary 4', 'Primary 5'],
        'JSS': ['JSS 1', 'JSS 2', 'JSS 3'],
        'SSS': ['SSS 1', 'SSS 2', 'SSS 3']
    }
    display_classes = group_mapping.get(group_name, [])
    return [private_school_classes_to_code[display] for display in display_classes if display in private_school_classes_to_code]


def parse_class_code(raw_class):
    """Convert flexible class string to standard short code (JSS1, JSS2, JSS3, SSS1, SSS2, SSS3)."""
    if not raw_class:
        return None
    cleaned = re.sub(r'\s+', '', raw_class).upper()
    
    # J1, J2, J3 or J1A, J 1 -> JSS1, JSS2, JSS3
    if cleaned.startswith('J') and len(cleaned) >= 2 and cleaned[1].isdigit():
        return f"JSS{cleaned[1]}"
    
    # JS1, JS2, JS3 or JS 1 -> JSS1, JSS2, JSS3
    if cleaned.startswith('JS') and len(cleaned) >= 3 and cleaned[2].isdigit():
        return f"JSS{cleaned[2]}"
    
    # SS1, SS2, SS3 or SS 1 -> SSS1, SSS2, SSS3
    if cleaned.startswith('SS') and len(cleaned) >= 3 and cleaned[2].isdigit():
        return f"SSS{cleaned[2]}"
    
    # SSS1, SSS2, SSS3 already correct
    if cleaned in ('SSS1', 'SSS2', 'SSS3'):
        return cleaned
    
    return None