import numpy as np
import pytest
from ctapipe.core import run_tool
from qualpipe.tools import MetricEvaluator


@pytest.fixture(scope="module")
def comprehensive_metrics_config():
    """
    Load the comprehensive quality metrics configuration from examples.

    This configuration includes:
    - Metric 1: Subarray trigger rate (time-based, 1 criterion)
    - Metric 2: Telescope trigger pixels (event-based, 2 criteria)
    - Metric 3: Muon ring radius (event-based, 2 criteria)
    - Metric 4: Muon optical efficiency (event-based, 2 criteria)
    """
    from pathlib import Path

    config_path = (
        Path(__file__).parent.parent.parent.parent.parent
        / "docs"
        / "static"
        / "examples"
        / "comprehensive_quality_metrics.yaml"
    )
    assert config_path.exists(), f"Config file not found: {config_path}"
    return config_path


def test_comprehensive_quality_metrics(
    comprehensive_metrics_config, muon_test_dl1_file, tmp_path
):
    """
    Test MetricEvaluator with comprehensive quality metrics on muon test data.

    This test validates:
    1. Time-based chunking (trigger rate)
    2. Event-based chunking (muon parameters)
    3. Subarray-level metrics (trigger rate)
    4. Telescope-specific metrics (muon parameters)
    5. Multiple criteria per metric (1, 2, 2, and 2 criteria)
    6. Different pass rates (some criteria pass, some fail)
    7. HDF5 monitoring table writing
    8. No duplicate criterion types per metric (validated at config load time)

    The test uses real muon ring data with ~10k events.
    """
    import shutil

    from ctapipe.io import read_table

    # Copy the cached file to tmp directory so we don't modify the original
    test_file = tmp_path / "muon_test.h5"
    shutil.copy(muon_test_dl1_file, test_file)

    # Run the MetricEvaluator tool
    run_tool(
        MetricEvaluator(),
        argv=[
            f"--config={comprehensive_metrics_config}",
            f"--input-url={test_file}",
        ],
    )

    # Check that the file still exists
    assert test_file.exists()

    # =========================================================================
    # Metric 1: Subarray trigger rate (1 criterion)
    # =========================================================================
    rate_table = read_table(
        test_file,
        "/dl1/monitoring/subarray/quality/SUBARRAY/subarray_trigger_rate",
    )

    # Check columns
    assert "rate" in rate_table.colnames
    assert "RangeCriterion_pass" in rate_table.colnames
    assert "overall_pass" in rate_table.colnames
    assert "pass_rate" in rate_table.colnames

    # Should have multiple 1-second time windows (~792 seconds of data)
    assert len(rate_table) > 700

    # All rates should be within expected range (10-20 Hz based on ~12.6 Hz average)
    assert all(8 <= row["rate"] <= 18 for row in rate_table)

    # Check that pass_rate is 1.0 for windows that passed (1 criterion)
    for row in rate_table:
        if row["overall_pass"]:
            assert row["pass_rate"] == pytest.approx(1.0)

    # =========================================================================
    # Metric 2: Muon completeness (2 criteria, ~50% pass rate expected)
    # =========================================================================
    completeness_table = read_table(
        test_file,
        "/dl1/monitoring/telescope/quality/muon_completeness_check/tel_001",
    )

    # Check columns for 2 criteria
    assert "mean" in completeness_table.colnames
    assert "TelescopeRangeCriterion_pass" in completeness_table.colnames
    assert "TelescopeThresholdCriterion_pass" in completeness_table.colnames
    assert "overall_pass" in completeness_table.colnames
    assert "pass_rate" in completeness_table.colnames

    # Should have multiple chunks of 500 events (a bit less than 20 chunks for 10k events due to some NaNs)
    assert len(completeness_table) > 15

    # Verify mean completeness values are valid (not all NaN)
    mean_values = completeness_table["mean"]
    n_valid = np.sum(~np.isnan(mean_values))
    assert n_valid > 0, "All completeness mean values are NaN"
    assert (
        n_valid > len(completeness_table) * 0.8
    ), f"Too many NaN values in completeness: {n_valid}/{len(completeness_table)} valid"

    # Check that valid mean values are in reasonable range [0, 1] for completeness
    valid_means = mean_values[~np.isnan(mean_values)]
    assert np.all(
        (valid_means >= 0) & (valid_means <= 1)
    ), f"Completeness values out of range [0, 1]: {valid_means}"

    # Check pass rates - should be mix of 0.5 (one criterion passed) and possibly 0.0 or 1.0
    pass_rates = set(completeness_table["pass_rate"])
    assert 0.5 in pass_rates or 0.0 in pass_rates or 1.0 in pass_rates

    # =========================================================================
    # Metric 3: Muon ring radius (2 criteria)
    # =========================================================================
    radius_table = read_table(
        test_file,
        "/dl1/monitoring/telescope/quality/muon_ring_radius_quality/tel_001",
    )

    # Should have multiple chunks
    assert len(radius_table) > 1

    # Verify median radius values are valid (not all NaN)
    median_values = radius_table["median"]
    n_valid = np.sum(~np.isnan(median_values))
    assert n_valid > 0, "All radius median values are NaN"
    assert (
        n_valid > len(radius_table) * 0.8
    ), f"Too many NaN values in radius: {n_valid}/{len(radius_table)} valid"

    # Check that valid median values are in reasonable range for muon ring radius (deg)
    valid_medians = median_values[~np.isnan(median_values)]
    assert np.all(
        (valid_medians > 0.5) & (valid_medians < 2.0)
    ), f"Radius values out of reasonable range: {valid_medians}"

    # Check that we have some variety in pass rates
    # (with the threshold set at median, we should see mix of pass/fail)
    pass_rate_values = radius_table["pass_rate"]
    min_pass_rate = float(np.min(pass_rate_values))
    max_pass_rate = float(np.max(pass_rate_values))
    # If all values are the same (all 1.0 or all 0.0), min == max
    # We want some variation
    assert (
        max_pass_rate - min_pass_rate >= 0.1
    ), f"Pass rates too uniform: min={min_pass_rate}, max={max_pass_rate}"

    # =========================================================================
    # Metric 4: Muon optical efficiency (2 criteria)
    # =========================================================================
    efficiency_table = read_table(
        test_file,
        "/dl1/monitoring/telescope/quality/muon_optical_efficiency/tel_001",
    )

    # Should have multiple chunks
    assert len(efficiency_table) > 1

    # Check that we have varied pass rates (not all 100%)
    assert not all(row["pass_rate"] == pytest.approx(1.0) for row in efficiency_table)

    # Note: Optical efficiency may have some NaN values due to invalid muon fits
    # But we should still have mostly valid data
    mean_efficiency = efficiency_table["mean"]
    n_valid = np.sum(~np.isnan(mean_efficiency))
    # Allow up to 50% NaN for optical efficiency (it's more sensitive to fit quality)
    assert (
        n_valid > len(efficiency_table) * 0.5
    ), f"Too many NaN values in optical efficiency: {n_valid}/{len(efficiency_table)} valid"


@pytest.mark.verifies_usecase("UC-140-1.2.2")
def test_calibpipe_monitoring_metrics_end_to_end(calibpipe_statsagg_dl1_file, tmp_path):
    """
    End-to-end test of MetricEvaluator with calibpipe DL1_MONITORING data.

    This test validates the complete workflow:
    1. Read DL1_MONITORING data (pre-aggregated calibration statistics)
    2. Apply TrivialDescriptor (pass-through, no additional aggregation)
    3. Evaluate quality criteria on monitoring data
    4. Write new monitoring tables with quality assessment results

    Uses flatfield mean and pedestal std statistics from calibpipe test data.
    """
    import shutil

    from ctapipe.io import read_table

    # Copy test file to tmp directory
    test_file = tmp_path / "calibpipe_test.h5"
    shutil.copy(calibpipe_statsagg_dl1_file, test_file)

    # Create metrics configuration file for calibpipe monitoring data
    config_file = tmp_path / "calibpipe_metrics.yaml"

    config_content = f"""
MetricEvaluator:
  input_url: {test_file}
  data_category: DL1_MONITORING
  metrics:
    - Metric:
        name: flatfield_mean_quality
        input_source: camera/pixel_statistics
        input_source_subtype: flatfield_image
        telescope_specific: true
        array_element: LST
        descriptor_column: mean
        criteria:
          - TelescopeThresholdCriterion:
              threshold: 7000
              above: true

    - Metric:
        name: pedestal_std_quality
        input_source: camera/pixel_statistics
        input_source_subtype: sky_pedestal_image
        telescope_specific: true
        array_element: LST
        descriptor_column: std
        criteria:
          - TelescopeRangeCriterion:
              min_value: 100
              max_value: 150
"""

    config_file.write_text(config_content)

    # Run MetricEvaluator with monitoring metrics
    run_tool(
        MetricEvaluator(),
        argv=[
            f"--config={config_file}",
            f"--input-url={test_file}",
        ],
    )  # =========================================================================
    # Verify flatfield mean quality results
    # =========================================================================
    flatfield_results = read_table(
        test_file, "/dl1/monitoring/telescope/quality/flatfield_mean_quality/tel_001"
    )

    # Should have same number of rows as input (TrivialDescriptor pass-through)
    assert len(flatfield_results) == 10

    # Check required columns are present
    assert "mean" in flatfield_results.colnames

    # Should preserve original time windows and event info
    assert "time_start" in flatfield_results.colnames
    assert "time_end" in flatfield_results.colnames
    assert "event_id_start" in flatfield_results.colnames
    assert "event_id_end" in flatfield_results.colnames

    # Check that criteria were evaluated successfully
    # (TelescopeRangeCriterion evaluates each pixel individually)

    # =========================================================================
    # Verify pedestal std quality results
    # =========================================================================
    pedestal_results = read_table(
        test_file, "/dl1/monitoring/telescope/quality/pedestal_std_quality/tel_001"
    )

    # Should have same structure as flatfield results
    assert len(pedestal_results) == 10
    assert "std" in pedestal_results.colnames

    # Check that pass rate values are properly computed (0.0 to 1.0)
    pass_rate_values = pedestal_results["pass_rate"]
    assert all(0.0 <= rate <= 1.0 for rate in pass_rate_values)
