#!/usr/bin/env python3
"""
RF Device Identification Benchmarking Suite

Measures accuracy, precision, recall, and confidence distribution
across known protocol test signals.
"""

import sys
from pathlib import Path
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass, field
from collections import defaultdict
import json

# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from src.parser.sub_parser import parse_sub_file
from src.matcher.pattern_decoder import get_pattern_decoder, DeviceMatch


@dataclass
class BenchmarkResult:
    """Result for a single test case"""
    test_file: str
    expected_protocol: str
    expected_metadata: Dict

    # Results
    top_match: Optional[str] = None
    top_confidence: float = 0.0
    all_matches: List[DeviceMatch] = field(default_factory=list)

    # Metrics
    correct: bool = False
    rank: int = -1  # Rank of correct protocol in results (1-indexed, -1 if not found)

    # Performance
    parse_time_ms: float = 0.0
    match_time_ms: float = 0.0


@dataclass
class ProtocolStats:
    """Statistics for a specific protocol"""
    protocol_name: str
    total_tests: int = 0
    correct_top1: int = 0
    correct_top3: int = 0
    correct_top5: int = 0
    not_found: int = 0

    avg_confidence_when_correct: float = 0.0
    avg_confidence_when_wrong: float = 0.0
    avg_rank: float = 0.0

    confidences_correct: List[float] = field(default_factory=list)
    confidences_wrong: List[float] = field(default_factory=list)


@dataclass
class BenchmarkSummary:
    """Overall benchmark statistics"""
    total_tests: int = 0

    # Top-K accuracy
    top1_accuracy: float = 0.0
    top3_accuracy: float = 0.0
    top5_accuracy: float = 0.0

    # Confidence distribution
    high_confidence_count: int = 0  # >80%
    medium_confidence_count: int = 0  # 50-80%
    low_confidence_count: int = 0  # <50%

    # Per-protocol stats
    protocol_stats: Dict[str, ProtocolStats] = field(default_factory=dict)

    # Performance
    avg_parse_time_ms: float = 0.0
    avg_match_time_ms: float = 0.0

    # All results
    results: List[BenchmarkResult] = field(default_factory=list)


class RFBenchmark:
    """RF device identification benchmark runner"""

    def __init__(self):
        self.decoder = get_pattern_decoder()

    def run_benchmark(
        self,
        test_cases: List[Tuple[Path, str, Dict]]
    ) -> BenchmarkSummary:
        """
        Run benchmark on test cases

        Args:
            test_cases: List of (filepath, expected_protocol, metadata)

        Returns:
            BenchmarkSummary with all metrics
        """
        summary = BenchmarkSummary()
        summary.total_tests = len(test_cases)

        print(f"Running benchmark on {len(test_cases)} test cases...")
        print()

        for i, (filepath, expected_protocol, metadata) in enumerate(test_cases, 1):
            print(f"[{i}/{len(test_cases)}] Testing {filepath.name}...", end=" ")

            result = self._test_single_file(filepath, expected_protocol, metadata)
            summary.results.append(result)

            # Update protocol stats
            if expected_protocol not in summary.protocol_stats:
                summary.protocol_stats[expected_protocol] = ProtocolStats(
                    protocol_name=expected_protocol
                )

            stats = summary.protocol_stats[expected_protocol]
            stats.total_tests += 1

            if result.correct and result.rank == 1:
                stats.correct_top1 += 1
                stats.confidences_correct.append(result.top_confidence)
                print(f"✓ CORRECT (confidence: {result.top_confidence:.1%})")
            elif result.rank > 0 and result.rank <= 3:
                stats.correct_top3 += 1
                print(f"⚠ Rank {result.rank} (expected in top 3)")
            elif result.rank > 0 and result.rank <= 5:
                stats.correct_top5 += 1
                print(f"⚠ Rank {result.rank} (expected in top 5)")
            else:
                stats.not_found += 1
                stats.confidences_wrong.append(result.top_confidence)
                print(f"✗ WRONG: {result.top_match} (confidence: {result.top_confidence:.1%})")

        print()

        # Calculate aggregate metrics
        self._calculate_summary_metrics(summary)

        return summary

    def _test_single_file(
        self,
        filepath: Path,
        expected_protocol: str,
        metadata: Dict
    ) -> BenchmarkResult:
        """Test a single .sub file"""
        import time

        result = BenchmarkResult(
            test_file=str(filepath),
            expected_protocol=expected_protocol,
            expected_metadata=metadata
        )

        # Parse file
        t0 = time.time()
        try:
            signal_metadata = parse_sub_file(str(filepath))
        except Exception as e:
            print(f"Parse error: {e}")
            return result
        result.parse_time_ms = (time.time() - t0) * 1000

        # Run matching
        t0 = time.time()
        try:
            matches = self.decoder.decode(signal_metadata)
            result.all_matches = matches
        except Exception as e:
            print(f"Match error: {e}")
            return result
        result.match_time_ms = (time.time() - t0) * 1000

        if not matches:
            return result

        # Extract top match
        result.top_match = matches[0].name
        result.top_confidence = matches[0].confidence

        # Check if correct protocol is in results
        for rank, match in enumerate(matches, 1):
            if self._protocol_matches(match.name, expected_protocol):
                result.correct = (rank == 1)
                result.rank = rank
                break

        return result

    def _protocol_matches(self, actual: str, expected: str) -> bool:
        """Check if protocol names match (fuzzy matching)"""
        actual_lower = actual.lower()
        expected_lower = expected.lower()

        # Exact match
        if actual_lower == expected_lower:
            return True

        # Partial match (handle variations like "LaCrosse TX141-BV2" vs "LaCrosse-TX141TH-BV2")
        actual_parts = set(actual_lower.replace('-', ' ').split())
        expected_parts = set(expected_lower.replace('-', ' ').split())

        # If 2+ common tokens, consider match
        common = actual_parts & expected_parts
        if len(common) >= 2:
            return True

        return False

    def _calculate_summary_metrics(self, summary: BenchmarkSummary):
        """Calculate aggregate metrics from individual results"""
        if summary.total_tests == 0:
            return

        # Top-K accuracy
        top1_correct = sum(1 for r in summary.results if r.correct and r.rank == 1)
        top3_correct = sum(1 for r in summary.results if r.rank > 0 and r.rank <= 3)
        top5_correct = sum(1 for r in summary.results if r.rank > 0 and r.rank <= 5)

        summary.top1_accuracy = top1_correct / summary.total_tests
        summary.top3_accuracy = top3_correct / summary.total_tests
        summary.top5_accuracy = top5_correct / summary.total_tests

        # Confidence distribution
        for result in summary.results:
            if result.top_confidence >= 0.8:
                summary.high_confidence_count += 1
            elif result.top_confidence >= 0.5:
                summary.medium_confidence_count += 1
            else:
                summary.low_confidence_count += 1

        # Performance
        if summary.results:
            summary.avg_parse_time_ms = sum(r.parse_time_ms for r in summary.results) / len(summary.results)
            summary.avg_match_time_ms = sum(r.match_time_ms for r in summary.results) / len(summary.results)

        # Per-protocol stats
        for protocol, stats in summary.protocol_stats.items():
            if stats.total_tests > 0:
                stats.correct_top3 = stats.correct_top1  # Already counted in loop
                stats.correct_top5 = stats.correct_top1

            if stats.confidences_correct:
                stats.avg_confidence_when_correct = sum(stats.confidences_correct) / len(stats.confidences_correct)

            if stats.confidences_wrong:
                stats.avg_confidence_when_wrong = sum(stats.confidences_wrong) / len(stats.confidences_wrong)

    def print_summary(self, summary: BenchmarkSummary):
        """Print human-readable summary"""
        print("=" * 80)
        print("BENCHMARK SUMMARY")
        print("=" * 80)
        print()

        print(f"Total Tests: {summary.total_tests}")
        print()

        print("TOP-K ACCURACY:")
        print(f"  Top-1: {summary.top1_accuracy:.1%} ({int(summary.top1_accuracy * summary.total_tests)}/{summary.total_tests})")
        print(f"  Top-3: {summary.top3_accuracy:.1%} ({int(summary.top3_accuracy * summary.total_tests)}/{summary.total_tests})")
        print(f"  Top-5: {summary.top5_accuracy:.1%} ({int(summary.top5_accuracy * summary.total_tests)}/{summary.total_tests})")
        print()

        print("CONFIDENCE DISTRIBUTION:")
        print(f"  High (>80%):   {summary.high_confidence_count} ({summary.high_confidence_count/summary.total_tests:.1%})")
        print(f"  Medium (50-80%): {summary.medium_confidence_count} ({summary.medium_confidence_count/summary.total_tests:.1%})")
        print(f"  Low (<50%):    {summary.low_confidence_count} ({summary.low_confidence_count/summary.total_tests:.1%})")
        print()

        print("PERFORMANCE:")
        print(f"  Avg Parse Time: {summary.avg_parse_time_ms:.2f} ms")
        print(f"  Avg Match Time: {summary.avg_match_time_ms:.2f} ms")
        print(f"  Total Time:     {summary.avg_parse_time_ms + summary.avg_match_time_ms:.2f} ms")
        print()

        print("PER-PROTOCOL ACCURACY:")
        print(f"{'Protocol':<40} {'Tests':>6} {'Accuracy':>10} {'Avg Conf':>10}")
        print("-" * 80)

        # Sort by accuracy (worst first for tuning focus)
        sorted_protocols = sorted(
            summary.protocol_stats.items(),
            key=lambda x: x[1].correct_top1 / x[1].total_tests if x[1].total_tests > 0 else 0
        )

        for protocol_name, stats in sorted_protocols:
            accuracy = stats.correct_top1 / stats.total_tests if stats.total_tests > 0 else 0
            avg_conf = stats.avg_confidence_when_correct if stats.avg_confidence_when_correct > 0 else stats.avg_confidence_when_wrong

            print(f"{protocol_name:<40} {stats.total_tests:>6} {accuracy:>9.1%} {avg_conf:>9.1%}")

        print()

        # Worst performers
        print("WORST PERFORMERS (need tuning):")
        worst = sorted_protocols[:5]
        for protocol_name, stats in worst:
            accuracy = stats.correct_top1 / stats.total_tests if stats.total_tests > 0 else 0
            if accuracy < 1.0:
                print(f"  - {protocol_name}: {accuracy:.1%} ({stats.correct_top1}/{stats.total_tests})")

        print()

    def save_report(self, summary: BenchmarkSummary, output_path: Path):
        """Save detailed report to markdown"""
        with open(output_path, 'w') as f:
            f.write("# RF Device Identification - Benchmark Results\n\n")

            f.write(f"**Date**: {Path(__file__).stat().st_mtime}\n")
            f.write(f"**Total Tests**: {summary.total_tests}\n\n")

            f.write("## Overall Metrics\n\n")
            f.write("### Top-K Accuracy\n\n")
            f.write(f"- **Top-1**: {summary.top1_accuracy:.1%}\n")
            f.write(f"- **Top-3**: {summary.top3_accuracy:.1%}\n")
            f.write(f"- **Top-5**: {summary.top5_accuracy:.1%}\n\n")

            f.write("### Confidence Distribution\n\n")
            f.write(f"- **High (>80%)**: {summary.high_confidence_count} ({summary.high_confidence_count/summary.total_tests:.1%})\n")
            f.write(f"- **Medium (50-80%)**: {summary.medium_confidence_count} ({summary.medium_confidence_count/summary.total_tests:.1%})\n")
            f.write(f"- **Low (<50%)**: {summary.low_confidence_count} ({summary.low_confidence_count/summary.total_tests:.1%})\n\n")

            f.write("### Performance\n\n")
            f.write(f"- **Avg Parse Time**: {summary.avg_parse_time_ms:.2f} ms\n")
            f.write(f"- **Avg Match Time**: {summary.avg_match_time_ms:.2f} ms\n")
            f.write(f"- **Total**: {summary.avg_parse_time_ms + summary.avg_match_time_ms:.2f} ms\n\n")

            f.write("## Per-Protocol Results\n\n")
            f.write("| Protocol | Tests | Top-1 Acc | Avg Confidence |\n")
            f.write("|----------|-------|-----------|----------------|\n")

            sorted_protocols = sorted(
                summary.protocol_stats.items(),
                key=lambda x: x[1].correct_top1 / x[1].total_tests if x[1].total_tests > 0 else 0
            )

            for protocol_name, stats in sorted_protocols:
                accuracy = stats.correct_top1 / stats.total_tests if stats.total_tests > 0 else 0
                avg_conf = stats.avg_confidence_when_correct if stats.avg_confidence_when_correct > 0 else stats.avg_confidence_when_wrong

                f.write(f"| {protocol_name} | {stats.total_tests} | {accuracy:.1%} | {avg_conf:.1%} |\n")

            f.write("\n## Detailed Results\n\n")

            for result in summary.results:
                status = "✓" if result.correct else "✗"
                f.write(f"### {status} {Path(result.test_file).name}\n\n")
                f.write(f"- **Expected**: {result.expected_protocol}\n")
                f.write(f"- **Got**: {result.top_match} (confidence: {result.top_confidence:.1%})\n")
                f.write(f"- **Rank**: {result.rank if result.rank > 0 else 'Not Found'}\n")

                if result.all_matches:
                    f.write(f"\n**Top 5 Matches**:\n")
                    for i, match in enumerate(result.all_matches[:5], 1):
                        f.write(f"{i}. {match.name} ({match.confidence:.1%})\n")

                f.write("\n")


def main():
    """Main benchmark runner"""
    # Generate synthetic test signals
    benchmark_dir = Path(__file__).parent.parent / "tests" / "benchmark"
    sys.path.insert(0, str(benchmark_dir))
    from test_data_generator import SyntheticSignalGenerator

    print("=" * 80)
    print("RF DEVICE IDENTIFICATION BENCHMARK")
    print("=" * 80)
    print()

    # Setup
    synthetic_dir = benchmark_dir / "synthetic_signals"

    generator = SyntheticSignalGenerator(synthetic_dir)

    print("Step 1: Generating synthetic test signals...")
    test_cases = generator.generate_test_suite()
    print(f"  Generated {len(test_cases)} test signals")
    print()

    # Run benchmark
    print("Step 2: Running benchmark...")
    benchmark = RFBenchmark()
    summary = benchmark.run_benchmark(test_cases)

    # Print results
    print()
    benchmark.print_summary(summary)

    # Save report
    report_path = Path(__file__).parent.parent / "TEST_RESULTS_SUMMARY.md"
    benchmark.save_report(summary, report_path)
    print(f"Detailed report saved to: {report_path}")
    print()

    # Return exit code based on accuracy
    if summary.top1_accuracy >= 0.25:  # 25% target
        print("✓ Benchmark PASSED (Top-1 accuracy >= 25%)")
        return 0
    else:
        print(f"✗ Benchmark FAILED (Top-1 accuracy {summary.top1_accuracy:.1%} < 25%)")
        return 1


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