import re
import logging
from typing import List, Dict, Any
from days.models import Day
from timeslot.models import TimeSlot
from course.models import Course
from teacher_management.models import Teacher
from django.db.models import Q
from .ai_parser_service import AIParserService

logger = logging.getLogger(__name__)

class ParserService:
    """
    Service for parsing extracted text into structured routine JSON data.
    """

    @staticmethod
    def parse_routine_text(text: str) -> List[Dict[str, Any]]:
        """
        Hybrid parser: tries AI first, then fallbacks to regex.
        """
        try:
            logger.info("Attempting AI parsing...")
            data = AIParserService.ai_parse_service(text)
            logger.info("AI parsing completed.")
            if data:
                return ParserService.validate_and_clean_entries(data)
        except Exception as e:
            logger.error(f"AI parsing failed, falling back to regex: {str(e)}")
        
        # Fallback to existing regex logic
        return ParserService.regex_fallback_parser(text)

    @staticmethod
    def regex_fallback_parser(text: str) -> List[Dict[str, Any]]:
        """
        Existing regex-based parsing logic.
        """
        entries = []
        lines = text.split('\n')
        
        # Simple regex for time: HH:MM - HH:MM
        time_pattern = r'(\d{1,2}:\d{2})\s*[-—]\s*(\d{1,2}:\d{2})'
        
        # List of valid days to search for
        valid_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
        
        current_day = None
        
        for line in lines:
            line = line.strip()
            if not line:
                continue
            
            # Check if line contains a day
            day_match = None
            for d in valid_days:
                if d.lower() in line.lower():
                    day_match = d
                    break
            
            if day_match:
                current_day = day_match
            
            # Check for time pattern
            time_match = re.search(time_pattern, line)
            if time_match:
                start_time, end_time = time_match.groups()
                
                # Extract subject and teacher
                parts = re.split(r'[,|;]', line)
                
                # Default values
                subject = "Unknown"
                teacher = "Unknown"
                
                # Attempt to find subject/teacher if they aren't the time
                other_parts = [p.strip() for p in parts if not re.search(time_pattern, p) and not any(d.lower() in p.lower() for d in valid_days)]
                
                if len(other_parts) >= 1:
                    subject = other_parts[0]
                if len(other_parts) >= 2:
                    teacher = other_parts[1]
                
                entries.append({
                    "day": current_day or "Monday",
                    "start_time": start_time,
                    "end_time": end_time,
                    "subject": subject,
                    "teacher": teacher
                })
        
        return entries

    @staticmethod
    def validate_and_clean_entries(entries: List[Dict[str, Any]] | Dict[str, Any]) -> List[Dict[str, Any]] | Dict[str, Any]:
        """
        Validate and clean parsed routine data.
        Handles both simple entry lists and complete routine structures.
        """
        # If it's a single complex routine object (from AI parser)
        if isinstance(entries, dict) and 'routine_data' in entries:
            return ParserService._validate_routine_structure(entries)
        
        # If it's a list of simple entries (from regex parser)
        if isinstance(entries, list):
            return ParserService._validate_simple_entries(entries)
        
        return entries

    @staticmethod
    def _validate_routine_structure(routine: Dict[str, Any]) -> Dict[str, Any]:
        """
        Validate complete routine structure from AI parser.
        """
        valid_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
        time_format = re.compile(r'^\d{1,2}:\d{2}$')
        
        cleaned_routine = routine.copy()
        
        # Validate teachers
        if 'teachers' in cleaned_routine and isinstance(cleaned_routine['teachers'], list):
            teachers = []
            for teacher in cleaned_routine['teachers']:
                if teacher.get('first_name') and teacher.get('last_name'):
                    teachers.append(teacher)
            cleaned_routine['teachers'] = teachers
        
        # Validate courses
        if 'courses' in cleaned_routine and isinstance(cleaned_routine['courses'], list):
            courses = []
            for course in cleaned_routine['courses']:
                if course.get('code') and course.get('name'):
                    courses.append(course)
            cleaned_routine['courses'] = courses
        
        # Validate time_slots
        if 'time_slots' in cleaned_routine and isinstance(cleaned_routine['time_slots'], list):
            time_slots = []
            for slot in cleaned_routine['time_slots']:
                if slot.get('start') and slot.get('end'):
                    if time_format.match(slot['start']) and time_format.match(slot['end']):
                        time_slots.append(slot)
            cleaned_routine['time_slots'] = time_slots
        
        # Validate routine_data
        if 'routine_data' in cleaned_routine and isinstance(cleaned_routine['routine_data'], dict):
            cleaned_data = {}
            for day, entries in cleaned_routine['routine_data'].items():
                if day in valid_days:
                    valid_entries = []
                    if isinstance(entries, list):
                        for entry in entries:
                            # Validate required fields for routine entry
                            if (entry.get('slot') is not None and 
                                entry.get('course') and 
                                entry.get('teacher') and 
                                entry.get('room')):
                                # Ensure slot is an integer
                                try:
                                    entry['slot'] = int(entry['slot'])
                                    valid_entries.append(entry)
                                except (ValueError, TypeError):
                                    continue
                    cleaned_data[day] = valid_entries
            cleaned_routine['routine_data'] = cleaned_data
        
        logger.info(f"Validated routine structure: {cleaned_routine.get('name', 'Unknown')}")
        return cleaned_routine

    @staticmethod
    def _validate_simple_entries(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        Validate simple day/time/subject entries from regex parser.
        """
        valid_entries = []
        valid_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
        time_format = re.compile(r'^\d{1,2}:\d{2}$')

        for entry in entries:
            # Check for missing required fields
            if not entry.get('day') or not entry.get('start_time') or not entry.get('subject'):
                continue
            
            # Validate day
            if entry['day'].capitalize() not in valid_days:
                continue
            
            # Validate time formats
            if not time_format.match(entry['start_time']):
                continue
            
            if entry.get('end_time') and not time_format.match(entry['end_time']):
                # If end_time is provided but invalid, set to null instead of rejecting entry
                entry['end_time'] = None

            valid_entries.append(entry)
            
        return valid_entries

    @staticmethod
    def match_entities(entries: List[Dict[str, Any]] | Dict[str, Any]) -> List[Dict[str, Any]] | Dict[str, Any]:
        """
        Match extracted strings with database entities.
        Handles both simple entries and complete routine structures.
        """
        # If it's a complete routine structure
        if isinstance(entries, dict) and 'routine_data' in entries:
            return ParserService._match_routine_entities(entries)
        
        # If it's a list of simple entries
        if isinstance(entries, list):
            return ParserService._match_simple_entities(entries)
        
        return entries

    @staticmethod
    def _match_routine_entities(routine: Dict[str, Any]) -> Dict[str, Any]:
        """
        Match entities in complete routine structure with database records.
        """
        matched_routine = routine.copy()
        
        # Match teachers
        if 'teachers' in matched_routine and isinstance(matched_routine['teachers'], list):
            matched_teachers = []
            for teacher in matched_routine['teachers']:
                teacher_obj = Teacher.objects.filter(
                    Q(first_name__icontains=teacher.get('first_name', '')) |
                    Q(last_name__icontains=teacher.get('last_name', '')) |
                    Q(code__iexact=teacher.get('code', ''))
                ).first()
                
                teacher['teacher_id'] = teacher_obj.id if teacher_obj else None
                teacher['teacher_slug'] = teacher_obj.slug if teacher_obj else None
                matched_teachers.append(teacher)
            matched_routine['teachers'] = matched_teachers
        
        # Match courses
        if 'courses' in matched_routine and isinstance(matched_routine['courses'], list):
            matched_courses = []
            for course in matched_routine['courses']:
                course_obj = Course.objects.filter(
                    Q(name__icontains=course.get('name', '')) |
                    Q(code__icontains=course.get('code', ''))
                ).first()
                
                course['course_id'] = course_obj.id if course_obj else None
                matched_courses.append(course)
            matched_routine['courses'] = matched_courses
        
        # Match time_slots
        if 'time_slots' in matched_routine and isinstance(matched_routine['time_slots'], list):
            matched_slots = []
            for slot in matched_routine['time_slots']:
                # Try to match by start_time
                timeslot_obj = TimeSlot.objects.filter(
                    start_time__icontains=slot.get('start', '')
                ).first()
                
                slot['timeslot_id'] = timeslot_obj.id if timeslot_obj else None
                matched_slots.append(slot)
            matched_routine['time_slots'] = matched_slots
        
        # Match routine_data entries
        if 'routine_data' in matched_routine and isinstance(matched_routine['routine_data'], dict):
            matched_data = {}
            for day, entries in matched_routine['routine_data'].items():
                matched_entries = []
                if isinstance(entries, list):
                    for entry in entries:
                        # Match Day
                        day_obj = Day.objects.filter(name__iexact=day).first()
                        entry['day_id'] = day_obj.id if day_obj else None
                        
                        # Match TimeSlot by slot index
                        if entry.get('slot') is not None:
                            try:
                                slot_index = int(entry['slot'])
                                if 'time_slots' in matched_routine:
                                    if slot_index < len(matched_routine['time_slots']):
                                        timeslot_data = matched_routine['time_slots'][slot_index]
                                        # Find timeslot in DB by start_time
                                        timeslot_obj = TimeSlot.objects.filter(
                                            start_time__icontains=timeslot_data.get('start', '')
                                        ).first()
                                        entry['timeslot_id'] = timeslot_obj.id if timeslot_obj else None
                            except (ValueError, TypeError, IndexError):
                                pass
                        
                        # Match Course
                        course_obj = Course.objects.filter(
                            Q(name__icontains=entry.get('course', '')) |
                            Q(code__icontains=entry.get('course', ''))
                        ).first()
                        entry['course_id'] = course_obj.id if course_obj else None
                        
                        # Match Teacher by code or name
                        teacher_obj = Teacher.objects.filter(
                            Q(first_name__icontains=entry.get('teacher', '')) |
                            Q(last_name__icontains=entry.get('teacher', '')) |
                            Q(code__iexact=entry.get('teacher', ''))
                        ).first()
                        entry['teacher_slug'] = teacher_obj.slug if teacher_obj else None
                        
                        matched_entries.append(entry)
                matched_data[day] = matched_entries
            matched_routine['routine_data'] = matched_data
        
        logger.info(f"Matched entities for routine: {matched_routine.get('name', 'Unknown')}")
        return matched_routine

    @staticmethod
    def _match_simple_entities(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        Match extracted strings with database entities for simple entries.
        """
        matched_entries = []
        for entry in entries:
            # Match Day
            day_obj = Day.objects.filter(name__iexact=entry['day']).first()
            entry['day_id'] = day_obj.id if day_obj else None
            
            # Match TimeSlot (approximate match)
            # We match by start_time being contained in the timeslot's start_time string
            timeslot_obj = TimeSlot.objects.filter(start_time__icontains=entry['start_time']).first()
            entry['timeslot_id'] = timeslot_obj.id if timeslot_obj else None
            
            # Match Course
            course_obj = Course.objects.filter(
                Q(name__icontains=entry['subject']) | Q(code__icontains=entry['subject'])
            ).first()
            entry['course_id'] = course_obj.id if course_obj else None
            
            # Match Teacher
            teacher_obj = Teacher.objects.filter(
                Q(first_name__icontains=entry['teacher']) | 
                Q(last_name__icontains=entry['teacher']) |
                Q(code__icontains=entry['teacher'])
            ).first()
            entry['teacher_slug'] = teacher_obj.slug if teacher_obj else None
            
            matched_entries.append(entry)
            
        return matched_entries
