"""Load test report generation with visualizations for Story 6-8.

This module generates comprehensive reports from load test results including:
- Latency distribution histograms
- Throughput timeline graphs
- Memory usage trends
- Summary markdown reports
"""

import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, List

import matplotlib

matplotlib.use("Agg")  # Use non-interactive backend
import matplotlib.pyplot as plt

logger = logging.getLogger(__name__)


class LoadTestReporter:
    """Generate load test reports with visualizations."""

    def __init__(self, output_dir: str = None):
        """Initialize reporter.

        Args:
            output_dir: Directory for report outputs (default: tests/performance/results)
        """
        if output_dir is None:
            base_dir = Path(__file__).parent / "results"
        else:
            base_dir = Path(output_dir)

        self.output_dir = base_dir
        self.output_dir.mkdir(parents=True, exist_ok=True)

        self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        logger.info(f"Report output directory: {self.output_dir}")

    def generate_latency_histogram(
        self,
        results: List[Dict],
        output_filename: str = None,
    ) -> Path:
        """Generate latency distribution histogram.

        Args:
            results: List of scenario result dicts
            output_filename: Output filename (default: latency_distribution_{timestamp}.png)

        Returns:
            Path to generated image
        """
        if output_filename is None:
            output_filename = f"latency_distribution_{self.timestamp}.png"

        output_path = self.output_dir / output_filename

        fig, axes = plt.subplots(len(results), 1, figsize=(12, 4 * len(results)))

        if len(results) == 1:
            axes = [axes]

        for idx, result in enumerate(results):
            ax = axes[idx]
            latencies = result["latency"]["all_latencies"]

            if not latencies:
                continue

            # Histogram with percentile lines
            ax.hist(latencies, bins=50, alpha=0.7, color="blue", edgecolor="black")

            # Add percentile lines
            p50 = result["latency"]["p50_ms"]
            p95 = result["latency"]["p95_ms"]
            p99 = result["latency"]["p99_ms"]

            ax.axvline(
                p50,
                color="green",
                linestyle="--",
                linewidth=2,
                label=f"p50: {p50:.2f}ms",
            )
            ax.axvline(
                p95,
                color="orange",
                linestyle="--",
                linewidth=2,
                label=f"p95: {p95:.2f}ms",
            )
            ax.axvline(p99, color="red", linestyle="--", linewidth=2, label=f"p99: {p99:.2f}ms")

            # Add 100ms threshold line (AC2)
            ax.axvline(100, color="red", linestyle=":", linewidth=2, label="100ms threshold")

            ax.set_xlabel("Latency (ms)")
            ax.set_ylabel("Frequency")
            ax.set_title(f"Latency Distribution - {result['scenario']}")
            ax.legend()
            ax.grid(True, alpha=0.3)

        plt.tight_layout()
        plt.savefig(output_path, dpi=150)
        plt.close()

        logger.info(f"Generated latency histogram: {output_path}")
        return output_path

    def generate_throughput_timeline(
        self,
        results: List[Dict],
        output_filename: str = None,
    ) -> Path:
        """Generate throughput over time graph.

        Args:
            results: List of scenario result dicts
            output_filename: Output filename (default: throughput_timeline_{timestamp}.png)

        Returns:
            Path to generated image
        """
        if output_filename is None:
            output_filename = f"throughput_timeline_{self.timestamp}.png"

        output_path = self.output_dir / output_filename

        fig, ax = plt.subplots(figsize=(14, 6))

        for result in results:
            # Use ACTUAL request timestamps to calculate real throughput over time
            request_timestamps = result.get("request_timestamps", [])
            duration = result["duration_seconds"]

            if not request_timestamps or duration == 0:
                continue

            # Determine start time and bin requests into 1-second windows
            start_time = min(request_timestamps)
            end_time = max(request_timestamps)
            test_duration = end_time - start_time

            # Create 1-second bins
            num_bins = int(test_duration) + 1
            bins = [0] * num_bins

            # Count requests in each 1-second bin
            for timestamp in request_timestamps:
                elapsed = timestamp - start_time
                bin_index = int(elapsed)
                if 0 <= bin_index < num_bins:
                    bins[bin_index] += 1

            # Time points for x-axis
            time_points = list(range(num_bins))

            # Plot actual measured RPS
            ax.plot(time_points, bins, label=result["scenario"], linewidth=2)

        # Add 1000 RPS target line
        ax.axhline(1000, color="green", linestyle="--", linewidth=2, label="1000 RPS target")

        ax.set_xlabel("Time (seconds)")
        ax.set_ylabel("Requests per Second")
        ax.set_title("Throughput Timeline")
        ax.legend()
        ax.grid(True, alpha=0.3)

        plt.tight_layout()
        plt.savefig(output_path, dpi=150)
        plt.close()

        logger.info(f"Generated throughput timeline: {output_path}")
        return output_path

    def generate_memory_usage_graph(
        self,
        collector_reports: List[Dict],
        output_filename: str = None,
    ) -> Path:
        """Generate memory usage trend graph for all scenarios.

        Args:
            collector_reports: List of collector performance reports
            output_filename: Output filename (default: memory_usage_{timestamp}.png)

        Returns:
            Path to generated image
        """
        if output_filename is None:
            output_filename = f"memory_usage_{self.timestamp}.png"

        output_path = self.output_dir / output_filename

        fig, ax = plt.subplots(figsize=(12, 6))

        # Plot memory for each scenario
        categories = []
        start_values = []
        end_values = []

        for idx, report in enumerate(collector_reports):
            if not report or "memory" not in report:
                continue

            scenario_label = f"Scenario {idx + 1}"
            categories.append(f"{scenario_label}\nStart")
            categories.append(f"{scenario_label}\nEnd")

            start_mb = report["memory"]["start_bytes"] / 1024 / 1024
            end_mb = report["memory"]["end_bytes"] / 1024 / 1024

            start_values.append(start_mb)
            end_values.append(end_mb)

        # Interleave start and end values
        all_values = []
        all_colors = []
        for idx, report in enumerate(collector_reports):
            if not report or "memory" not in report:
                continue
            all_values.append(start_values[idx])
            all_values.append(end_values[idx])
            all_colors.append("blue")
            stable = report["memory"]["stable"]
            all_colors.append("green" if stable else "red")

        bars = ax.bar(categories, all_values, color=all_colors, alpha=0.7, edgecolor="black")

        # Add value labels on bars
        for bar in bars:
            height = bar.get_height()
            ax.text(
                bar.get_x() + bar.get_width() / 2.0,
                height,
                f"{height:.0f}",
                ha="center",
                va="bottom",
                fontsize=9,
            )

        ax.set_ylabel("Memory Usage (MB)")
        ax.set_title("Collector Memory Usage Across All Load Test Scenarios")
        ax.grid(True, axis="y", alpha=0.3)
        plt.xticks(rotation=45, ha="right")

        plt.tight_layout()
        plt.savefig(output_path, dpi=150)
        plt.close()

        logger.info(f"Generated memory usage graph: {output_path}")
        return output_path

    def generate_markdown_report(
        self,
        results: List[Dict],
        collector_reports: List[Dict] = None,
        completeness_results: Dict = None,
        output_filename: str = None,
    ) -> Path:
        """Generate markdown summary report.

        Args:
            results: List of scenario result dicts
            collector_reports: List of collector performance reports (one per scenario)
            completeness_results: Telemetry completeness validation results
            output_filename: Output filename (default: load_test_report_{timestamp}.md)

        Returns:
            Path to generated report
        """
        if output_filename is None:
            output_filename = f"load_test_report_{self.timestamp}.md"

        output_path = self.output_dir / output_filename

        with open(output_path, "w") as f:
            # Header
            f.write("# Load Test Report - Story 6-8\n\n")
            f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
            f.write("---\n\n")

            # Executive Summary
            f.write("## Executive Summary\n\n")

            all_passed = all(all(r["acceptance_criteria"].values()) for r in results)

            f.write(f"**Overall Result:** {'✅ PASS' if all_passed else '❌ FAIL'}\n\n")

            # Scenario Results Table
            f.write("## Scenario Results\n\n")
            f.write("| Scenario | RPS | Duration | Requests | p95 Latency | Drops | Status |\n")
            f.write("|----------|-----|----------|----------|-------------|-------|--------|\n")

            for result in results:
                status = "✅ PASS" if all(result["acceptance_criteria"].values()) else "❌ FAIL"
                f.write(
                    f"| {result['scenario']} "
                    f"| {result['actual_rps']:.1f} "
                    f"| {result['duration_seconds']:.1f}s "
                    f"| {result['total_requests']:,} "
                    f"| {result['latency']['p95_ms']:.2f}ms "
                    f"| {result['dropped_count']} "
                    f"| {status} |\n"
                )

            f.write("\n")

            # Detailed Scenario Reports
            f.write("## Detailed Scenario Reports\n\n")

            for result in results:
                f.write(f"### {result['scenario']}\n\n")

                # Latency stats
                f.write("**Latency Statistics:**\n")
                f.write(f"- Average: {result['latency']['avg_ms']:.3f}ms\n")
                f.write(f"- p50: {result['latency']['p50_ms']:.3f}ms\n")
                f.write(f"- p95: {result['latency']['p95_ms']:.3f}ms\n")
                f.write(f"- p99: {result['latency']['p99_ms']:.3f}ms\n")
                f.write(f"- Min: {result['latency']['min_ms']:.3f}ms\n")
                f.write(f"- Max: {result['latency']['max_ms']:.3f}ms\n\n")

                # CPU stats
                f.write("**CPU Usage:**\n")
                f.write(f"- Average: {result['cpu']['average_percent']:.2f}%\n")
                f.write(f"- Max: {result['cpu']['max_percent']:.2f}%\n")
                f.write(
                    f"- Stable: {'✅ Yes' if result['cpu']['stable'] else '❌ No'} ({result['cpu']['increase_percent']:+.1f}%)\n\n"
                )

                # Acceptance criteria
                f.write("**Acceptance Criteria:**\n")
                for criterion, passed in result["acceptance_criteria"].items():
                    status = "✅ PASS" if passed else "❌ FAIL"
                    f.write(f"- {criterion}: {status}\n")
                f.write("\n")

            # Collector Performance (all scenarios)
            if collector_reports:
                f.write("## Collector Performance (AC3)\n\n")

                for idx, collector_report in enumerate(collector_reports):
                    if not collector_report:
                        continue

                    f.write(f"### Scenario {idx + 1} - {results[idx]['scenario']}\n\n")

                    f.write("**Receiver:**\n")
                    f.write(
                        f"- Accepted: {collector_report['receiver']['accepted_metric_points']:,.0f} metric points\n"
                    )
                    f.write(
                        f"- Rate: {collector_report['receiver']['rate_per_second']:.1f} points/sec\n\n"
                    )

                    f.write("**Exporter:**\n")
                    f.write(
                        f"- Sent: {collector_report['exporter']['sent_metric_points']:,.0f} metric points\n"
                    )
                    f.write(
                        f"- Rate: {collector_report['exporter']['rate_per_second']:.1f} points/sec\n\n"
                    )

                    f.write("**Batch Processor:**\n")
                    f.write(
                        f"- Batches sent: {collector_report['batch_processor']['batches_sent']:,.0f}\n"
                    )
                    f.write(
                        f"- Average batch size: {collector_report['batch_processor']['avg_batch_size']:.1f}\n\n"
                    )

                    f.write("**Memory:**\n")
                    f.write(
                        f"- Start: {self._format_bytes(collector_report['memory']['start_bytes'])}\n"
                    )
                    f.write(
                        f"- End: {self._format_bytes(collector_report['memory']['end_bytes'])}\n"
                    )
                    f.write(f"- Growth: {collector_report['memory']['growth_percent']:+.1f}%\n")
                    f.write(
                        f"- Stable: {'✅ Yes' if collector_report['memory']['stable'] else '❌ No'}\n\n"
                    )

            # Telemetry Completeness
            if completeness_results:
                f.write("## Telemetry Completeness (AC4)\n\n")

                f.write("| Signal | Sent | Received | Completeness | Status |\n")
                f.write("|--------|------|----------|--------------|--------|\n")

                for signal_name, result in completeness_results.items():
                    status = "✅ PASS" if result["passed"] else "❌ FAIL"
                    f.write(
                        f"| {signal_name} "
                        f"| {result['sdk_sent']:,} "
                        f"| {result['collector_received']:,} "
                        f"| {result['completeness_percent']:.2f}% "
                        f"| {status} |\n"
                    )

                f.write("\n")

            # Acceptance Criteria Summary
            f.write("## Acceptance Criteria Summary\n\n")
            f.write("| Criterion | Status |\n")
            f.write("|-----------|--------|\n")

            # AC1: 1000 RPS sustained
            ac1_pass = any(r["actual_rps"] >= 950 and r["duration_seconds"] >= 60 for r in results)
            f.write(f"| AC1: 1000 RPS for 60s | {'✅ PASS' if ac1_pass else '❌ FAIL'} |\n")

            # AC2: p95 latency <100ms
            ac2_pass = all(r["latency"]["p95_ms"] < 100 for r in results)
            f.write(f"| AC2: p95 latency <100ms | {'✅ PASS' if ac2_pass else '❌ FAIL'} |\n")

            # AC3: Collector efficiency (all scenarios must pass)
            ac3_pass = (
                all(r["acceptance_criteria"]["memory_stable"] for r in collector_reports if r)
                if collector_reports
                else False
            )
            f.write(f"| AC3: Collector efficiency | {'✅ PASS' if ac3_pass else '❌ FAIL'} |\n")

            # AC4: Telemetry completeness >=99%
            ac4_pass = (
                all(r["passed"] for r in completeness_results.values())
                if completeness_results
                else False
            )
            f.write(
                f"| AC4: Telemetry completeness >=99% | {'✅ PASS' if ac4_pass else '❌ FAIL'} |\n"
            )

            f.write("\n---\n\n")
            f.write("*Report generated by MCA SDK Load Test Suite*\n")

        logger.info(f"Generated markdown report: {output_path}")
        return output_path

    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"

    def generate_full_report(
        self,
        results: List[Dict],
        collector_reports: List[Dict] = None,
        completeness_results: Dict = None,
    ) -> Dict[str, Path]:
        """Generate complete report with all visualizations.

        Args:
            results: List of scenario result dicts
            collector_reports: List of collector performance reports (one per scenario)
            completeness_results: Telemetry completeness validation results

        Returns:
            Dict mapping output type to file path
        """
        output_files = {}

        # Generate graphs
        output_files["latency_histogram"] = self.generate_latency_histogram(results)
        output_files["throughput_timeline"] = self.generate_throughput_timeline(results)

        if collector_reports:
            output_files["memory_usage"] = self.generate_memory_usage_graph(collector_reports)

        # Generate markdown report
        output_files["markdown_report"] = self.generate_markdown_report(
            results,
            collector_reports,
            completeness_results,
        )

        logger.info(f"\n{'=' * 60}")
        logger.info("LOAD TEST REPORT GENERATED")
        logger.info(f"{'=' * 60}")
        for output_type, path in output_files.items():
            logger.info(f"  {output_type}: {path.name}")
        logger.info(f"{'=' * 60}\n")

        return output_files


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

    # Mock data for testing
    mock_results = [
        {
            "scenario": "test_scenario",
            "duration_seconds": 60.0,
            "total_requests": 60000,
            "actual_rps": 1000.0,
            "dropped_count": 0,
            "latency": {
                "p50_ms": 25.0,
                "p95_ms": 45.0,
                "p99_ms": 65.0,
                "avg_ms": 30.0,
                "min_ms": 10.0,
                "max_ms": 100.0,
                "all_latencies": [25.0] * 1000,
            },
            "cpu": {
                "average_percent": 15.0,
                "max_percent": 20.0,
                "stable": True,
                "increase_percent": 2.0,
            },
            "acceptance_criteria": {
                "p95_under_100ms": True,
                "no_drops": True,
                "cpu_stable": True,
            },
        }
    ]

    reporter = LoadTestReporter()
    output_files = reporter.generate_full_report(mock_results)

    logger.info("Report generation test complete")
