#!/usr/bin/env python3
"""
Cross-Course Curriculum Verifier
=================================
Runs automated quality checks on all 2nd grade courses.

Checks per course:
  ELA:      phonics accuracy, spelling, reading passage level
  Math:     answer key verification (same-line only)
  History:  anachronism detection, factual red flags
  Science:  safety checks, scientific accuracy spot-checks

All courses: reading level tracked (reported as summary stats, not flagged individually).

Usage:
    python3 verify_curriculum.py              # all courses
    python3 verify_curriculum.py --course math
    python3 verify_curriculum.py --report     # save JSON
"""
import fitz, glob, os, re, json, sys, argparse
from collections import defaultdict
from datetime import datetime

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


# ─── Flesch-Kincaid Grade Level ───

def count_syllables(word):
    """Count syllables in a word."""
    w = word.lower().strip()
    if len(w) <= 3:
        return 1
    vowels = 'aeiouy'
    count = 0
    prev_vowel = False
    for c in w:
        is_vowel = c in vowels
        if is_vowel and not prev_vowel:
            count += 1
        prev_vowel = is_vowel
    if w.endswith('e') and count > 1:
        count -= 1
    if w.endswith('le') and len(w) > 2 and w[-3] not in vowels:
        count += 1
    return max(count, 1)


def fk_grade(text):
    """Estimate Flesch-Kincaid grade level."""
    sentences = re.findall(r'[^.!?]+[.!?]+', text)
    words = re.findall(r'[a-zA-Z]+', text)
    if not words or len(words) < 10:
        return None
    total_s = max(len(sentences), 1)
    total_w = len(words)
    total_sy = sum(count_syllables(w) for w in words)
    return round(0.39 * (total_w / total_s) + 11.8 * (total_sy / total_w) - 15.59, 1)


# ─── Shared helpers ───

def strip_html(text):
    return re.sub(r'<[^>]+>', '', text)


def get_student_pdfs(course_dir):
    """Get all student PDFs recursively (exclude Teacher Guides)."""
    all_pdfs = sorted(glob.glob(os.path.join(course_dir, '**', '*.pdf'), recursive=True))
    return [p for p in all_pdfs if 'Teacher' not in p and 'teacher' not in p]


def extract_week_day(pdf_path):
    """Extract week number and day from PDF path."""
    week_match = re.search(r'[Ww]eek[_\-]?(\d+)', pdf_path)
    week = int(week_match.group(1)) if week_match else 0
    day_match = re.search(r'(Monday|Tuesday|Wednesday|Thursday|Friday)', pdf_path, re.I)
    day = day_match.group(1).capitalize() if day_match else 'Unknown'
    return week, day


def get_passage_fks(text):
    """Extract narrative passages and return their FK scores (filtered).
    Returns list of FK scores for passages with 25+ words."""
    text_clean = strip_html(text)
    text_joined = ' '.join(text_clean.split())
    sentences = re.findall(r'[^.!?]+[.!?]+', text_joined)

    # Sentences that look like instructions, not narrative
    instruction_kw = [
        r'read\s+(each|the)', r'write\s+(a|each|your|sentences)',
        r'circle\s+(each|all)', r'underline', r'fill\s+(in|out)',
        r'match\s+(each|the)', r'sort\s+(these|the)', r'copy\s+(it|each)',
        r'unscramble', r'name\s*:', r'here\s*\'?s?\s*what',
        r'what\s+we\s+are\s+learning', r'word\s+cards', r'copy\s+practice',
        r'spelling\s+detective', r'grammar\s*:', r'phonics\s*:',
        r'pattern\s+hunt', r'word\s+builder', r'write\s+your\s+answer',
        r'this\s+week', r'vowel\s+(team|teams)',
        r'review\s+questions', r'estimate\s+first', r'count\s+on',
        r'fill\s+in\s+the\s+missing', r'today\s+you\s+will',
        r'today\s+we\s+will', r'answer\s+the\s+questions',
        r'learning\s+goal', r'today\'s\s+goal',
    ]

    narrative = []
    for sent in sentences:
        sl = sent.lower().strip()
        if len(sl.split()) < 5:
            continue
        if any(re.search(k, sl) for k in instruction_kw):
            continue
        narrative.append(sent.strip())

    # Group into passages of 3+ sentences, 25+ words
    fks = []
    current = []
    for sent in narrative:
        current.append(sent)
        if len(current) >= 3:
            passage = ' '.join(current)
            wc = len(re.findall(r'[a-zA-Z]+', passage))
            if wc >= 25:
                fk = fk_grade(passage)
                if fk and 1 <= fk <= 8:
                    fks.append(fk)
            current = []

    return fks


# ═══════════════════════════════════════════════════════════
# COURSE CHECKS
# ═══════════════════════════════════════════════════════════

def check_ela():
    """ELA: phonics accuracy, spelling typos, reading level tracking."""
    pdfs = get_student_pdfs(os.path.join(BASE, 'English_Language_Arts'))
    issues = []
    passage_fks = defaultdict(list)

    PHONICS = {
        'ai': 'a', 'ay': 'a', 'ee': 'e', 'ea': 'e',
        'oa': 'o', 'igh': 'i', 'oi': 'oi', 'oy': 'oi',
    }
    PHONICS_RE = [
        (r'AI\s+(?:makes?|sounds?\s+like|=)\s+(?:long\s+)?([aeiou])', 'ai'),
        (r'EE\s+(?:makes?|sounds?\s+like|=)\s+(?:long\s+)?([aeiou])', 'ee'),
        (r'EA\s+(?:makes?|sounds?\s+like|=)\s+(?:long\s+)?([aeiou])', 'ea'),
        (r'OA\s+(?:makes?|sounds?\s+like|=)\s+(?:long\s+)?([aeiou])', 'oa'),
        (r'IGH\s+(?:makes?|sounds?\s+like|=)\s+(?:long\s+)?([aeiou])', 'igh'),
    ]

    SPELL_TYPOS = {
        'teh': 'the', 'adn': 'and', 'taht': 'that', 'wiht': 'with',
        'recieve': 'receive', 'becuase': 'because',
    }

    for pdf_path in pdfs:
        week, day = extract_week_day(pdf_path)

        try:
            doc = fitz.open(pdf_path)
            full_text = strip_html(''.join(p.get_text() for p in doc))
            doc.close()
        except Exception:
            continue

        # Track reading level
        fks = get_passage_fks(full_text)
        passage_fks[week].extend(fks)

        # Phonics accuracy
        for pattern, team in PHONICS_RE:
            for m in re.finditer(pattern, full_text, re.I):
                claimed = m.group(1).lower()
                expected = PHONICS.get(team, claimed)
                if claimed != expected:
                    after = full_text[m.end():m.end()+30].strip().lower()
                    if not re.match(r'^(a|an|the)\s+\w+', after):
                        issues.append(
                            f'PHONICS: W{week:02d} {day}: "{m.group(0)}" — '
                            f'{team.upper()} = long {expected.upper()}, not {claimed.upper()}'
                        )

        # Spelling typos
        words = re.findall(r'\b([a-zA-Z]{4,})\b', full_text)
        for w in words:
            wl = w.lower()
            if wl in SPELL_TYPOS and wl != SPELL_TYPOS[wl]:
                issues.append(f'SPELLING: W{week:02d} {day}: "{w}" → "{SPELL_TYPOS[wl]}"')

    # Build FK summary
    all_fks = []
    fk_by_week = {}
    for w in sorted(passage_fks.keys()):
        fks = sorted(passage_fks[w])
        all_fks.extend(fks)
        fk_by_week[f'W{w:02d}'] = {
            'avg': round(sum(fks)/len(fks), 1),
            'median': round(fks[len(fks)//2], 1),
        }
    overall = {}
    if all_fks:
        overall = {
            'avg': round(sum(all_fks)/len(all_fks), 1),
            'median': round(sorted(all_fks)[len(all_fks)//2], 1),
        }

    return {
        'course': 'ELA', 'pdfs': len(pdfs), 'passages': len(all_fks),
        'issues': issues, 'reading_levels': fk_by_week, 'overall_fk': overall,
    }


def check_math():
    """Math: verify same-line answer keys (not fill-in-the-blank)."""
    pdfs = get_student_pdfs(os.path.join(BASE, 'Math'))
    issues = []
    week_fks = defaultdict(list)

    for pdf_path in pdfs:
        week, day = extract_week_day(pdf_path)

        try:
            doc = fitz.open(pdf_path)
            full_text = strip_html(''.join(p.get_text() for p in doc))
            doc.close()
        except Exception:
            continue

        # Check same-line equations only
        # Student exercises have answers blank (on next line) — skip cross-line
        # Only match lines with exactly ONE +/- operator to avoid repeated addition (3+2+3+2=10)
        for line in full_text.split('\n'):
            n_ops = len(re.findall(r'[+\-]', line))
            if n_ops != 1:
                continue
            equations = re.findall(r'(\d+)\s*([+\-])\s*(\d+)\s*= *(\d+)', line)
            for a, op, b, result in equations:
                a, b, result = int(a), int(b), int(result)
                expected = a + b if op == '+' else a - b
                if result != expected:
                    issues.append(
                        f'EQUATION: W{week:02d} {day}: "{a} {op} {b} = {result}" '
                        f'(should be {expected})'
                    )

        # Track reading level of word problems
        fks = get_passage_fks(full_text)
        week_fks[week].extend(fks)

    all_fks = []
    for fks in week_fks.values():
        all_fks.extend(fks)
    overall = {}
    if all_fks:
        overall = {'avg': round(sum(all_fks)/len(all_fks), 1)}

    return {
        'course': 'Math', 'pdfs': len(pdfs), 'passages': len(all_fks),
        'issues': issues, 'overall_fk': overall,
    }


def check_history():
    """History: anachronism detection, factual red flags, reading level tracking."""
    pdfs = get_student_pdfs(os.path.join(BASE, 'History'))
    issues = []
    passage_fks = defaultdict(list)

    ANACHRONISMS = [
        ('internet', 'caveman'), ('internet', 'ancient egypt'),
        ('computer', 'pioneer'), ('smartphone', 'colonial'),
        ('electricity', 'ancient rome'),
    ]

    RED_FLAGS = [
        (r'caveman.*\s*dinosa', 'Cavemen/dinosaurs coexistence'),
    ]

    for pdf_path in pdfs:
        week, day = extract_week_day(pdf_path)

        try:
            doc = fitz.open(pdf_path)
            full_text = strip_html(''.join(p.get_text() for p in doc))
            doc.close()
        except Exception:
            continue

        text_lower = full_text.lower()

        # Track reading level
        fks = get_passage_fks(full_text)
        passage_fks[week].extend(fks)

        # Anachronism check
        for w1, w2 in ANACHRONISMS:
            if w1 in text_lower and w2 in text_lower:
                issues.append(f'ANACHRONISM: W{week:02d} {day}: "{w1}" + "{w2}"')

        # Red flag patterns
        for pattern, desc in RED_FLAGS:
            if re.search(pattern, text_lower):
                issues.append(f'FACT_CHECK: W{week:02d} {day}: {desc}')

    all_fks = []
    for fks in passage_fks.values():
        all_fks.extend(fks)
    overall = {}
    if all_fks:
        overall = {'avg': round(sum(all_fks)/len(all_fks), 1)}

    return {
        'course': 'History', 'pdfs': len(pdfs), 'passages': len(all_fks),
        'issues': issues, 'overall_fk': overall,
    }


def check_science():
    """Science: safety checks, scientific accuracy spot-checks, reading level tracking."""
    pdfs = get_student_pdfs(os.path.join(BASE, 'Science'))
    issues = []
    passage_fks = defaultdict(list)

    SAFETY = [
        (r'taste\s+(?:unknown|chemical|cleaning)', 'Tasting unknown substances'),
        (r'touch\s+hot\s+(?:stove|fire)', 'Touching hot surfaces'),
        (r'microwave\s+metal', 'Metal in microwave'),
    ]

    for pdf_path in pdfs:
        week, day = extract_week_day(pdf_path)

        try:
            doc = fitz.open(pdf_path)
            full_text = strip_html(''.join(p.get_text() for p in doc))
            doc.close()
        except Exception:
            continue

        text_lower = full_text.lower()

        # Track reading level
        fks = get_passage_fks(full_text)
        passage_fks[week].extend(fks)

        # Safety check
        for pattern, desc in SAFETY:
            if re.search(pattern, text_lower):
                issues.append(f'SAFETY: W{week:02d} {day}: {desc}')

        # Photosynthesis: only flag if text discusses plants making food
        if re.search(r'plant.*make.*food|food.*plant.*make', text_lower):
            if 'sunlight' not in text_lower and 'sun' not in text_lower:
                issues.append(f'SCIENCE: W{week:02d} {day}: "plant making food" without sunlight')

        # Water cycle completeness
        if 'water cycle' in text_lower:
            missing = []
            for term in ['evaporat', 'condens', 'precipitat']:
                if term not in text_lower:
                    missing.append(term)
            if missing and len(missing) < 3:
                issues.append(
                    f'SCIENCE: W{week:02d} {day}: water cycle incomplete ({", ".join(missing)})'
                )

        # Earth orbit
        if 'earth' in text_lower and 'orbit' in text_lower:
            if re.search(r'earth.*orbit.*moon', text_lower):
                if 'sun' not in text_lower:
                    issues.append(f'SCIENCE: W{week:02d} {day}: earth orbit without sun')

    all_fks = []
    for fks in passage_fks.values():
        all_fks.extend(fks)
    overall = {}
    if all_fks:
        overall = {'avg': round(sum(all_fks)/len(all_fks), 1)}

    return {
        'course': 'Science', 'pdfs': len(pdfs), 'passages': len(all_fks),
        'issues': issues, 'overall_fk': overall,
    }


# ═══════════════════════════════════════════════════════════
# OUTPUT
# ═══════════════════════════════════════════════════════════

def print_issues(issues, limit=10):
    """Print issues, truncating if too many."""
    for issue in issues[:limit]:
        print(f'  ⚠ {issue}')
    if len(issues) > limit:
        print(f'  ... and {len(issues) - limit} more')


def main():
    parser = argparse.ArgumentParser(description='Curriculum quality verifier')
    parser.add_argument('--course', default='all',
                        help='Course: ela, math, history, science, all')
    parser.add_argument('--report', action='store_true',
                        help='Save JSON report')
    args = parser.parse_args()

    courses = {
        'ela': check_ela,
        'math': check_math,
        'history': check_history,
        'science': check_science,
    }

    if args.course != 'all':
        key = args.course.lower()
        if key in courses:
            courses = {key: courses[key]}
        else:
            print(f'Unknown course: {key}. Available: ela, math, history, science, all')
            return 1

    results = {}
    total_issues = 0

    for name, check_fn in courses.items():
        print(f'\n{"="*55}')
        print(f'Checking {name.upper()}...')
        print('='*55)

        try:
            result = check_fn()
            results[name] = result

            print(f'  PDFs: {result["pdfs"]}')
            if 'passages' in result:
                print(f'  Passages: {result["passages"]}')
            issue_count = len(result.get('issues', []))
            print(f'  Issues: {issue_count}')

            if result.get('overall_fk'):
                fk = result['overall_fk']
                print(f'  Reading level: avg={fk.get("avg", "?")}, '
                      f'median={fk.get("median", "?")}')

            total_issues += issue_count

            if issues_to_show := result.get('issues', []):
                print_issues(issues_to_show)

        except Exception as e:
            print(f'  āŒ ERROR: {e}')
            import traceback
            traceback.print_exc()
            results[name] = {'error': str(e)}

    # Summary
    print(f'\n{"="*55}')
    print('SUMMARY')
    print('='*55)
    print(f'\n{"Course":<10} {"PDFs":>6} {"Issues":>8} {"Reading":>8}')
    print('-'*36)
    for name, result in results.items():
        pdfs = result.get('pdfs', 0)
        n_issues = len(result.get('issues', []))
        fk_avg = result.get('overall_fk', {}).get('avg', '-')
        print(f'{name.upper():<10} {pdfs:>6} {n_issues:>8} {"avg=" + str(fk_avg):>8}')

    print(f'\nTotal issues: {total_issues}')

    if total_issues == 0:
        print('\nāœ… All courses pass automated checks.')
    else:
        # Categorize issues
        cats = defaultdict(list)
        for result in results.values():
            for issue in result.get('issues', []):
                cat = issue.split(':')[0]
                cats[cat].append(issue)
        print('\nBreakdown:')
        for cat, cat_issues in sorted(cats.items()):
            print(f'  {cat}: {len(cat_issues)}')

    # Save report
    if args.report:
        report = {
            'timestamp': datetime.now().isoformat(),
            'total_issues': total_issues,
            'courses': results,
        }
        path = os.path.join(BASE, 'verification_report.json')
        with open(path, 'w') as f:
            json.dump(report, f, indent=2)
        print(f'\nReport: {path}')

    return 1 if total_issues > 0 else 0


if __name__ == '__main__':
    sys.exit(main())