"""
Academic periods management routes.

Endpoints for full CRUD lifecycle including archive, restore, and hard delete.
All write operations require admin+ role.
"""

from flask import Blueprint, jsonify, request, session
from server.permissions import admin_or_higher
from server.database import Session
from server.serializers import dict_to_camel_case
from models.models import AcademicPeriod, Event
from datetime import datetime, timezone
from sqlalchemy import and_
from dateutil.rrule import rrulestr
from dateutil.tz import UTC
import sys

sys.path.append('/workspace')

academic_periods_bp = Blueprint(
    'academic_periods', __name__, url_prefix='/api/academic_periods')


# ============================================================================
# GET ENDPOINTS
# ============================================================================

@academic_periods_bp.route('', methods=['GET'])
def list_academic_periods():
    """List academic periods with optional archived visibility filters, ordered by start_date."""
    db_session = Session()
    try:
        include_archived = request.args.get('includeArchived', '0') == '1'
        archived_only = request.args.get('archivedOnly', '0') == '1'

        query = db_session.query(AcademicPeriod)

        if archived_only:
            query = query.filter(AcademicPeriod.is_archived == True)
        elif not include_archived:
            query = query.filter(AcademicPeriod.is_archived == False)

        periods = query.order_by(AcademicPeriod.start_date.asc()).all()
        
        result = [dict_to_camel_case(p.to_dict()) for p in periods]
        return jsonify({'periods': result}), 200
    finally:
        db_session.close()


@academic_periods_bp.route('/<int:period_id>', methods=['GET'])
def get_academic_period(period_id):
    """Get a single academic period by ID (including archived)."""
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 200
    finally:
        db_session.close()


@academic_periods_bp.route('/active', methods=['GET'])
def get_active_academic_period():
    """Get the currently active academic period."""
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).filter(
            AcademicPeriod.is_active == True
        ).first()
        if not period:
            return jsonify({'period': None}), 200
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 200
    finally:
        db_session.close()


@academic_periods_bp.route('/for_date', methods=['GET'])
def get_period_for_date():
    """
    Returns the non-archived academic period that covers the provided date (YYYY-MM-DD).
    If multiple match, prefer the one with the latest start_date.
    """
    date_str = request.args.get('date')
    if not date_str:
        return jsonify({'error': 'Missing required query param: date (YYYY-MM-DD)'}), 400
    try:
        target = datetime.strptime(date_str, '%Y-%m-%d').date()
    except ValueError:
        return jsonify({'error': 'Invalid date format. Expected YYYY-MM-DD'}), 400

    db_session = Session()
    try:
        period = (
            db_session.query(AcademicPeriod)
            .filter(
                AcademicPeriod.start_date <= target,
                AcademicPeriod.end_date >= target,
                AcademicPeriod.is_archived == False
            )
            .order_by(AcademicPeriod.start_date.desc())
            .first()
        )
        return jsonify({'period': dict_to_camel_case(period.to_dict()) if period else None}), 200
    finally:
        db_session.close()


@academic_periods_bp.route('/<int:period_id>/usage', methods=['GET'])
def get_period_usage(period_id):
    """
    Check what events and media are linked to this period.
    Used for pre-flight checks before delete/archive.
    
    Returns:
        {
            "linked_events": count,
            "linked_media": count,
            "has_active_recurrence": boolean,
            "blockers": ["list of reasons why delete/archive would fail"]
        }
    """
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        # Count linked events
        linked_events = db_session.query(Event).filter(
            Event.academic_period_id == period_id
        ).count()
        
        # Check for active recurrence (events with recurrence_rule that have future occurrences)
        has_active_recurrence = False
        blockers = []
        
        now = datetime.now(timezone.utc)
        recurring_events = db_session.query(Event).filter(
            Event.academic_period_id == period_id,
            Event.recurrence_rule != None
        ).all()
        
        for evt in recurring_events:
            try:
                rrule_obj = rrulestr(evt.recurrence_rule, dtstart=evt.start)
                # Check if there are any future occurrences
                next_occurrence = rrule_obj.after(now, inc=True)
                if next_occurrence:
                    has_active_recurrence = True
                    blockers.append(f"Recurring event '{evt.title}' has active occurrences")
                    break
            except Exception:
                pass
        
        # If period is active, cannot archive/delete
        if period.is_active:
            blockers.append("Cannot archive or delete an active period")
        
        return jsonify({
            'usage': {
                'linked_events': linked_events,
                'has_active_recurrence': has_active_recurrence,
                'blockers': blockers
            }
        }), 200
    finally:
        db_session.close()


# ============================================================================
# CREATE ENDPOINT
# ============================================================================

@academic_periods_bp.route('', methods=['POST'])
@admin_or_higher
def create_academic_period():
    """
    Create a new academic period.
    
    Request body:
    {
        "name": "Schuljahr 2026/27",
        "displayName": "SJ 26/27",
        "startDate": "2026-09-01",
        "endDate": "2027-08-31",
        "periodType": "schuljahr"
    }
    """
    data = request.get_json(silent=True) or {}
    
    # Validate required fields
    name = data.get('name', '').strip()
    if not name:
        return jsonify({'error': 'Name is required and cannot be empty'}), 400
    
    start_date_str = data.get('startDate')
    end_date_str = data.get('endDate')
    period_type = data.get('periodType', 'schuljahr')
    display_name = data.get('displayName', '').strip() or None
    
    # Parse dates
    try:
        start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date()
        end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date()
    except (ValueError, TypeError):
        return jsonify({'error': 'Invalid date format. Expected YYYY-MM-DD'}), 400
    
    # Validate date range
    if start_date > end_date:
        return jsonify({'error': 'Start date must be less than or equal to end date'}), 400
    
    # Validate period type
    valid_types = ['schuljahr', 'semester', 'trimester']
    if period_type not in valid_types:
        return jsonify({'error': f'Invalid periodType. Must be one of: {", ".join(valid_types)}'}), 400
    
    db_session = Session()
    try:
        # Check name uniqueness among non-archived periods
        existing = db_session.query(AcademicPeriod).filter(
            AcademicPeriod.name == name,
            AcademicPeriod.is_archived == False
        ).first()
        if existing:
            return jsonify({'error': 'A non-archived period with this name already exists'}), 409
        
        # Check for overlaps within same period type
        overlapping = db_session.query(AcademicPeriod).filter(
            AcademicPeriod.period_type == period_type,
            AcademicPeriod.is_archived == False,
            AcademicPeriod.start_date <= end_date,
            AcademicPeriod.end_date >= start_date
        ).first()
        if overlapping:
            return jsonify({'error': f'Overlapping {period_type} period already exists'}), 409
        
        # Create period
        period = AcademicPeriod(
            name=name,
            display_name=display_name,
            start_date=start_date,
            end_date=end_date,
            period_type=period_type,
            is_active=False,
            is_archived=False
        )
        db_session.add(period)
        db_session.commit()
        db_session.refresh(period)
        
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 201
    finally:
        db_session.close()


# ============================================================================
# UPDATE ENDPOINT
# ============================================================================

@academic_periods_bp.route('/<int:period_id>', methods=['PUT'])
@admin_or_higher
def update_academic_period(period_id):
    """
    Update an academic period (cannot be archived).
    
    Request body (all fields optional):
    {
        "name": "...",
        "displayName": "...",
        "startDate": "YYYY-MM-DD",
        "endDate": "YYYY-MM-DD",
        "periodType": "schuljahr|semester|trimester"
    }
    """
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        if period.is_archived:
            return jsonify({'error': 'Cannot update an archived period'}), 409
        
        data = request.get_json(silent=True) or {}
        
        # Update fields if provided
        if 'name' in data:
            name = data['name'].strip()
            if not name:
                return jsonify({'error': 'Name cannot be empty'}), 400
            
            # Check uniqueness among non-archived (excluding self)
            existing = db_session.query(AcademicPeriod).filter(
                AcademicPeriod.name == name,
                AcademicPeriod.is_archived == False,
                AcademicPeriod.id != period_id
            ).first()
            if existing:
                return jsonify({'error': 'A non-archived period with this name already exists'}), 409
            
            period.name = name
        
        if 'displayName' in data:
            period.display_name = data['displayName'].strip() or None
        
        if 'periodType' in data:
            period_type = data['periodType']
            valid_types = ['schuljahr', 'semester', 'trimester']
            if period_type not in valid_types:
                return jsonify({'error': f'Invalid periodType. Must be one of: {", ".join(valid_types)}'}), 400
            period.period_type = period_type
        
        # Handle date updates with overlap checking
        if 'startDate' in data or 'endDate' in data:
            start_date = period.start_date
            end_date = period.end_date
            
            if 'startDate' in data:
                try:
                    start_date = datetime.strptime(data['startDate'], '%Y-%m-%d').date()
                except (ValueError, TypeError):
                    return jsonify({'error': 'Invalid startDate format. Expected YYYY-MM-DD'}), 400
            
            if 'endDate' in data:
                try:
                    end_date = datetime.strptime(data['endDate'], '%Y-%m-%d').date()
                except (ValueError, TypeError):
                    return jsonify({'error': 'Invalid endDate format. Expected YYYY-MM-DD'}), 400
            
            if start_date > end_date:
                return jsonify({'error': 'Start date must be less than or equal to end date'}), 400
            
            # Check for overlaps within same period type (excluding self)
            overlapping = db_session.query(AcademicPeriod).filter(
                AcademicPeriod.period_type == period.period_type,
                AcademicPeriod.is_archived == False,
                AcademicPeriod.id != period_id,
                AcademicPeriod.start_date <= end_date,
                AcademicPeriod.end_date >= start_date
            ).first()
            if overlapping:
                return jsonify({'error': f'Overlapping {period.period_type.value} period already exists'}), 409
            
            period.start_date = start_date
            period.end_date = end_date
        
        db_session.commit()
        db_session.refresh(period)
        
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 200
    finally:
        db_session.close()


# ============================================================================
# ACTIVATE ENDPOINT
# ============================================================================

@academic_periods_bp.route('/<int:period_id>/activate', methods=['POST'])
@admin_or_higher
def activate_academic_period(period_id):
    """
    Activate an academic period (deactivates all others).
    Cannot activate an archived period.
    """
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        if period.is_archived:
            return jsonify({'error': 'Cannot activate an archived period'}), 409
        
        # Deactivate all, then activate target
        db_session.query(AcademicPeriod).update({AcademicPeriod.is_active: False})
        period.is_active = True
        db_session.commit()
        db_session.refresh(period)
        
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 200
    finally:
        db_session.close()


# ============================================================================
# ARCHIVE/RESTORE ENDPOINTS
# ============================================================================

@academic_periods_bp.route('/<int:period_id>/archive', methods=['POST'])
@admin_or_higher
def archive_academic_period(period_id):
    """
    Archive an academic period (soft delete).
    Cannot archive an active period or one with active recurring events.
    """
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        if period.is_archived:
            return jsonify({'error': 'Period already archived'}), 409
        
        if period.is_active:
            return jsonify({'error': 'Cannot archive an active period'}), 409
        
        # Check for recurrence spillover
        now = datetime.now(timezone.utc)
        recurring_events = db_session.query(Event).filter(
            Event.academic_period_id == period_id,
            Event.recurrence_rule != None
        ).all()
        
        for evt in recurring_events:
            try:
                rrule_obj = rrulestr(evt.recurrence_rule, dtstart=evt.start)
                next_occurrence = rrule_obj.after(now, inc=True)
                if next_occurrence:
                    return jsonify({'error': f'Cannot archive: recurring event "{evt.title}" has active occurrences'}), 409
            except Exception:
                pass
        
        # Archive
        user_id = session.get('user_id')
        period.is_archived = True
        period.archived_at = datetime.now(timezone.utc)
        period.archived_by = user_id
        db_session.commit()
        db_session.refresh(period)
        
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 200
    finally:
        db_session.close()


@academic_periods_bp.route('/<int:period_id>/restore', methods=['POST'])
@admin_or_higher
def restore_academic_period(period_id):
    """
    Restore an archived academic period (returns to inactive state).
    """
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        if not period.is_archived:
            return jsonify({'error': 'Period is not archived'}), 409
        
        # Restore
        period.is_archived = False
        period.archived_at = None
        period.archived_by = None
        db_session.commit()
        db_session.refresh(period)
        
        return jsonify({'period': dict_to_camel_case(period.to_dict())}), 200
    finally:
        db_session.close()


# ============================================================================
# DELETE ENDPOINT
# ============================================================================

@academic_periods_bp.route('/<int:period_id>', methods=['DELETE'])
@admin_or_higher
def delete_academic_period(period_id):
    """
    Hard delete an archived, inactive academic period.
    Blocked if linked events exist, linked media exist, or recurrence spillover detected.
    """
    db_session = Session()
    try:
        period = db_session.query(AcademicPeriod).get(period_id)
        if not period:
            return jsonify({'error': 'AcademicPeriod not found'}), 404
        
        if not period.is_archived:
            return jsonify({'error': 'Cannot hard-delete a non-archived period'}), 409
        
        if period.is_active:
            return jsonify({'error': 'Cannot hard-delete an active period'}), 409
        
        # Check for linked events
        linked_events = db_session.query(Event).filter(
            Event.academic_period_id == period_id
        ).count()
        if linked_events > 0:
            return jsonify({'error': f'Cannot delete: {linked_events} event(s) linked to this period'}), 409
        
        # Delete
        db_session.delete(period)
        db_session.commit()
        
        return jsonify({'message': 'Period deleted successfully'}), 200
    finally:
        db_session.close()
