"""
End-to-end tests for complete HBAT workflow with new interaction types.

These tests verify the complete analysis pipeline from PDB input to final results,
ensuring all new interaction types (π-π, carbonyl-carbonyl, n→π*) work together
seamlessly with existing functionality.
"""

import pytest
import os
from hbat.core.analysis import MolecularInteractionAnalyzer
from hbat.constants.parameters import AnalysisParameters


@pytest.mark.e2e
class TestCompleteWorkflow:
    """Test complete HBAT workflow with all interaction types."""

    def test_full_analysis_pipeline_7nwd(self):
        """Test complete analysis pipeline with 7nwd.pdb (π-π interactions)."""
        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        # Step 1: Load and analyze structure
        success = analyzer.analyze_file("example_pdb_files/7nwd.pdb")
        assert success, "Failed to analyze 7nwd.pdb"

        # Step 2: Verify all interaction types are detected
        assert hasattr(analyzer, "hydrogen_bonds"), "Missing hydrogen bonds"
        assert hasattr(analyzer, "halogen_bonds"), "Missing halogen bonds"
        assert hasattr(analyzer, "pi_pi_interactions"), "Missing π-π interactions"
        assert hasattr(analyzer, "carbonyl_interactions"), (
            "Missing carbonyl interactions"
        )
        assert hasattr(analyzer, "n_pi_interactions"), "Missing n→π* interactions"

        # Step 3: Count interactions
        h_bonds = len(analyzer.hydrogen_bonds) if analyzer.hydrogen_bonds else 0
        x_bonds = len(analyzer.halogen_bonds) if analyzer.halogen_bonds else 0
        pi_pi = len(analyzer.pi_pi_interactions) if analyzer.pi_pi_interactions else 0
        carbonyl = (
            len(analyzer.carbonyl_interactions) if analyzer.carbonyl_interactions else 0
        )
        n_pi = len(analyzer.n_pi_interactions) if analyzer.n_pi_interactions else 0

        # Step 4: Verify reasonable numbers of interactions
        assert h_bonds > 0, "No hydrogen bonds detected"
        assert pi_pi > 0, (
            "No π-π interactions detected in 7nwd (expected for nucleic acid structure)"
        )

        total_interactions = h_bonds + x_bonds + pi_pi + carbonyl + n_pi
        assert total_interactions > 10, (
            f"Too few total interactions: {total_interactions}"
        )

        # Step 5: Generate summary
        summary = analyzer.get_summary()
        assert "hydrogen_bonds" in summary, "Missing hydrogen bonds in summary"
        assert "pi_pi_interactions" in summary, "Missing π-π interactions in summary"

        print("✓ 7nwd.pdb analysis complete:")
        print(
            f"  H-bonds: {h_bonds}, X-bonds: {x_bonds}, π-π: {pi_pi}, Carbonyl: {carbonyl}, n→π*: {n_pi}"
        )
        print(f"  Total: {total_interactions} interactions")

    def test_full_analysis_pipeline_6rsa(self):
        """Test complete analysis pipeline with 6rsa.pdb (carbonyl interactions)."""
        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        # Step 1: Load and analyze structure
        success = analyzer.analyze_file("example_pdb_files/6rsa.pdb")
        assert success, "Failed to analyze 6rsa.pdb"

        # Step 2: Count interactions
        h_bonds = len(analyzer.hydrogen_bonds) if analyzer.hydrogen_bonds else 0
        x_bonds = len(analyzer.halogen_bonds) if analyzer.halogen_bonds else 0
        pi_pi = len(analyzer.pi_pi_interactions) if analyzer.pi_pi_interactions else 0
        carbonyl = (
            len(analyzer.carbonyl_interactions) if analyzer.carbonyl_interactions else 0
        )
        n_pi = len(analyzer.n_pi_interactions) if analyzer.n_pi_interactions else 0

        # Step 3: Verify protein structure has appropriate interactions
        assert h_bonds > 10, f"Expected more hydrogen bonds in protein: {h_bonds}"
        assert carbonyl > 0, (
            "No carbonyl interactions detected in 6rsa (expected for protein)"
        )

        # Step 4: Verify carbonyl interactions have correct properties
        if carbonyl > 0:
            for interaction in analyzer.carbonyl_interactions[:5]:  # Check first 5
                assert 2.5 <= interaction.distance <= 4.0, (
                    f"Invalid carbonyl distance: {interaction.distance}"
                )
                assert 90.0 <= interaction.burgi_dunitz_angle <= 130.0, (
                    f"Invalid Bürgi-Dunitz angle: {interaction.burgi_dunitz_angle}"
                )
                assert hasattr(interaction, "interaction_classification")

        print("✓ 6rsa.pdb analysis complete:")
        print(
            f"  H-bonds: {h_bonds}, X-bonds: {x_bonds}, π-π: {pi_pi}, Carbonyl: {carbonyl}, n→π*: {n_pi}"
        )

    def test_full_analysis_pipeline_n_pi_structures(self):
        """Test complete analysis pipeline with structures having n→π* interactions."""
        params = AnalysisParameters()

        for pdb_file in ["example_pdb_files/1luc.pdb", "example_pdb_files/2cdg.pdb"]:
            analyzer = MolecularInteractionAnalyzer(parameters=params)

            # Step 1: Analyze structure
            success = analyzer.analyze_file(pdb_file)
            assert success, f"Failed to analyze {pdb_file}"

            # Step 2: Check n→π* interactions
            n_pi = len(analyzer.n_pi_interactions) if analyzer.n_pi_interactions else 0
            assert n_pi > 0, (
                f"No n→π* interactions detected in {pdb_file} (expected after fix)"
            )

            # Step 3: Verify n→π* interaction properties
            for interaction in analyzer.n_pi_interactions[:3]:  # Check first 3
                assert 2.5 <= interaction.distance <= 3.6, (
                    f"Invalid n→π* distance: {interaction.distance}"
                )
                assert 0.0 <= interaction.angle_to_plane <= 45.0, (
                    f"Invalid n→π* angle: {interaction.angle_to_plane}"
                )
                assert interaction.donor_element in ["O", "N", "S"], (
                    f"Invalid donor element: {interaction.donor_element}"
                )
                assert len(interaction.subtype) > 0, "Missing interaction subtype"

            print(f"✓ {pdb_file}: {n_pi} n→π* interactions detected")


@pytest.mark.e2e
class TestWorkflowRobustness:
    """Test workflow robustness and error handling."""

    def test_analysis_with_all_interaction_types_enabled(self):
        """Test that all interaction types can be analyzed together without conflicts."""
        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        # Use a structure that should have multiple interaction types
        success = analyzer.analyze_file("example_pdb_files/7nwd.pdb")
        assert success

        # Verify no interactions interfere with each other
        h_bonds = len(analyzer.hydrogen_bonds) if analyzer.hydrogen_bonds else 0
        pi_pi = len(analyzer.pi_pi_interactions) if analyzer.pi_pi_interactions else 0

        # Both traditional and new interactions should be detected
        assert h_bonds > 0, "Traditional H-bond detection broken"
        assert pi_pi > 0, "New π-π detection not working"

        # Summary should include all types
        summary = analyzer.get_summary()
        assert "total_interactions" in summary

        total_from_summary = summary["total_interactions"]
        manual_total = (
            len(analyzer.hydrogen_bonds or [])
            + len(analyzer.halogen_bonds or [])
            + len(analyzer.pi_pi_interactions or [])
            + len(analyzer.carbonyl_interactions or [])
            + len(analyzer.n_pi_interactions or [])
        )

        # Allow for small discrepancies due to potential other interaction types
        assert abs(total_from_summary - manual_total) <= 5, (
            f"Summary total ({total_from_summary}) differs significantly from manual count ({manual_total})"
        )

    def test_parameter_validation(self):
        """Test parameter validation for new interactions."""
        params = AnalysisParameters()

        # Verify new parameters exist and have reasonable values
        assert hasattr(params, "pi_pi_distance_cutoff")
        assert hasattr(params, "carbonyl_distance_cutoff")
        assert hasattr(params, "n_pi_distance_cutoff")

        assert 3.0 <= params.pi_pi_distance_cutoff <= 6.0
        assert 2.5 <= params.carbonyl_distance_cutoff <= 4.0
        assert 3.0 <= params.n_pi_distance_cutoff <= 4.0

        # Test parameter modification
        original_cutoff = params.n_pi_distance_cutoff
        params.n_pi_distance_cutoff = 5.0

        analyzer = MolecularInteractionAnalyzer(parameters=params)
        assert analyzer.parameters.n_pi_distance_cutoff == 5.0

        # Restore original
        params.n_pi_distance_cutoff = original_cutoff

    def test_empty_structure_handling(self):
        """Test handling of structures without new interaction types."""
        # Test with a small structure that might not have new interactions
        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        # This should not crash even if no new interactions are found
        success = analyzer.analyze_file("example_pdb_files/7nwd.pdb")
        assert success

        # Empty interaction lists should be handled gracefully
        summary = analyzer.get_summary()
        assert "total_interactions" in summary
        assert summary["total_interactions"] >= 0


@pytest.mark.e2e
class TestResultsOutput:
    """Test results output and formatting."""

    def test_summary_includes_all_interaction_types(self):
        """Test that summary includes all new interaction types."""
        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        success = analyzer.analyze_file("example_pdb_files/7nwd.pdb")
        assert success

        summary = analyzer.get_summary()

        # Check that all main interaction types are in summary
        expected_keys = [
            "hydrogen_bonds",
            "halogen_bonds",
            "pi_interactions",
            "pi_pi_interactions",
            "carbonyl_interactions",
            "n_pi_interactions",
            "total_interactions",
        ]

        for key in expected_keys:
            assert key in summary, f"Missing {key} in summary"
            if key != "total_interactions":  # total_interactions is just an integer
                assert isinstance(summary[key], dict), f"{key} should be a dict"
                assert "count" in summary[key], f"Missing count in {key}"

    def test_interaction_string_representations(self):
        """Test string representations of all new interaction types."""
        params = AnalysisParameters()

        # Test π-π interactions
        analyzer = MolecularInteractionAnalyzer(parameters=params)
        success = analyzer.analyze_file("example_pdb_files/7nwd.pdb")
        assert success

        if analyzer.pi_pi_interactions:
            pi_pi_str = str(analyzer.pi_pi_interactions[0])
            assert "π-π" in pi_pi_str or "Pi-Pi" in pi_pi_str
            assert "Å" in pi_pi_str

        # Test carbonyl interactions
        analyzer2 = MolecularInteractionAnalyzer(parameters=params)
        success2 = analyzer2.analyze_file("example_pdb_files/6rsa.pdb")
        assert success2

        if analyzer2.carbonyl_interactions:
            carbonyl_str = str(analyzer2.carbonyl_interactions[0])
            assert "C=O" in carbonyl_str
            assert "Å" in carbonyl_str

        # Test n→π* interactions
        analyzer3 = MolecularInteractionAnalyzer(parameters=params)
        success3 = analyzer3.analyze_file("example_pdb_files/1luc.pdb")
        assert success3

        if analyzer3.n_pi_interactions:
            n_pi_str = str(analyzer3.n_pi_interactions[0])
            assert "n→π*" in n_pi_str or "n-Pi" in n_pi_str
            assert "Å" in n_pi_str


@pytest.mark.e2e
class TestPerformanceAndScaling:
    """Test performance and scaling of complete workflow."""

    def test_large_structure_analysis(self):
        """Test analysis performance with larger structures."""
        import time

        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        # Test with 1luc.pdb (larger structure)
        start_time = time.time()
        success = analyzer.analyze_file("example_pdb_files/1luc.pdb")
        end_time = time.time()

        assert success
        analysis_time = end_time - start_time

        # Should complete reasonably fast (allowing for CI/slower systems)
        # Increased to 150s to accommodate GitHub Actions and varying system loads
        assert analysis_time < 150.0, f"Analysis too slow: {analysis_time:.2f}s"

        # Should detect reasonable number of interactions
        total_interactions = (
            len(analyzer.hydrogen_bonds or [])
            + len(analyzer.halogen_bonds or [])
            + len(analyzer.pi_pi_interactions or [])
            + len(analyzer.carbonyl_interactions or [])
            + len(analyzer.n_pi_interactions or [])
        )

        assert total_interactions > 20, (
            f"Too few interactions for large structure: {total_interactions}"
        )

        print(
            f"✓ Large structure analysis: {analysis_time:.2f}s, {total_interactions} interactions"
        )

    def test_multiple_structure_analysis(self):
        """Test analyzing multiple structures in sequence."""
        params = AnalysisParameters()

        structures = [
            "example_pdb_files/7nwd.pdb",
            "example_pdb_files/6rsa.pdb",
            "example_pdb_files/1luc.pdb",
        ]

        total_interactions = 0

        for pdb_file in structures:
            analyzer = MolecularInteractionAnalyzer(parameters=params)
            success = analyzer.analyze_file(pdb_file)
            assert success, f"Failed to analyze {pdb_file}"

            structure_total = (
                len(analyzer.hydrogen_bonds or [])
                + len(analyzer.halogen_bonds or [])
                + len(analyzer.pi_pi_interactions or [])
                + len(analyzer.carbonyl_interactions or [])
                + len(analyzer.n_pi_interactions or [])
            )

            total_interactions += structure_total
            print(f"  {os.path.basename(pdb_file)}: {structure_total} interactions")

        assert total_interactions > 50, (
            f"Expected more total interactions: {total_interactions}"
        )
        print(f"✓ Multiple structure analysis: {total_interactions} total interactions")


@pytest.mark.e2e
class TestBackwardCompatibility:
    """Test backward compatibility with existing functionality."""

    def test_existing_functionality_unchanged(self):
        """Test that existing H-bond and X-bond detection is unchanged."""
        params = AnalysisParameters()
        analyzer = MolecularInteractionAnalyzer(parameters=params)

        success = analyzer.analyze_file("example_pdb_files/6rsa.pdb")
        assert success

        # Traditional interactions should still work as before
        assert analyzer.hydrogen_bonds is not None
        assert len(analyzer.hydrogen_bonds) > 0

        # Check that H-bond properties are unchanged
        h_bond = analyzer.hydrogen_bonds[0]
        assert hasattr(h_bond, "donor")
        assert hasattr(h_bond, "hydrogen")
        assert hasattr(h_bond, "acceptor")
        assert hasattr(h_bond, "distance")
        assert hasattr(h_bond, "angle")

        # Summary should include traditional interactions
        summary = analyzer.get_summary()
        assert summary["hydrogen_bonds"]["count"] > 0

        print(
            f"✓ Backward compatibility: {summary['hydrogen_bonds']['count']} H-bonds detected"
        )


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
