#!/usr/bin/env python3
"""
Validate Lark grammars against TCK queries.

Tests each generated grammar against the extracted query corpus
and reports completeness metrics.
"""

import json
import time
from pathlib import Path
from dataclasses import dataclass, field
from typing import Optional
import sys


@dataclass
class ValidationResult:
    grammar_name: str
    total_queries: int = 0
    parsed_ok: int = 0
    parse_failed: int = 0
    parse_time_ms: float = 0.0
    failures: list = field(default_factory=list)

    @property
    def success_rate(self) -> float:
        if self.total_queries == 0:
            return 0.0
        return (self.parsed_ok / self.total_queries) * 100


def load_grammar(grammar_path: Path) -> Optional['lark.Lark']:
    """Load a Lark grammar file and create parser."""
    try:
        import lark
    except ImportError:
        print("Error: lark package not installed. Run: pip install lark")
        sys.exit(1)

    try:
        grammar_text = grammar_path.read_text()

        # Try to create parser
        parser = lark.Lark(
            grammar_text,
            parser='earley',  # More permissive parser
            ambiguity='explicit',
            propagate_positions=True,
        )
        return parser
    except Exception as e:
        print(f"Failed to load grammar {grammar_path.name}: {e}")
        return None


def validate_grammar(grammar_path: Path, queries: list[dict], max_failures: int = 50) -> ValidationResult:
    """Validate a grammar against the query corpus."""
    result = ValidationResult(grammar_name=grammar_path.name)
    result.total_queries = len(queries)

    parser = load_grammar(grammar_path)
    if parser is None:
        result.parse_failed = len(queries)
        result.failures.append({
            'error': 'Grammar failed to load',
            'query': None,
        })
        return result

    start_time = time.time()

    for q in queries:
        query_text = q.get('query', '')
        if not query_text.strip():
            continue

        try:
            parser.parse(query_text)
            result.parsed_ok += 1
        except Exception as e:
            result.parse_failed += 1
            if len(result.failures) < max_failures:
                result.failures.append({
                    'query': query_text[:200],
                    'error': str(e)[:200],
                    'source': q.get('source_file', 'unknown'),
                    'category': q.get('category', 'unknown'),
                })

    result.parse_time_ms = (time.time() - start_time) * 1000

    return result


def analyze_failure_patterns(failures: list) -> dict:
    """Analyze common patterns in parse failures."""
    patterns = {}

    for f in failures:
        error = f.get('error', '')
        query = f.get('query', '')

        # Extract key patterns from errors
        if 'Unexpected token' in error:
            patterns['unexpected_token'] = patterns.get('unexpected_token', 0) + 1
        if 'Expected' in error:
            patterns['expected_token'] = patterns.get('expected_token', 0) + 1
        if 'No terminal' in error:
            patterns['missing_terminal'] = patterns.get('missing_terminal', 0) + 1

        # Check query patterns
        if not query:
            continue
        query_upper = query.upper()
        if 'FOREACH' in query_upper:
            patterns['foreach'] = patterns.get('foreach', 0) + 1
        if 'LOAD CSV' in query_upper:
            patterns['load_csv'] = patterns.get('load_csv', 0) + 1
        if 'USING' in query_upper:
            patterns['hints'] = patterns.get('hints', 0) + 1
        if 'EXISTS' in query_upper and '{' in query:
            patterns['exists_subquery'] = patterns.get('exists_subquery', 0) + 1
        if 'shortestPath' in query.lower():
            patterns['shortest_path'] = patterns.get('shortest_path', 0) + 1
        if 'REDUCE' in query_upper:
            patterns['reduce'] = patterns.get('reduce', 0) + 1

    return patterns


def main():
    grammars_dir = Path(__file__).parent
    lark_dir = grammars_dir / 'lark_grammars'
    queries_file = grammars_dir / 'tck-queries' / 'all_queries.json'

    # Load queries
    print("Loading test queries...")
    with open(queries_file) as f:
        queries = json.load(f)
    print(f"Loaded {len(queries)} queries\n")

    # Find all Lark grammars
    lark_files = sorted(lark_dir.glob('*.lark'))
    print(f"Found {len(lark_files)} grammar files to test:\n")
    for f in lark_files:
        print(f"  - {f.name}")
    print()

    # Also test nxcypher's embedded grammar
    nxcypher_result = None
    try:
        sys.path.insert(0, str(grammars_dir.parent))
        from nxcypher import NxCypher

        print("Testing nxcypher embedded grammar...")
        nxcypher = NxCypher(None)  # Graph not needed for parsing

        nxcypher_result = ValidationResult(grammar_name="nxcypher (embedded)")
        nxcypher_result.total_queries = len(queries)

        start_time = time.time()
        for q in queries:
            query_text = q.get('query', '')
            if not query_text.strip():
                continue
            try:
                nxcypher.parser.parse(query_text)
                nxcypher_result.parsed_ok += 1
            except Exception as e:
                nxcypher_result.parse_failed += 1
                if len(nxcypher_result.failures) < 50:
                    nxcypher_result.failures.append({
                        'query': query_text[:200],
                        'error': str(e)[:200],
                        'category': q.get('category', 'unknown'),
                    })
        nxcypher_result.parse_time_ms = (time.time() - start_time) * 1000
        print(f"  nxcypher: {nxcypher_result.success_rate:.1f}% ({nxcypher_result.parsed_ok}/{nxcypher_result.total_queries})\n")
    except Exception as e:
        print(f"Could not test nxcypher grammar: {e}\n")

    # Test each generated grammar
    results = []
    for grammar_path in lark_files:
        print(f"Testing {grammar_path.name}...")
        result = validate_grammar(grammar_path, queries)
        results.append(result)
        print(f"  {result.success_rate:.1f}% success ({result.parsed_ok}/{result.total_queries}) in {result.parse_time_ms:.0f}ms")

    # Summary report
    print("\n" + "=" * 70)
    print("VALIDATION SUMMARY")
    print("=" * 70)
    print(f"\n{'Grammar':<40} {'Success':<12} {'Rate':<10} {'Time':<10}")
    print("-" * 70)

    if nxcypher_result:
        print(f"{'nxcypher (embedded)':<40} {nxcypher_result.parsed_ok:>5}/{nxcypher_result.total_queries:<5} {nxcypher_result.success_rate:>6.1f}%    {nxcypher_result.parse_time_ms:>6.0f}ms")

    for r in sorted(results, key=lambda x: -x.success_rate):
        print(f"{r.grammar_name:<40} {r.parsed_ok:>5}/{r.total_queries:<5} {r.success_rate:>6.1f}%    {r.parse_time_ms:>6.0f}ms")

    # Failure analysis for best grammar
    best_generated = max(results, key=lambda x: x.success_rate) if results else None

    if best_generated and best_generated.failures:
        print(f"\n{'=' * 70}")
        print(f"FAILURE ANALYSIS for {best_generated.grammar_name}")
        print("=" * 70)

        patterns = analyze_failure_patterns(best_generated.failures)
        if patterns:
            print("\nCommon failure patterns:")
            for pattern, count in sorted(patterns.items(), key=lambda x: -x[1]):
                print(f"  {pattern}: {count}")

        print(f"\nFirst 10 failures:")
        for i, f in enumerate(best_generated.failures[:10]):
            query = f.get('query') or ''
            error = f.get('error') or ''
            print(f"\n{i+1}. Category: {f.get('category', 'unknown')}")
            print(f"   Query: {query[:100]}...")
            print(f"   Error: {error[:100]}")

    # Save detailed results
    output_file = grammars_dir / 'validation_results.json'
    results_data = {
        'summary': {
            'total_queries': len(queries),
            'grammars_tested': len(results) + (1 if nxcypher_result else 0),
        },
        'results': []
    }

    if nxcypher_result:
        results_data['results'].append({
            'name': nxcypher_result.grammar_name,
            'parsed_ok': nxcypher_result.parsed_ok,
            'parse_failed': nxcypher_result.parse_failed,
            'success_rate': nxcypher_result.success_rate,
            'parse_time_ms': nxcypher_result.parse_time_ms,
            'failure_patterns': analyze_failure_patterns(nxcypher_result.failures),
        })

    for r in results:
        results_data['results'].append({
            'name': r.grammar_name,
            'parsed_ok': r.parsed_ok,
            'parse_failed': r.parse_failed,
            'success_rate': r.success_rate,
            'parse_time_ms': r.parse_time_ms,
            'failure_patterns': analyze_failure_patterns(r.failures),
        })

    with open(output_file, 'w') as f:
        json.dump(results_data, f, indent=2)
    print(f"\nDetailed results saved to {output_file}")


if __name__ == '__main__':
    main()
