"""Collector performance monitoring for Story 6-8.

This module provides utilities to monitor OTel Collector performance:
- Prometheus metrics scraping (:8889)
- Docker container stats (CPU, memory)
- Batch processor efficiency tracking
"""

import json
import logging
import re
import subprocess
import threading
import time
from typing import Dict, List, Optional

import requests

logger = logging.getLogger(__name__)


class CollectorMonitor:
    """Monitor OTel Collector performance during load tests."""

    def __init__(
        self,
        prometheus_endpoint: str = "http://localhost:8889/metrics",
        container_name: str = "mca-otel-collector",
        poll_interval: float = 2.0,
    ):
        """Initialize collector monitor.

        Args:
            prometheus_endpoint: Prometheus metrics endpoint
            container_name: Docker container name for stats
            poll_interval: Interval for continuous monitoring (seconds)
        """
        self.prometheus_endpoint = prometheus_endpoint
        self.container_name = container_name
        self.poll_interval = poll_interval
        self.start_metrics: Optional[Dict] = None
        self.end_metrics: Optional[Dict] = None
        self.start_time: Optional[float] = None
        self.end_time: Optional[float] = None

        # Continuous monitoring
        self._monitoring = False
        self._monitor_thread: Optional[threading.Thread] = None
        self._docker_samples: List[Dict] = []

    def _monitoring_loop(self):
        """Background thread for continuous Docker stats monitoring."""
        while self._monitoring:
            stats = self.get_docker_stats()
            if stats:
                self._docker_samples.append(stats)
            time.sleep(self.poll_interval)

    def __enter__(self):
        """Start monitoring (context manager entry)."""
        self.start_time = time.time()
        self.start_metrics = self.scrape_metrics()
        self._docker_samples = []

        # Start continuous monitoring thread
        self._monitoring = True
        self._monitor_thread = threading.Thread(target=self._monitoring_loop, daemon=True)
        self._monitor_thread.start()

        logger.info("Started collector monitoring with continuous Docker stats polling")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Stop monitoring and calculate deltas (context manager exit)."""
        # Stop monitoring thread
        self._monitoring = False
        if self._monitor_thread:
            self._monitor_thread.join(timeout=5.0)

        self.end_time = time.time()
        self.end_metrics = self.scrape_metrics()
        logger.info(
            f"Stopped collector monitoring ({len(self._docker_samples)} Docker samples collected)"
        )

    def scrape_metrics(self) -> Dict:
        """Scrape Prometheus metrics from collector.

        Returns:
            Dict with parsed metrics
        """
        try:
            response = requests.get(self.prometheus_endpoint, timeout=5)
            response.raise_for_status()
            text = response.text

            metrics = {
                "receiver_accepted_metric_points": self._parse_prometheus_value(
                    text, r"otelcol_receiver_accepted_metric_points\{[^}]*\}\s+([\d.]+)"
                ),
                "exporter_sent_metric_points": self._parse_prometheus_value(
                    text, r"otelcol_exporter_sent_metric_points\{[^}]*\}\s+([\d.]+)"
                ),
                "batch_send_size_count": self._parse_prometheus_value(
                    text,
                    r"otelcol_processor_batch_batch_send_size_count\{[^}]*\}\s+([\d.]+)",
                ),
                "batch_send_size_sum": self._parse_prometheus_value(
                    text,
                    r"otelcol_processor_batch_batch_send_size_sum\{[^}]*\}\s+([\d.]+)",
                ),
                "process_resident_memory_bytes": self._parse_prometheus_value(
                    text, r"process_resident_memory_bytes\s+([\d.]+)"
                ),
                "timestamp": time.time(),
            }

            return metrics

        except requests.RequestException as e:
            logger.warning(f"Failed to scrape Prometheus metrics: {e}")
            return {}

    def _parse_prometheus_value(self, text: str, pattern: str) -> float:
        """Parse a numeric value from Prometheus text format.

        Args:
            text: Prometheus metrics text
            pattern: Regex pattern to match metric

        Returns:
            Parsed value or 0.0 if not found
        """
        match = re.search(pattern, text)
        if match:
            return float(match.group(1))
        return 0.0

    def get_docker_stats(self) -> Dict:
        """Get Docker container stats.

        Returns:
            Dict with CPU and memory usage
        """
        try:
            result = subprocess.run(
                [
                    "docker",
                    "stats",
                    self.container_name,
                    "--no-stream",
                    "--format",
                    "json",
                ],
                capture_output=True,
                text=True,
                timeout=10,
            )

            if result.returncode != 0:
                logger.warning(f"Failed to get Docker stats: {result.stderr}")
                return {}

            stats = json.loads(result.stdout)

            # Parse CPU percentage (format: "1.23%")
            cpu_percent = 0.0
            if "CPUPerc" in stats:
                cpu_str = stats["CPUPerc"].strip("%")
                cpu_percent = float(cpu_str) if cpu_str else 0.0

            # Parse memory usage (format: "123.4MiB / 512MiB")
            mem_usage_bytes = 0
            if "MemUsage" in stats:
                mem_str = stats["MemUsage"].split("/")[0].strip()
                mem_usage_bytes = self._parse_memory_string(mem_str)

            return {
                "cpu_percent": cpu_percent,
                "memory_bytes": mem_usage_bytes,
                "timestamp": time.time(),
            }

        except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception) as e:
            logger.warning(f"Failed to get Docker stats: {e}")
            return {}

    def _parse_memory_string(self, mem_str: str) -> int:
        """Parse memory string to bytes.

        Args:
            mem_str: Memory string like "123.4MiB" or "1.5GiB"

        Returns:
            Memory in bytes
        """
        mem_str = mem_str.strip()
        if mem_str.endswith("GiB"):
            return int(float(mem_str[:-3]) * 1024 * 1024 * 1024)
        elif mem_str.endswith("MiB"):
            return int(float(mem_str[:-3]) * 1024 * 1024)
        elif mem_str.endswith("KiB"):
            return int(float(mem_str[:-3]) * 1024)
        else:
            return int(float(mem_str))

    def _analyze_docker_samples(self) -> Dict:
        """Analyze continuous Docker samples collected during test.

        Returns:
            Dict with aggregated CPU and memory statistics
        """
        if not self._docker_samples:
            return {
                "cpu_avg_percent": 0.0,
                "cpu_peak_percent": 0.0,
                "memory_avg_bytes": 0,
                "memory_peak_bytes": 0,
                "samples": 0,
            }

        import statistics

        cpu_values = [s["cpu_percent"] for s in self._docker_samples if "cpu_percent" in s]
        mem_values = [s["memory_bytes"] for s in self._docker_samples if "memory_bytes" in s]

        return {
            "cpu_avg_percent": statistics.mean(cpu_values) if cpu_values else 0.0,
            "cpu_peak_percent": max(cpu_values) if cpu_values else 0.0,
            "memory_avg_bytes": statistics.mean(mem_values) if mem_values else 0,
            "memory_peak_bytes": max(mem_values) if mem_values else 0,
            "samples": len(self._docker_samples),
        }

    def get_report(self) -> Dict:
        """Generate performance report from monitoring session.

        Returns:
            Dict with collector performance metrics and analysis
        """
        if not self.start_metrics or not self.end_metrics:
            logger.warning("Monitoring session incomplete")
            return {}

        duration = self.end_time - self.start_time

        # Calculate deltas
        receiver_delta = self.end_metrics.get(
            "receiver_accepted_metric_points", 0
        ) - self.start_metrics.get("receiver_accepted_metric_points", 0)
        exporter_delta = self.end_metrics.get(
            "exporter_sent_metric_points", 0
        ) - self.start_metrics.get("exporter_sent_metric_points", 0)
        batch_count_delta = self.end_metrics.get(
            "batch_send_size_count", 0
        ) - self.start_metrics.get("batch_send_size_count", 0)
        batch_sum_delta = self.end_metrics.get("batch_send_size_sum", 0) - self.start_metrics.get(
            "batch_send_size_sum", 0
        )

        # Memory analysis
        start_memory = self.start_metrics.get("process_resident_memory_bytes", 0)
        end_memory = self.end_metrics.get("process_resident_memory_bytes", 0)
        memory_growth_bytes = end_memory - start_memory
        memory_growth_percent = (
            (memory_growth_bytes / start_memory * 100) if start_memory > 0 else 0
        )

        # Batch efficiency
        avg_batch_size = (batch_sum_delta / batch_count_delta) if batch_count_delta > 0 else 0

        # Analyze continuous Docker stats
        docker_stats = self._analyze_docker_samples()

        report = {
            "duration_seconds": duration,
            "receiver": {
                "accepted_metric_points": receiver_delta,
                "rate_per_second": receiver_delta / duration if duration > 0 else 0,
            },
            "exporter": {
                "sent_metric_points": exporter_delta,
                "rate_per_second": exporter_delta / duration if duration > 0 else 0,
            },
            "batch_processor": {
                "batches_sent": batch_count_delta,
                "total_items": batch_sum_delta,
                "avg_batch_size": avg_batch_size,
            },
            "memory": {
                "start_bytes": start_memory,
                "end_bytes": end_memory,
                "growth_bytes": memory_growth_bytes,
                "growth_percent": memory_growth_percent,
                "stable": memory_growth_percent < 10,  # <10% growth is stable
            },
            "docker": docker_stats,
            "acceptance_criteria": {
                "memory_stable": memory_growth_percent < 10,
                "batch_efficiency": avg_batch_size > 100,  # Batches should be reasonably sized
            },
        }

        return report

    def print_report(self, report: Dict) -> None:
        """Print collector performance report.

        Args:
            report: Report dict from get_report()
        """
        logger.info("\n" + "=" * 60)
        logger.info("COLLECTOR PERFORMANCE REPORT")
        logger.info("=" * 60)
        logger.info(f"Duration: {report['duration_seconds']:.1f}s")
        logger.info("")
        logger.info("Receiver:")
        logger.info(
            f"  Accepted: {report['receiver']['accepted_metric_points']:,.0f} metric points"
        )
        logger.info(f"  Rate: {report['receiver']['rate_per_second']:.1f} points/sec")
        logger.info("")
        logger.info("Exporter:")
        logger.info(f"  Sent: {report['exporter']['sent_metric_points']:,.0f} metric points")
        logger.info(f"  Rate: {report['exporter']['rate_per_second']:.1f} points/sec")
        logger.info("")
        logger.info("Batch Processor:")
        logger.info(f"  Batches: {report['batch_processor']['batches_sent']:,.0f}")
        logger.info(f"  Avg batch size: {report['batch_processor']['avg_batch_size']:.1f}")
        logger.info("")
        logger.info("Memory:")
        logger.info(f"  Start: {self._format_bytes(report['memory']['start_bytes'])}")
        logger.info(f"  End: {self._format_bytes(report['memory']['end_bytes'])}")
        logger.info(
            f"  Growth: {self._format_bytes(report['memory']['growth_bytes'])} ({report['memory']['growth_percent']:+.1f}%)"
        )
        logger.info(f"  Stable: {'YES' if report['memory']['stable'] else 'NO'}")
        logger.info("")
        if report.get("docker"):
            logger.info("Docker Stats (continuous monitoring):")
            logger.info(f"  CPU avg: {report['docker']['cpu_avg_percent']:.2f}%")
            logger.info(f"  CPU peak: {report['docker']['cpu_peak_percent']:.2f}%")
            logger.info(f"  Memory avg: {self._format_bytes(report['docker']['memory_avg_bytes'])}")
            logger.info(
                f"  Memory peak: {self._format_bytes(report['docker']['memory_peak_bytes'])}"
            )
            logger.info(f"  Samples: {report['docker']['samples']}")
        logger.info("")
        logger.info("Acceptance Criteria:")
        logger.info(
            f"  Memory stable: {'PASS' if report['acceptance_criteria']['memory_stable'] else 'FAIL'}"
        )
        logger.info(
            f"  Batch efficiency: {'PASS' if report['acceptance_criteria']['batch_efficiency'] else 'FAIL'}"
        )
        logger.info("=" * 60)

    def _format_bytes(self, bytes_value: float) -> str:
        """Format bytes as human-readable size."""
        if bytes_value < 1024:
            return f"{bytes_value:.0f} B"
        elif bytes_value < 1024 * 1024:
            return f"{bytes_value / 1024:.1f} KB"
        elif bytes_value < 1024 * 1024 * 1024:
            return f"{bytes_value / 1024 / 1024:.1f} MB"
        else:
            return f"{bytes_value / 1024 / 1024 / 1024:.2f} GB"


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

    monitor = CollectorMonitor()

    logger.info("Testing collector monitoring...")
    with monitor:
        # Simulate work
        time.sleep(5)

    report = monitor.get_report()
    monitor.print_report(report)
