"""
Validation test: GATA1/PPIF regions — new pipeline vs. old CRISPRDesigner output.

Compares CRISPRDesigner2 output for the 210701_GATA1PPIFpegRNAs dataset against the
original CRISPRDesigner run (hg19, lifted to hg38 for guide-sequence matching).

Reference data
--------------
tests/data/GATA1PPIF_reference_specificity.tsv
    MIT specificity scores (0–100) from the old pipeline, keyed by protospacer sequence.
    Source: .../210701_GATA1PPIFpegRNAs/02_RunCRISPRDesigner/.../designGuides.txt

Pipeline output required
------------------------
results/GuideDesign/designGuides.txt  (generated by running snakemake with
    config/config.yaml pointing to resources/Variants.50bp.hg38.bed)

Tests are marked @pytest.mark.integration and are skipped automatically when
the pipeline output file is absent or when specificity scores are all NaN
(e.g. GuideScan2 index not available).

Expected comparison
-------------------
- GuideScan2 specificity scores (0–1) vs. MIT specificity scores (0–100) should be
  positively rank-correlated (Spearman r > 0.3) because both measure off-target
  uniqueness, despite using different algorithms.
- All new-pipeline guides should appear in the old pipeline output (the new pipeline
  is more conservative in filtering, so it is a subset of the old output).
"""

import os
import pytest
import pandas as pd
import numpy as np

DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
REPO_ROOT = os.path.join(os.path.dirname(__file__), '..')

REFERENCE_FILE = os.path.join(DATA_DIR, 'GATA1PPIF_reference_specificity.tsv')
PIPELINE_OUTPUT = os.path.join(REPO_ROOT, 'results', 'GuideDesign', 'designGuides.txt')


def _load_pipeline_output():
    return pd.read_csv(PIPELINE_OUTPUT, sep='\t')


def _load_reference():
    return pd.read_csv(REFERENCE_FILE, sep='\t')


def _pipeline_output_exists():
    return os.path.exists(PIPELINE_OUTPUT)


def _has_specificity_scores(df):
    return df['specificity_score'].notna().any()


@pytest.mark.integration
class TestGATA1PPIFValidation:
    """Validation tests comparing new pipeline output to old CRISPRDesigner results."""

    def test_pipeline_output_exists(self):
        """Pipeline output file must exist before other validation tests can run."""
        assert os.path.exists(PIPELINE_OUTPUT), (
            f"Pipeline output not found: {PIPELINE_OUTPUT}\n"
            "Run: conda run -n CRISPRDesigner2 snakemake --configfile config/config.yaml "
            "--cores 4"
        )

    def test_guide_counts_by_region(self):
        """Guide counts per region should be positive and within expected bounds."""
        if not _pipeline_output_exists():
            pytest.skip("Pipeline output not found")

        new = _load_pipeline_output()

        expected_regions = {
            'PPIF_CCAAT_box', 'PPIF_ATG_SNV', 'PPIF_ATG_MNV', 'PPIF_SIL_AA2',
            'NEG_5PRIM_FIRST_INTRON', '5PRIME_FIRST_INTRON',
            '3PRIME_FIRST_INTRON', 'NEG_3PRIM_FIRST_INTRON', 'eHDAC6_GATA_SITE'
        }
        present_regions = set(new['guideSet'].unique())
        assert present_regions == expected_regions, (
            f"Missing regions: {expected_regions - present_regions}\n"
            f"Extra regions: {present_regions - expected_regions}"
        )

        counts = new.groupby('guideSet').size()
        for region, count in counts.items():
            assert count > 0, f"Region {region} has no guides"
            assert count <= 60, f"Region {region} has unexpectedly many guides: {count}"

    def test_guide_sequence_overlap_with_old_pipeline(self):
        """At least 70% of new-pipeline protospacers should appear in old pipeline output."""
        if not _pipeline_output_exists():
            pytest.skip("Pipeline output not found")

        new = _load_pipeline_output()
        ref = _load_reference()

        new_seqs = set(new['GuideSequenceWithPAM'].str[:20])
        ref_seqs = set(ref['protospacer'])

        n_shared = len(new_seqs & ref_seqs)
        n_new = len(new_seqs)
        overlap_frac = n_shared / n_new if n_new > 0 else 0

        assert overlap_frac >= 0.70, (
            f"Only {n_shared}/{n_new} ({overlap_frac:.1%}) new-pipeline guides appear "
            f"in the old pipeline output. Expected ≥ 70%."
        )

    def test_specificity_score_correlation(self):
        """GuideScan2 specificity scores should be positively correlated with MIT scores.

        GuideScan2 (0–1) and MIT Feng Zhang scoring (0–100) both measure off-target
        uniqueness. Scores will differ numerically but should agree in rank order
        (Spearman r > 0.3).

        Skipped if specificity scores are all NaN (GuideScan2 index not available).
        """
        if not _pipeline_output_exists():
            pytest.skip("Pipeline output not found")

        from scipy.stats import spearmanr

        new = _load_pipeline_output()

        if not _has_specificity_scores(new):
            pytest.skip(
                "All specificity_score values are NaN — GuideScan2 index not available. "
                "Create the BAM index with: module load samtools/1.16.1 && "
                "samtools index resources/hg38_no_alt.bam.sorted"
            )

        ref = _load_reference()

        # Match by protospacer sequence
        new['protospacer'] = new['GuideSequenceWithPAM'].str[:20]
        merged = new[['protospacer', 'specificity_score']].merge(
            ref[['protospacer', 'specificity_score_MIT']], on='protospacer', how='inner'
        ).dropna()

        assert len(merged) >= 10, (
            f"Too few guides with both scores for comparison: {len(merged)}"
        )

        r, p = spearmanr(merged['specificity_score'], merged['specificity_score_MIT'])

        assert r > 0.3, (
            f"Spearman rank correlation between GuideScan2 and MIT specificity scores "
            f"is too low: r = {r:.3f} (p = {p:.2e}) on {len(merged)} shared guides. "
            f"Expected r > 0.3."
        )
