"""Telemetry completeness validation for Story 6-8.

This module validates that telemetry sent by the SDK is successfully
received by the OTel Collector with 99%+ completeness.
"""

import json
import logging
import subprocess
import time
from pathlib import Path
from typing import Dict, Optional

logger = logging.getLogger(__name__)


class TelemetryCompletenessValidator:
    """Validate telemetry completeness between SDK and Collector."""

    def __init__(
        self,
        export_file_path: str = "/home/coder/emms/sdk.worktrees/development/mca-prototype/test-exports/otel-metrics.json",
        container_name: str = "mca-otel-collector",
    ):
        """Initialize completeness validator.

        Args:
            export_file_path: Path to OTel file exporter output
            container_name: Docker container name
        """
        self.export_file_path = Path(export_file_path)
        self.container_name = container_name

    def wait_for_collector_flush(self, wait_seconds: float = 10.0) -> bool:
        """Wait for collector to naturally flush buffers.

        This is safer than sending SIGHUP which can trigger config reload.
        The collector's batch processor has a timeout (5s) that will cause
        natural flushing.

        Args:
            wait_seconds: How long to wait for natural flush

        Returns:
            True (always returns True, waiting is best-effort)
        """
        logger.info(f"Waiting {wait_seconds}s for collector to flush buffers...")
        time.sleep(wait_seconds)
        return True

    def count_exported_items(self, signal_type: str = "metrics") -> int:
        """Count individual data points in file export.

        Properly parses OTLP JSON format to count actual metric/log/trace points,
        not just batch entries.

        Args:
            signal_type: Signal type to count (metrics, logs, traces)

        Returns:
            Number of exported individual data points
        """
        # Map signal type to file path
        file_paths = {
            "metrics": self.export_file_path,
            "logs": self.export_file_path.parent / "otel-logs.json",
            "traces": self.export_file_path.parent / "otel-traces.json",
        }

        file_path = file_paths.get(signal_type, self.export_file_path)

        if not file_path.exists():
            logger.warning(f"Export file not found: {file_path}")
            return 0

        try:
            total_points = 0

            with open(file_path, "r") as f:
                for line_num, line in enumerate(f, 1):
                    line = line.strip()
                    if not line:
                        continue

                    try:
                        batch = json.loads(line)

                        # Parse OTLP JSON format
                        if signal_type == "metrics":
                            # Navigate: resourceMetrics -> scopeMetrics -> metrics -> dataPoints
                            for resource_metric in batch.get("resourceMetrics", []):
                                for scope_metric in resource_metric.get("scopeMetrics", []):
                                    for metric in scope_metric.get("metrics", []):
                                        # Count data points based on metric type
                                        if "gauge" in metric:
                                            total_points += len(
                                                metric["gauge"].get("dataPoints", [])
                                            )
                                        elif "sum" in metric:
                                            total_points += len(metric["sum"].get("dataPoints", []))
                                        elif "histogram" in metric:
                                            total_points += len(
                                                metric["histogram"].get("dataPoints", [])
                                            )
                                        elif "summary" in metric:
                                            total_points += len(
                                                metric["summary"].get("dataPoints", [])
                                            )

                        elif signal_type == "logs":
                            # Navigate: resourceLogs -> scopeLogs -> logRecords
                            for resource_log in batch.get("resourceLogs", []):
                                for scope_log in resource_log.get("scopeLogs", []):
                                    total_points += len(scope_log.get("logRecords", []))

                        elif signal_type == "traces":
                            # Navigate: resourceSpans -> scopeSpans -> spans
                            for resource_span in batch.get("resourceSpans", []):
                                for scope_span in resource_span.get("scopeSpans", []):
                                    total_points += len(scope_span.get("spans", []))

                    except json.JSONDecodeError as e:
                        logger.warning(f"Failed to parse line {line_num} in {file_path.name}: {e}")
                        continue

            logger.info(f"Counted {total_points} individual data points in {file_path.name}")
            return total_points

        except Exception as e:
            logger.error(f"Failed to count exported items: {e}")
            return 0

    def validate_completeness(
        self,
        sdk_sent_count: int,
        threshold_percent: float = 99.0,
        signal_type: str = "metrics",
        wait_for_flush: bool = True,
    ) -> Dict:
        """Validate telemetry completeness.

        Args:
            sdk_sent_count: Number of items sent by SDK
            threshold_percent: Minimum completeness threshold
            signal_type: Signal type to validate
            wait_for_flush: Whether to wait for natural collector flush

        Returns:
            Dict with validation results
        """
        if wait_for_flush:
            self.wait_for_collector_flush()

        # Count received items
        collector_received_count = self.count_exported_items(signal_type)

        # Calculate completeness
        completeness_percent = 0.0
        if sdk_sent_count > 0:
            completeness_percent = (collector_received_count / sdk_sent_count) * 100

        passed = completeness_percent >= threshold_percent

        result = {
            "signal_type": signal_type,
            "sdk_sent": sdk_sent_count,
            "collector_received": collector_received_count,
            "completeness_percent": completeness_percent,
            "threshold_percent": threshold_percent,
            "passed": passed,
            "missing_count": max(0, sdk_sent_count - collector_received_count),
        }

        return result

    def print_validation_result(self, result: Dict) -> None:
        """Print validation result.

        Args:
            result: Result dict from validate_completeness()
        """
        logger.info("\n" + "=" * 60)
        logger.info("TELEMETRY COMPLETENESS VALIDATION")
        logger.info("=" * 60)
        logger.info(f"Signal Type: {result['signal_type']}")
        logger.info(f"SDK Sent: {result['sdk_sent']:,} items")
        logger.info(f"Collector Received: {result['collector_received']:,} items")
        logger.info(f"Completeness: {result['completeness_percent']:.2f}%")
        logger.info(f"Threshold: {result['threshold_percent']:.2f}%")
        logger.info(f"Missing: {result['missing_count']:,} items")
        logger.info("")
        logger.info(f"Result: {'PASS' if result['passed'] else 'FAIL'}")
        logger.info("=" * 60)

    def clear_export_files(self) -> None:
        """Clear existing export files for fresh test."""
        for signal in ["metrics", "logs", "traces"]:
            file_paths = {
                "metrics": self.export_file_path,
                "logs": self.export_file_path.parent / "otel-logs.json",
                "traces": self.export_file_path.parent / "otel-traces.json",
            }

            file_path = file_paths.get(signal)
            if file_path and file_path.exists():
                try:
                    file_path.unlink()
                    logger.info(f"Cleared {file_path.name}")
                except Exception as e:
                    logger.warning(f"Failed to clear {file_path}: {e}")

    def get_buffer_stats_sent_count(self, client) -> Dict[str, int]:
        """Extract sent counts from MCAClient buffer_stats.

        Args:
            client: MCAClient instance

        Returns:
            Dict with signal type -> items enqueued count
        """
        if not hasattr(client, "buffer_stats"):
            logger.warning("Client does not have buffer_stats() method")
            return {}

        buffer_stats = client.buffer_stats()
        sent_counts = {}

        if "per_signal_stats" in buffer_stats:
            for signal_name, signal_stats in buffer_stats["per_signal_stats"].items():
                items_enqueued = signal_stats.get("items_enqueued", 0)
                sent_counts[signal_name] = items_enqueued

        return sent_counts


def validate_load_test_completeness(
    client,
    threshold_percent: float = 99.0,
    wait_for_flush: bool = True,
) -> Dict:
    """Convenience function to validate completeness after load test.

    Args:
        client: MCAClient instance that ran the load test
        threshold_percent: Minimum completeness threshold
        wait_for_flush: Whether to wait for natural collector flush

    Returns:
        Dict with validation results for all signals
    """
    validator = TelemetryCompletenessValidator()

    # Get sent counts from SDK
    sent_counts = validator.get_buffer_stats_sent_count(client)

    if not sent_counts:
        logger.warning("No buffer stats available from SDK")
        return {}

    # Validate each signal type
    results = {}
    for signal_name, sent_count in sent_counts.items():
        if sent_count > 0:
            result = validator.validate_completeness(
                sdk_sent_count=sent_count,
                threshold_percent=threshold_percent,
                signal_type=signal_name,
                wait_for_flush=wait_for_flush,
            )
            results[signal_name] = result
            validator.print_validation_result(result)

    return results


if __name__ == "__main__":
    # Test completeness validation
    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

    validator = TelemetryCompletenessValidator()

    # Example: validate 1000 sent items
    result = validator.validate_completeness(
        sdk_sent_count=1000,
        threshold_percent=99.0,
        signal_type="metrics",
    )

    validator.print_validation_result(result)

    if result["passed"]:
        logger.info("Completeness validation PASSED")
    else:
        logger.error("Completeness validation FAILED")
