import string
import random
import secrets
import re
import html
import logging
from datetime import datetime, timedelta, timezone
from flask import request
from sqlalchemy import func
from sqlalchemy.orm import joinedload

logger = logging.getLogger(__name__)

def get_jakarta_now():
    """Returns current time in Jakarta (WIB, UTC+7)."""
    return datetime.now(timezone(timedelta(hours=7)))

def log_activity(user_id, action, target_type=None, target_id=None, details=None, school_id=None):
    """Logs user activity in the database."""
    from app.models import db, ActivityLog
    try:
        # Auto-detect school_id from user if not provided
        if school_id is None:
            from app.models import User
            user = db.session.get(User, user_id)
            if user:
                school_id = user.school_id

        log = ActivityLog(
            user_id=user_id,
            action=action,
            target_type=target_type,
            target_id=target_id,
            details=details,
            ip_address=request.remote_addr if request else '127.0.0.1',
            school_id=school_id
        )
        db.session.add(log)
        db.session.commit()
    except Exception as e:
        db.session.rollback()
        logger.error(f"Failed to log activity: {e}", exc_info=True)

def generate_random_password(length=6):
    """Generates a random password using cryptographically secure secrets module.
    
    Args:
        length: Password length (minimum 6 characters). Default is 6.
    
    Returns:
        A random password containing lowercase, digits, and symbols.
    """
    if length < 6:
        length = 6
    
    lower = string.ascii_lowercase
    digits = string.digits
    symbols = "!@#$%^&*"
    all_chars = lower + digits + symbols

    # Ensure at least one of each required type using secrets (CSPRNG)
    password = [
        secrets.choice(lower),
        secrets.choice(digits),
        secrets.choice(symbols),
    ]
    
    # Fill remaining length with random characters
    for _ in range(length - 3):
        password.append(secrets.choice(all_chars))
    
    # Shuffle securely
    secrets.SystemRandom().shuffle(password)
    return ''.join(password)

def generate_class_code(length=6):
    from app.models import Course
    characters = string.ascii_uppercase + string.digits
    while True:
        code = ''.join(secrets.choice(characters) for _ in range(length))
        if not Course.query.filter_by(class_code=code).first():
            return code

def get_courses_for_user(user, year_id):
    from app.models import Course, User, UserRole, UserCourseOrder
    if not year_id:
        return []

    query = Course.query.options(joinedload(Course.teacher))

    # Outer join with UserCourseOrder for the current user
    query = query.outerjoin(
        UserCourseOrder,
        (UserCourseOrder.course_id == Course.id) & (UserCourseOrder.user_id == user.id)
    )

    if user.role == UserRole.GURU:
        # For teachers, show their own courses in the specified year
        # If year_id is -1, show all courses (no year filter)
        if year_id == -1:
            query = query.filter(Course.teacher_id == user.id)
        else:
            query = query.filter(Course.teacher_id == user.id, Course.academic_year_id == year_id)
    else:
        # For students, show enrolled courses
        query = query.join(Course.students).filter(User.id == user.id, Course.academic_year_id == year_id)

    # Order by manual_order first (coalesce NULL to 0), then by name
    return query.order_by(func.coalesce(UserCourseOrder.manual_order, 0), Course.name).all()

def format_course_data(course, user):
    return {
        'id': course.id,
        'name': course.name,
        'teacher': {
            'name': course.teacher.name
        },
        'studentCount': len(course.students),
        'classCode': course.class_code,
        'color': course.color,
        'is_teacher': course.teacher_id == user.id,
    }

def sanitize_text(value: str, max_len: int = 150) -> str:
    if value is None:
        return ''
    s = value.strip()
    s = re.sub(r'<[^>]*?>', '', s)
    s = html.escape(s)
    if len(s) > max_len:
        s = s[:max_len]
    return s

def sanitize_rich_text(value: str, max_len: int = 5000) -> str:
    """Sanitize rich text HTML, allowing only safe formatting tags."""
    if value is None:
        return ''
    s = value.strip()
    allowed_tags = {'b', 'i', 'u', 'strong', 'em', 'ol', 'ul', 'li', 'br', 'p', 'div'}
    def replace_tag(match):
        tag_content = match.group(1)
        tag_name = re.match(r'/?(\w+)', tag_content)
        if tag_name and tag_name.group(1).lower() in allowed_tags:
            return match.group(0)
        return ''
    s = re.sub(r'<([^>]*?)>', replace_tag, s)
    if len(s) > max_len:
        s = s[:max_len]
    return s

def is_valid_email(email: str) -> bool:
    from email_validator import validate_email, EmailNotValidError
    if not isinstance(email, str):
        return False
    try:
        validate_email(email, check_deliverability=False)
        return True
    except EmailNotValidError:
        return False

def is_valid_color(color: str) -> bool:
    if not isinstance(color, str):
        return False
    return re.match(r'^#[0-9a-fA-F]{6}$', color.strip()) is not None

def is_valid_class_code(code: str) -> bool:
    if not isinstance(code, str):
        return False
    c = code.strip().upper()
    return re.match(r'^[A-Z0-9]{4,8}$', c) is not None
