#!/usr/bin/env python3
"""
Comprehensive Math Curriculum Audit
Validates curriculum structure, topic coverage, and lesson correctness.
"""

import os
import re
import sys
from pathlib import Path
from typing import List, Dict, Tuple, Any

BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")

# Define the expected curriculum structure
CURRICULUM = {
    "Phase 1: Number Sense & Operations": {
        "weeks": [1, 2, 3, 4, 5, 6],
        "topics": {
            1: "Counting to 100",
            2: "Place Value (Tens & Ones)",
            3: "Comparing Numbers",
            4: "Addition Facts to 20",
            5: "Subtraction Facts",
            6: "Phase 1 Review",
        },
        "skills": ["counting", "place value", "comparing", "addition", "subtraction"]
    },
    "Phase 2: Addition & Subtraction": {
        "weeks": [7, 8, 9, 10, 11, 12, 13, 14],
        "topics": {
            7: "Addition Strategies",
            8: "Subtraction Strategies",
            9: "Add to 100",
            10: "Subtract from 100",
            11: "Missing Addends",
            12: "Add/Sub Word Problems",
            13: "Phase 2 Review",
            14: "Word Problems Checkpoint",
        },
        "skills": ["addition", "subtraction", "word problems", "strategies"]
    },
    "Phase 3: Measurement, Time & Money": {
        "weeks": [15, 16, 17, 18, 19, 20],
        "topics": {
            15: "Length - Inches & Centimeters",
            16: "Telling Time",
            17: "Introduction to Fractions",
            18: "Money & Fractions",
            19: "Money: Adding & Change",
            20: "Data: Picture & Bar Graphs",
        },
        "skills": ["measurement", "time", "money", "fractions", "data"]
    },
    "Phase 4: Geometry & Fractions": {
        "weeks": [21, 22, 23, 24, 25, 26],
        "topics": {
            21: "2D Shapes & Attributes",
            22: "3D Shapes",
            23: "Lines, Angles & Symmetry",
            24: "Area & Perimeter",
            25: "Patterns & Rule Finding",
            26: "Phase 4 Review",
        },
        "skills": ["geometry", "shapes", "area", "perimeter", "patterns"]
    },
    "Phase 5: Multiplication, Division & Patterns": {
        "weeks": [27, 28, 29, 30, 31, 32],
        "topics": {
            27: "Equal Groups & Arrays",
            28: "Repeated Addition to Multiplication",
            29: "Multiplication Facts 0-5",
            30: "Multiplication Facts 6-10",
            31: "Division as Equal Sharing",
            32: "Final Review & Assessment",
        },
        "skills": ["multiplication", "division", "arrays", "patterns"]
    }
}

# Expected daily structure template
DAILY_TEMPLATE = {
    "Monday": "Introduction/New Concept",
    "Tuesday": "Practice/Application",
    "Wednesday": "Deepening Understanding",
    "Thursday": "Extension/Challenge",
    "Friday": "Review/Assessment"
}

class CurriculumError:
    def __init__(self, week: int, day: str, error_type: str, message: str):
        self.week = week
        self.day = day
        self.error_type = error_type  # "MISSING", "WRONG_TOPIC", "WRONG_PHASE", "STRUCTURE"
        self.message = message
    
    def __str__(self):
        day_str = self.day if self.day != "N/A" else "Week"
        return f"Week {self.week} {day_str}: [{self.error_type}] {self.message}"


class CurriculumAuditor:
    def __init__(self):
        self.errors: List[CurriculumError] = []
        self.warnings: List[CurriculumError] = []
        self.stats = {
            "weeks_audited": 0,
            "weeks_missing": 0,
            "topics_validated": 0,
            "days_validated": 0,
        }
    
    def log_error(self, week: int, day: str, error_type: str, message: str):
        self.errors.append(CurriculumError(week, day, error_type, message))
    
    def log_warning(self, week: int, day: str, error_type: str, message: str):
        self.warnings.append(CurriculumError(week, day, error_type, message))
    
    def get_phase_for_week(self, week_num: int) -> Tuple[str, Dict]:
        """Get the phase information for a given week."""
        for phase_name, phase_info in CURRICULUM.items():
            if week_num in phase_info["weeks"]:
                return phase_name, phase_info
        return "Unknown", {}
    
    def extract_week_info(self, filepath: Path) -> Dict:
        """Extract week number, topic, and phase from a generator file."""
        with open(filepath, 'r') as f:
            content = f.read()
        
        week_num = int(filepath.stem.replace('generate_week', ''))
        
        # Extract topic from docstring
        topic = ""
        phase = ""
        for line in content.split('\n')[:15]:
            if 'Week' in line and ':' in line:
                topic = re.sub(r'[^a-zA-Z0-9\s&\-]', '', line).strip()
            if 'Phase' in line:
                phase = line.strip()
        
        # Extract day topics
        days = {}
        for day_name in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']:
            pattern = rf'def {day_name}\(\):.*?""".*?"""'
            match = re.search(pattern, content, re.DOTALL)
            if match:
                doc_content = match.group(0)
                lines = doc_content.split('\n')
                if len(lines) > 1:
                    day_topic = lines[1].strip().replace('"""', '').strip()
                    if day_topic and not day_topic.startswith('#'):
                        days[day_name.capitalize()] = day_topic[:80]
        
        return {
            "week_num": week_num,
            "topic": topic,
            "phase": phase,
            "days": days,
            "content": content
        }
    
    def validate_week_topic(self, week_num: int, actual_topic: str, expected_topic: str):
        """Validate that a week covers the expected topic."""
        # Allow some flexibility in topic naming
        expected_keywords = expected_topic.lower().split()
        actual_lower = actual_topic.lower()
        
        # Check if key words match
        matching_keywords = sum(1 for kw in expected_keywords if kw in actual_lower)
        
        if matching_keywords >= len(expected_keywords) * 0.6:
            return True, "Topic matches"
        elif actual_topic:
            return False, f"Expected '{expected_topic}', got '{actual_topic[:50]}'"
        else:
            return False, "No topic found"
    
    def validate_phase_order(self, week_num: int, actual_phase: str, expected_phase: str):
        """Validate that a week is in the correct phase."""
        if not actual_phase:
            # Some weeks might not have explicit phase declarations - warn but don't error
            return True, "No phase declaration (optional)"
        
        # Check if expected phase name appears in actual phase
        # Also allow checkpoint/review variations
        if expected_phase.lower() in actual_phase.lower():
            return True, "Phase matches"
        
        # Allow checkpoint/review variations
        checkpoint_keywords = ["checkpoint", "review", "assessment", "bridge to", "finale"]
        if any(kw in actual_phase.lower() for kw in checkpoint_keywords):
            return True, "Phase checkpoint/review accepted"
        
        return False, f"Expected '{expected_phase}', got '{actual_phase[:50]}'"
    
    def validate_daily_structure(self, week_num: int, days: Dict):
        """Validate that all 5 days are present with proper structure."""
        required_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
        missing_days = []
        
        for day in required_days:
            if day not in days:
                missing_days.append(day)
                self.log_error(week_num, day, "MISSING", "Day function not found")
        
        # Check Friday is a review
        if "Friday" in days:
            friday_topic = days["Friday"].lower()
            if not any(word in friday_topic for word in ["review", "assessment", "challenge", "finale"]):
                self.log_warning(week_num, "Friday", "STRUCTURE", 
                               f"Friday should be review/assessment, got: '{days['Friday'][:40]}'")
    
    def validate_activity_count(self, week_num: int, content: str):
        """Validate that each day has appropriate number of activities."""
        for day_name in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']:
            pattern = rf'def {day_name}\(\):.*?(?=\ndef |\Z)'
            match = re.search(pattern, content, re.DOTALL)
            if match:
                day_content = match.group(0)
                activity_count = len(re.findall(r's\d+\s*=', day_content))
                
                if activity_count < 4:
                    self.log_warning(week_num, day_name.capitalize(), "STRUCTURE",
                                   f"Only {activity_count} activities (too few)")
                elif activity_count > 8:
                    self.log_warning(week_num, day_name.capitalize(), "STRUCTURE",
                                   f"{activity_count} activities (too many, max 6 recommended)")
    
    def validate_math_correctness(self, week_num: int, content: str):
        """Validate mathematical correctness in the week."""
        # Skip-counting validation
        skip_sequences = re.findall(r'class="skip-num">(\d+)', content)
        if skip_sequences:
            nums = [int(x) for x in skip_sequences]
            if len(nums) >= 4:
                # Check for count-by-10 sequences
                if all(n % 10 == 0 for n in nums[:4]):
                    for i in range(min(3, len(nums)-1)):
                        diff = nums[i+1] - nums[i]
                        if diff != 0 and abs(diff) not in [10, 20, 30, 40, 50, 60, 70, 80, 90]:
                            self.log_error(week_num, "N/A", "CORRECTNESS",
                                         f"Count-by-10s error: jump from {nums[i]} to {nums[i+1]}")
        
        # Check for count-by-5 sequences
        if skip_sequences:
            nums = [int(x) for x in skip_sequences]
            if len(nums) >= 4 and all(n % 5 == 0 for n in nums) and not all(n % 10 == 0 for n in nums):
                for i in range(min(3, len(nums)-1)):
                    diff = nums[i+1] - nums[i]
                    if diff != 0 and abs(diff) not in [5, 10, 15, 20]:
                        self.log_error(week_num, "N/A", "CORRECTNESS",
                                     f"Count-by-5s error: jump from {nums[i]} to {nums[i+1]}")
    
    def audit_week(self, filepath: Path):
        """Audit a single week file."""
        try:
            info = self.extract_week_info(filepath)
            week_num = info["week_num"]
            
            # Get expected phase and topic
            expected_phase_name, expected_phase_info = self.get_phase_for_week(week_num)
            expected_topic = expected_phase_info.get("topics", {}).get(week_num, "Unknown")
            
            # Validate topic
            topic_ok, topic_msg = self.validate_week_topic(week_num, info["topic"], expected_topic)
            if topic_ok:
                self.stats["topics_validated"] += 1
            else:
                self.log_error(week_num, "N/A", "WRONG_TOPIC", topic_msg)
            
            # Validate phase
            if expected_phase_name != "Unknown":
                phase_ok, phase_msg = self.validate_phase_order(week_num, info["phase"], expected_phase_name)
                if not phase_ok:
                    self.log_error(week_num, "N/A", "WRONG_PHASE", phase_msg)
            
            # Validate daily structure
            self.validate_daily_structure(week_num, info["days"])
            self.stats["days_validated"] += len(info["days"])
            
            # Validate activity counts
            self.validate_activity_count(week_num, info["content"])
            
            # Validate math correctness
            self.validate_math_correctness(week_num, info["content"])
            
            self.stats["weeks_audited"] += 1
            
        except Exception as e:
            self.log_error(week_num, "N/A", "ERROR", f"Failed to parse: {e}")
    
    def audit_missing_weeks(self, week_files: List[Path]):
        """Check for missing week files."""
        existing_weeks = {int(wf.stem.replace('generate_week', '')) for wf in week_files}
        
        for phase_name, phase_info in CURRICULUM.items():
            for week_num in phase_info["weeks"]:
                if week_num not in existing_weeks:
                    self.log_error(week_num, "N/A", "MISSING", 
                                 f"Missing week file: generate_week{week_num}.py ({phase_name})")
                    self.stats["weeks_missing"] += 1
    
    def audit_curriculum_flow(self, week_files: List[Path]):
        """Validate that curriculum flows logically through phases."""
        existing_weeks = sorted([int(wf.stem.replace('generate_week', '')) for wf in week_files])
        
        # Check for gaps in week numbers
        for i in range(1, 33):  # 32 weeks total
            if i not in existing_weeks:
                # Already logged in missing weeks check
                pass
            
            # Check review weeks are at phase boundaries
            if i in [6, 13, 14, 20, 26, 32]:  # Review/assessment weeks
                # These should have review topics
                pass  # Will be checked in individual week audit
    
    def print_report(self):
        """Print comprehensive audit report."""
        print(f"\n{'='*80}")
        print("CURRICULUM AUDIT REPORT")
        print(f"{'='*80}\n")
        
        print("STATISTICS:")
        print("-" * 40)
        print(f"  Weeks audited:     {self.stats['weeks_audited']}")
        print(f"  Weeks missing:     {self.stats['weeks_missing']}")
        print(f"  Topics validated:  {self.stats['topics_validated']}")
        print(f"  Days validated:    {self.stats['days_validated']}")
        print()
        
        # Print phase coverage
        print("PHASE COVERAGE:")
        print("-" * 40)
        for phase_name, phase_info in CURRICULUM.items():
            existing_weeks = [w for w in phase_info["weeks"] if 
                            any(int(f.stem.replace('generate_week', '')) == w 
                               for f in Path(BASE).glob("generate_week*.py"))]
            status = "✓" if len(existing_weeks) == len(phase_info["weeks"]) else "✗"
            print(f"  {status} {phase_name}")
            print(f"    Weeks {phase_info['weeks'][0]}-{phase_info['weeks'][-1]}: "
                  f"{len(existing_weeks)}/{len(phase_info['weeks'])} files present")
        print()
        
        # Print errors
        if self.errors:
            print(f"ERRORS ({len(self.errors)}):")
            print("-" * 40)
            
            # Group by type
            by_type = {}
            for error in self.errors:
                if error.error_type not in by_type:
                    by_type[error.error_type] = []
                by_type[error.error_type].append(error)
            
            for error_type, errors in by_type.items():
                print(f"\n  {error_type}:")
                for error in errors[:10]:  # Limit output
                    print(f"    ✗ {error}")
                if len(errors) > 10:
                    print(f"    ... and {len(errors) - 10} more")
            print()
        
        # Print warnings
        if self.warnings:
            print(f"WARNINGS ({len(self.warnings)}):")
            print("-" * 40)
            
            by_type = {}
            for warning in self.warnings:
                if warning.error_type not in by_type:
                    by_type[warning.error_type] = []
                by_type[warning.error_type].append(warning)
            
            for error_type, warnings in by_type.items():
                print(f"\n  {error_type}:")
                for warning in warnings[:10]:
                    print(f"    ⚠ {warning}")
                if len(warnings) > 10:
                    print(f"    ... and {len(warnings) - 10} more")
            print()
        
        if not self.errors and not self.warnings:
            print("✓ Curriculum audit passed! All weeks are on track.")
        
        print(f"\n{'='*80}")
        print("WHAT THIS AUDIT CHECKS:")
        print("-" * 40)
        print("  • All 32 weeks present (6 phases)")
        print("  • Each week covers expected topic")
        print("  • Phase declarations are correct")
        print("  • All 5 days present per week")
        print("  • Activity counts (4-8 per day)")
        print("  • Math correctness (skip-counting, etc.)")
        print(f"\n{'='*80}")
        
        return len(self.errors) == 0


def main():
    auditor = CurriculumAuditor()
    
    print(f"\n{'='*80}")
    print("COMPREHENSIVE CURRICULUM AUDIT")
    print(f"{'='*80}\n")
    
    # Get all week files
    week_files = sorted(Path(BASE).glob("generate_week*.py"))
    
    print(f"Found {len(week_files)} week files...\n")
    
    # Check for missing weeks
    auditor.audit_missing_weeks(week_files)
    
    # Audit each week
    for filepath in week_files:
        week_num = int(filepath.stem.replace('generate_week', ''))
        print(f"Auditing {filepath.name}... ", end="")
        auditor.audit_week(filepath)
        print("✓")
    
    # Print report
    success = auditor.print_report()
    
    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()
