#!/usr/bin/env python3
"""
Comprehensive quality audit for all ELA week generators.

Checks performed per file:
  1. Python syntax valid (no IndentationError, etc.)
  2. Duplicate if __name__ == "__main__" blocks
  3. Word swap data: original words actually appear in the sentence
  4. Word swap data: new words are different from original words
  5. Word swap data: grammatical role compatibility (noun→noun, adj→adj, etc.)
  6. Story text quality: length, article agreement (a/an), forced word cramming
  7. Teacher guide swap answers match the swap data
"""

import ast
import os
import re
import sys
from pathlib import Path

ELA_ROOT = Path(os.path.expanduser("~/Home_School/2nd_Grade/English_Language_Arts"))

# Part-of-speech hints for checking swap compatibility
NOUN_PATTERNS = re.compile(r'\b(animal|kitten|elephant|octopus|doctor|helmet|bubble|insect|basket|garden|umbrella|winter|cat|dog|fish|bird|tree|house|book|pen|pencil|paper|table|chair|door|window|car|boat|plane|train|bus|bicycle|rabbit|turtle|bear|wolf|fox|mouse|rabbit|sheep|cow|pig|horse|lion|tiger|monkey|penguin|dolphin|whale|seal|frog|snake|spider|butterfly|bee|ant|fly|bee|moth|worm|crab|shrimp|starfish|jellyfish)\b')
VERB_PATTERNS = re.compile(r'\b(run|jump|play|eat|sleep|walk|talk|sing|dance|read|write|draw|paint|swim|fly|climb|jump|hop|skip|roll|throw|catch|kick|hit|pull|push|lift|carry|open|close|start|stop|help|find|see|look|watch|hear|listen|smell|touch|feel|think|know|like|love|want|need|have|make|give|take|put|get|go|come|sit|stand|lie|fall|break|fix|build|create|change|grow|shrink|begin|end|finish|continue|try|work|move|turn|move)\b')

def extract_swap_data(filepath, source):
    """Extract all swap_data blocks from a file.
    
    Returns list of (line_no, sentence, original_words, new_words, data_type)
    """
    results = []
    
    # Strategy 1: Look for assignment to swap_data / SWAP_DATA variables
    # Pattern: TUESDAY_SWAP_DATA = [ ... ]
    # Pattern: swap_data = [ ... ]
    
    in_block = False
    block_start = 0
    block_lines = []
    block_name = ""
    bracket_depth = 0
    
    for i, line in enumerate(source.split('\n'), 1):
        stripped = line.strip()
        
        # Detect start of swap data block
        if not in_block:
            # Check for assignment patterns
            match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\[', stripped)
            if match and 'swap' in match.group(1).lower():
                in_block = True
                block_start = i
                block_name = match.group(1)
                block_lines = [line]
                bracket_depth = 1
                # Check if block ends on same line
                rest = line[match.end():]
                bracket_depth += rest.count('[') - rest.count(']')
                if bracket_depth <= 0:
                    in_block = False
                    results.append(parse_swap_block(block_name, block_lines, block_start))
                    block_lines = []
        else:
            block_lines.append(line)
            bracket_depth += line.count('[') - line.count(']')
            if bracket_depth <= 0:
                in_block = False
                results.append(parse_swap_block(block_name, block_lines, block_start))
                block_lines = []
    
    return results

def parse_swap_block(name, lines, start_line):
    """Parse a swap data block into structured data."""
    results = []
    
    # Join all lines and try to eval as Python
    full_text = '\n'.join(lines)
    
    # Try to extract the list literal
    try:
        tree = ast.parse(full_text, mode='eval')
        if isinstance(tree.body, ast.List):
            for idx, item in enumerate(tree.body.elts):
                line_no = start_line + idx
                if isinstance(item, ast.Tuple):
                    try:
                        sentence = ast.literal_eval(item.elts[0])
                    except:
                        sentence = None
                    
                    new_words = []
                    orig_words = []
                    
                    # Different swap data formats:
                    # Format A: (sentence, (orig1, new1), (orig2, new2))
                    # Format B: (sentence, orig_word, new_word)
                    
                    if len(item.elts) >= 3:
                        for el in item.elts[1:]:
                            if isinstance(el, ast.Tuple) and len(el) == 2:
                                try:
                                    orig, new = ast.literal_eval(el.elts[0]), ast.literal_eval(el.elts[1])
                                    orig_words.append(orig)
                                    new_words.append(new)
                                except:
                                    pass
                            elif isinstance(el, ast.Constant) or (hasattr(ast, 'Str') and isinstance(el, ast.Str)):
                                try:
                                    word = ast.literal_eval(el)
                                    if isinstance(word, str):
                                        # Could be orig_word or new_word depending on context
                                        if len(orig_words) == len(new_words):
                                            orig_words.append(word)
                                        elif len(orig_words) == len(new_words) + 1:
                                            new_words.append(word)
                                except:
                                    pass
                    
                    results.append({
                        'line': line_no,
                        'sentence': sentence,
                        'orig_words': orig_words,
                        'new_words': new_words,
                        'block_name': name,
                    })
    except:
        pass
    
    return results

def check_swap_issues(swap_item):
    """Check a single swap item for quality issues."""
    issues = []
    sentence = swap_item['sentence']
    orig_words = swap_item['orig_words']
    new_words = swap_item['new_words']
    line = swap_item['line']
    
    if not sentence:
        issues.append((line, "HIGH", "Empty or unreadable sentence in swap data"))
        return issues
    
    # Strip HTML tags for analysis
    plain = re.sub(r'<[^>]+>', '', sentence)
    sentence_words = set(re.findall(r'\b(\w+)\b', plain))
    
    for i, orig in enumerate(orig_words):
        if orig.lower() not in sentence_words:
            issues.append((line, "HIGH", f"Original word '{orig}' not found in sentence: {plain[:60]}..."))
    
    for i, new in enumerate(new_words):
        if i < len(orig_words):
            if new.lower() == orig_words[i].lower():
                issues.append((line, "HIGH", f"Swap word equals original: '{orig_words[i]}' → '{new}' (no change)"))
    
    # Check if new words are already in the sentence (different from what they replace)
    for i, new in enumerate(new_words):
        if i < len(orig_words):
            orig = orig_words[i]
            if new.lower() != orig.lower() and new.lower() in sentence_words:
                issues.append((line, "MEDIUM", f"New word '{new}' already appears in sentence (confusing)"))
    
    # Check grammatical role compatibility
    for i, (orig, new) in enumerate(zip(orig_words, new_words)):
        orig_is_noun = bool(NOUN_PATTERNS.search(orig.lower()))
        new_is_noun = bool(NOUN_PATTERNS.search(new.lower()))
        orig_is_verb = bool(VERB_PATTERNS.search(orig.lower()))
        new_is_verb = bool(VERB_PATTERNS.search(new.lower()))
        
        if orig_is_noun and not new_is_noun and not new_is_verb:
            # Check if it makes sense grammatically
            pass  # Hard to check without full grammar, skip for now
    
    return issues

def check_story_text(filepath, source):
    """Check story text for quality issues."""
    issues = []
    
    # Find long strings that look like stories (contain <strong> tags)
    for node in ast.walk(ast.parse(source)):
        if isinstance(node, ast.Constant) and isinstance(node.value, str):
            text = node.value
            if '<strong>' in text and len(text) > 80:
                plain = re.sub(r'<[^>]+>', '', text)
                words = re.findall(r'\b(\w+)\b', plain)
                word_count = len(words)
                
                # Check article agreement
                if re.search(r'\b[aA]\s+([aeiou])\w+', text):
                    for m in re.finditer(r'\b([aA])\s+(\w+)', plain):
                        article, word = m.group(1), m.group(2)
                        if word[0].lower() in 'aeiou' and article.lower() == 'a':
                            issues.append((node.lineno, "MEDIUM", f"Article error: '{article} {word}' should be 'an {word}'"))
                
                # Check for "an" before consonant
                if re.search(r'\b[aA][nN]\s+([bcdfghjklmnpqrstvwxyz])\w+', plain):
                    for m in re.finditer(r'\b([aA][nN])\s+(\w+)', plain):
                        article, word = m.group(1), m.group(2)
                        if word[0].lower() not in 'aeiou' and article.lower() == 'an':
                            issues.append((node.lineno, "MEDIUM", f"Article error: '{article} {word}' should be 'a {word}'"))
                
                # Check for nonsensical phrases
                # "bubble" + "insect" adjacent
                if re.search(r'\b(bubble|insect)\s+(bubble|insect)\b', plain):
                    issues.append((node.lineno, "MEDIUM", f"Possible repetitive/forced word usage in story"))
                
                # "a insect" / "a octopus"
                if re.search(r'\b[aA]\s+insect\b', plain):
                    issues.append((node.lineno, "HIGH", f"'a insect' should be 'an insect'"))
                if re.search(r'\b[aA]\s+octopus\b', plain):
                    issues.append((node.lineno, "HIGH", f"'a octopus' should be 'an octopus'"))
                
                # Story too short for reading comprehension
                if word_count < 40 and '<strong>' in text:
                    issues.append((node.lineno, "MEDIUM", f"Story only {word_count} words - may be too short for reading comprehension"))
                
                # Story too long
                if word_count > 350:
                    issues.append((node.lineno, "LOW", f"Story is {word_count} words - may be long for 2nd grade"))
    
    return issues

def check_syntax(filepath, source):
    """Check for syntax issues."""
    issues = []
    
    # Duplicate if __name__
    count = source.count('if __name__ == "__main__":')
    if count > 1:
        issues.append((0, "HIGH", f"Duplicate if __name__ == '__main__' blocks ({count} found)"))
    
    # Try to parse
    try:
        ast.parse(source)
    except SyntaxError as e:
        issues.append((e.lineno or 0, "HIGH", f"Syntax error: {e.msg}"))
    
    return issues

def check_teacher_answers(filepath, source):
    """Check that teacher guide swap answers actually correspond to the swap data."""
    issues = []
    # Look for TEACHER_*_SWAP_ANSWERS and compare with TUESDAY/THURSDAY_SWAP_DATA
    # This is a basic check - ensure the count matches
    
    swap_data_blocks = re.findall(r'(?:TUESDAY|THURSDAY)_SWAP_DATA\s*=\s*\[', source)
    teacher_answer_blocks = re.findall(r'TEACHER_(?:TUESDAY|THURSDAY)_SWAP_ANSWERS\s*=\s*\[', source)
    
    # Count entries in each
    tuesday_swap_count = 0
    thursday_swap_count = 0
    tuesday_teacher_count = 0
    thursday_teacher_count = 0
    
    # Extract and count list items (simple heuristic)
    tuesday_match = re.search(r'TUESDAY_SWAP_DATA\s*=\s*\[(.*?)\]', source, re.DOTALL)
    if tuesday_match:
        tuesday_swap_count = len(re.findall(r'\(', tuesday_match.group(1)))
    
    thursday_match = re.search(r'THURSDAY_SWAP_DATA\s*=\s*\[(.*?)\]', source, re.DOTALL)
    if thursday_match:
        thursday_swap_count = len(re.findall(r'\(', thursday_match.group(1)))
    
    tuesday_t_match = re.search(r'TEACHER_TUESDAY_SWAP_ANSWERS\s*=\s*\[(.*?)\]', source, re.DOTALL)
    if tuesday_t_match:
        tuesday_teacher_count = len(re.findall(r'\(', tuesday_t_match.group(1)))
    
    thursday_t_match = re.search(r'TEACHER_THURSDAY_SWAP_ANSWERS\s*=\s*\[(.*?)\]', source, re.DOTALL)
    if thursday_t_match:
        thursday_teacher_count = len(re.findall(r'\(', thursday_t_match.group(1)))
    
    if tuesday_swap_count > 0 and tuesday_teacher_count > 0 and tuesday_swap_count != tuesday_teacher_count:
        issues.append((0, "MEDIUM", f"Teacher Tuesday swap answers ({tuesday_teacher_count}) don't match swap data ({tuesday_swap_count})"))
    
    if thursday_swap_count > 0 and thursday_teacher_count > 0 and thursday_swap_count != thursday_teacher_count:
        issues.append((0, "MEDIUM", f"Teacher Thursday swap answers ({thursday_teacher_count}) don't match swap data ({thursday_swap_count})"))
    
    return issues

def audit_file(filepath):
    """Run all audits on a single file. Returns list of (line, severity, msg)."""
    rel = str(filepath.relative_to(ELA_ROOT))
    all_issues = []
    
    try:
        source = filepath.read_text()
    except Exception as e:
        return [(0, "ERROR", f"Cannot read file: {e}")]
    
    # Syntax check
    all_issues.extend(check_syntax(filepath, source))
    if any(s == "HIGH" for _, s, _ in all_issues):
        return all_issues  # Skip deeper checks if syntax broken
    
    # Swap data checks
    swap_blocks = extract_swap_data(filepath, source)
    for block in swap_blocks:
        for item in block:
            all_issues.extend(check_swap_issues(item))
    
    # Story quality checks
    all_issues.extend(check_story_text(filepath, source))
    
    # Teacher answer checks
    all_issues.extend(check_teacher_answers(filepath, source))
    
    return all_issues

def main():
    print("=" * 80)
    print("ELA CURRICULUM QUALITY AUDIT")
    print("=" * 80)
    
    # Find all week generators
    generators = []
    for week_dir in sorted(ELA_ROOT.glob("Week_*")):
        for f in week_dir.glob("generate_week*.py"):
            if "CORRUPTED" not in f.name and "backup" not in f.name and "_new" not in f.name:
                generators.append(f)
    
    print(f"\nScanning {len(generators)} week generators...\n")
    
    total_issues = 0
    high_count = 0
    medium_count = 0
    low_count = 0
    
    for filepath in generators:
        issues = audit_file(filepath)
        if issues:
            rel = filepath.relative_to(ELA_ROOT)
            print(f"📄 {rel}")
            for line, severity, msg in issues:
                if severity == "HIGH":
                    icon = "🔴"
                    high_count += 1
                elif severity == "MEDIUM":
                    icon = "🟡"
                    medium_count += 1
                elif severity == "LOW":
                    icon = "🔵"
                    low_count += 1
                else:
                    icon = "⚫"
                total_issues += 1
                print(f"  {icon} Line {line}: [{severity}] {msg}")
            print()
    
    print("=" * 80)
    print(f"AUDIT SUMMARY: {total_issues} issues found")
    print(f"  🔴 HIGH:   {high_count}")
    print(f"  🟡 MEDIUM: {medium_count}")
    print(f"  🔵 LOW:    {low_count}")
    print("=" * 80)

if __name__ == "__main__":
    main()
