"""
Unit tests for carbonyl-carbonyl n→π* interaction class.

These tests verify carbonyl-carbonyl interactions in isolation without dependencies
on PDB files or analysis workflows.
"""

import pytest
import math
from hbat.core.interactions import CarbonylInteraction, MolecularInteraction
from hbat.core.structure import Atom
from hbat.core.np_vector import NPVec3D


@pytest.mark.unit
class TestCarbonylInteractionCreation:
    """Test carbonyl-carbonyl interaction creation and basic properties."""

    @pytest.fixture
    def sample_carbonyl_atoms(self):
        """Create sample atoms for testing carbonyl interactions."""
        # Donor carbonyl C=O (backbone amide)
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        # Acceptor carbonyl C=O (backbone amide)
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        return donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen

    def test_carbonyl_interaction_creation_valid_atoms(self, sample_carbonyl_atoms):
        """Test carbonyl interaction creation with valid atoms."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = (
            sample_carbonyl_atoms
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        assert carbonyl.donor_carbon == donor_carbon
        assert carbonyl.donor_oxygen == donor_oxygen
        assert carbonyl.acceptor_carbon == acceptor_carbon
        assert carbonyl.acceptor_oxygen == acceptor_oxygen
        assert carbonyl.distance == 3.2
        assert carbonyl.burgi_dunitz_angle == 107.0
        assert carbonyl.is_backbone is True
        assert carbonyl.get_donor_residue() == "A:ALA:1"
        assert carbonyl.get_acceptor_residue() == "A:GLY:5"

    def test_carbonyl_interaction_inheritance(self, sample_carbonyl_atoms):
        """Test that CarbonylInteraction inherits from MolecularInteraction."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = (
            sample_carbonyl_atoms
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        assert isinstance(carbonyl, MolecularInteraction)

    def test_carbonyl_interaction_type(self, sample_carbonyl_atoms):
        """Test carbonyl interaction type."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = (
            sample_carbonyl_atoms
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        assert carbonyl.get_interaction_type() == "Carbonyl-Carbonyl"

    def test_carbonyl_interaction_interface_methods(self, sample_carbonyl_atoms):
        """Test carbonyl interaction interface methods."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = (
            sample_carbonyl_atoms
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        # Test interface methods
        assert carbonyl.get_donor() == donor_oxygen
        assert carbonyl.get_acceptor() == acceptor_carbon
        assert carbonyl.get_interaction() == donor_oxygen
        assert carbonyl.get_donor_residue() == "A:ALA:1"
        assert carbonyl.get_acceptor_residue() == "A:GLY:5"


@pytest.mark.unit
class TestCarbonylInteractionAngles:
    """Test carbonyl interaction angle properties and calculations."""

    @pytest.fixture
    def carbonyl_atoms(self):
        """Create atoms for angle testing."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        return donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen

    def test_burgi_dunitz_angle_range(self, carbonyl_atoms):
        """Test typical Bürgi-Dunitz angle range (95-125°)."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = carbonyl_atoms

        typical_angles = [95.0, 100.0, 107.0, 115.0, 125.0]

        for angle_deg in typical_angles:
            carbonyl = CarbonylInteraction(
                donor_carbon=donor_carbon,
                donor_oxygen=donor_oxygen,
                acceptor_carbon=acceptor_carbon,
                acceptor_oxygen=acceptor_oxygen,
                distance=3.2,
                burgi_dunitz_angle=angle_deg,
                is_backbone=True,
            )

            assert carbonyl.burgi_dunitz_angle == angle_deg
            assert 95.0 <= carbonyl.burgi_dunitz_angle <= 125.0

    def test_angle_property_conversion(self, carbonyl_atoms):
        """Test that angle property correctly converts degrees to radians."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = carbonyl_atoms

        angle_degrees = 107.0
        expected_radians = math.radians(angle_degrees)

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=angle_degrees,
            is_backbone=True,
        )

        assert carbonyl.burgi_dunitz_angle == angle_degrees
        assert abs(carbonyl.angle - expected_radians) < 1e-6

    def test_donor_interaction_acceptor_angle(self, carbonyl_atoms):
        """Test get_donor_interaction_acceptor_angle method."""
        donor_carbon, donor_oxygen, acceptor_carbon, acceptor_oxygen = carbonyl_atoms

        angle_degrees = 112.5

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=angle_degrees,
            is_backbone=True,
        )

        angle_radians = carbonyl.get_donor_interaction_acceptor_angle()
        assert abs(angle_radians - math.radians(angle_degrees)) < 1e-6


@pytest.mark.unit
class TestCarbonylInteractionTypes:
    """Test different types of carbonyl interactions."""

    @pytest.fixture
    def mixed_carbonyl_atoms(self):
        """Create atoms for testing different carbonyl types."""
        # Backbone carbonyl
        backbone_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        backbone_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        # Sidechain carbonyl (e.g., ASN, GLN)
        sidechain_carbon = Atom(
            serial=3,
            name="CG",
            alt_loc="",
            res_name="ASN",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        sidechain_oxygen = Atom(
            serial=4,
            name="OD1",
            alt_loc="",
            res_name="ASN",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        return backbone_carbon, backbone_oxygen, sidechain_carbon, sidechain_oxygen

    def test_backbone_backbone_interaction(self, mixed_carbonyl_atoms):
        """Test backbone-backbone carbonyl interaction."""
        bb_carbon, bb_oxygen, sc_carbon, sc_oxygen = mixed_carbonyl_atoms

        carbonyl = CarbonylInteraction(
            donor_carbon=bb_carbon,
            donor_oxygen=bb_oxygen,
            acceptor_carbon=bb_carbon,
            acceptor_oxygen=bb_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        assert carbonyl.is_backbone is True
        assert carbonyl.is_backbone_interaction() is True
        assert carbonyl.interaction_classification == "backbone-backbone"

    def test_sidechain_sidechain_interaction(self, mixed_carbonyl_atoms):
        """Test sidechain-sidechain carbonyl interaction."""
        bb_carbon, bb_oxygen, sc_carbon, sc_oxygen = mixed_carbonyl_atoms

        carbonyl = CarbonylInteraction(
            donor_carbon=sc_carbon,
            donor_oxygen=sc_oxygen,
            acceptor_carbon=sc_carbon,
            acceptor_oxygen=sc_oxygen,
            distance=3.5,
            burgi_dunitz_angle=102.0,
            is_backbone=False,
        )

        assert carbonyl.is_backbone is False
        assert carbonyl.is_backbone_interaction() is False
        assert carbonyl.interaction_classification == "sidechain-sidechain"

    def test_mixed_backbone_sidechain_interaction(self, mixed_carbonyl_atoms):
        """Test mixed backbone-sidechain carbonyl interaction."""
        bb_carbon, bb_oxygen, sc_carbon, sc_oxygen = mixed_carbonyl_atoms

        # Backbone donor, sidechain acceptor
        carbonyl = CarbonylInteraction(
            donor_carbon=bb_carbon,
            donor_oxygen=bb_oxygen,
            acceptor_carbon=sc_carbon,
            acceptor_oxygen=sc_oxygen,
            distance=3.4,
            burgi_dunitz_angle=110.0,
            is_backbone=False,
        )

        assert carbonyl.is_backbone is False
        assert carbonyl.is_backbone_interaction() is False
        assert carbonyl.interaction_classification == "backbone-sidechain"


@pytest.mark.unit
class TestCarbonylInteractionDistances:
    """Test carbonyl interaction distance calculations."""

    def test_donor_acceptor_distance(self):
        """Test O···C distance calculation."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(5.4, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        distance = 3.0  # O···C distance

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=distance,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        assert carbonyl.get_donor_acceptor_distance() == distance
        assert carbonyl.distance == distance

    def test_donor_interaction_distance(self):
        """Test C=O bond distance calculation."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.23, 0, 0),
            occupancy=1.0,  # Typical C=O bond
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        bond_distance = carbonyl.get_donor_interaction_distance()
        assert abs(bond_distance - 1.23) < 0.01  # Typical C=O bond length


@pytest.mark.unit
class TestCarbonylInteractionValidation:
    """Test validation of carbonyl interaction objects."""

    def test_carbonyl_interaction_required_attributes(self):
        """Test that carbonyl interaction has all required attributes."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        # Test required attributes
        required_attrs = [
            "donor_carbon",
            "donor_oxygen",
            "acceptor_carbon",
            "acceptor_oxygen",
            "distance",
            "burgi_dunitz_angle",
            "is_backbone",
            "interaction_classification",
            "is_between_residues",
        ]

        for attr in required_attrs:
            assert hasattr(carbonyl, attr), f"Missing attribute: {attr}"

        # Test reasonable values
        assert carbonyl.distance > 0
        assert 0 <= carbonyl.burgi_dunitz_angle <= 180
        assert isinstance(carbonyl.is_backbone, bool)
        assert isinstance(carbonyl.is_between_residues, bool)

    def test_carbonyl_interaction_bonding_validation(self):
        """Test carbonyl interaction bonding validation."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        # Carbonyl interactions assume oxygen is bonded to carbon
        assert carbonyl.is_donor_interaction_bonded() is True

    def test_carbonyl_atoms_retrieval(self):
        """Test get_carbonyl_atoms method."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=1,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="A",
            res_seq=5,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.2,
            burgi_dunitz_angle=107.0,
            is_backbone=True,
        )

        donor_atoms = carbonyl.get_carbonyl_atoms("donor")
        acceptor_atoms = carbonyl.get_carbonyl_atoms("acceptor")

        assert donor_atoms == (donor_carbon, donor_oxygen)
        assert acceptor_atoms == (acceptor_carbon, acceptor_oxygen)

        # Test invalid carbonyl type
        with pytest.raises(
            ValueError, match="Carbonyl type must be 'donor' or 'acceptor'"
        ):
            carbonyl.get_carbonyl_atoms("invalid")


@pytest.mark.unit
class TestCarbonylInteractionString:
    """Test carbonyl interaction string representation."""

    def test_carbonyl_interaction_string_representation(self):
        """Test carbonyl interaction string representation."""
        donor_carbon = Atom(
            serial=1,
            name="C",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=10,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="O",
            alt_loc="",
            res_name="ALA",
            chain_id="A",
            res_seq=10,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="GLY",
            chain_id="B",
            res_seq=25,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="GLY",
            chain_id="B",
            res_seq=25,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.15,
            burgi_dunitz_angle=108.5,
            is_backbone=True,
        )

        str_repr = str(carbonyl)

        # Check that important information is included
        assert "C=O···C=O" in str_repr
        assert "backbone-backbone" in str_repr
        assert "A:ALA:10" in str_repr
        assert "B:GLY:25" in str_repr
        assert "3.15" in str_repr
        assert "108.5" in str_repr

    def test_carbonyl_interaction_different_types(self):
        """Test string representation with different interaction types."""
        # Sidechain carbonyl atoms
        donor_carbon = Atom(
            serial=1,
            name="CG",
            alt_loc="",
            res_name="ASN",
            chain_id="A",
            res_seq=15,
            i_code="",
            coords=NPVec3D(0, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        donor_oxygen = Atom(
            serial=2,
            name="OD1",
            alt_loc="",
            res_name="ASN",
            chain_id="A",
            res_seq=15,
            i_code="",
            coords=NPVec3D(1.2, 0, 0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )
        acceptor_carbon = Atom(
            serial=3,
            name="C",
            alt_loc="",
            res_name="VAL",
            chain_id="A",
            res_seq=30,
            i_code="",
            coords=NPVec3D(3.0, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="C",
            charge="",
            record_type="ATOM",
        )
        acceptor_oxygen = Atom(
            serial=4,
            name="O",
            alt_loc="",
            res_name="VAL",
            chain_id="A",
            res_seq=30,
            i_code="",
            coords=NPVec3D(4.2, 2.0, 1.0),
            occupancy=1.0,
            temp_factor=20.0,
            element="O",
            charge="",
            record_type="ATOM",
        )

        carbonyl = CarbonylInteraction(
            donor_carbon=donor_carbon,
            donor_oxygen=donor_oxygen,
            acceptor_carbon=acceptor_carbon,
            acceptor_oxygen=acceptor_oxygen,
            distance=3.35,
            burgi_dunitz_angle=103.2,
            is_backbone=False,
        )

        str_repr = str(carbonyl)
        assert "A:ASN:15" in str_repr
        assert "A:VAL:30" in str_repr
        assert "3.35" in str_repr
        assert "103.2" in str_repr
        assert "sidechain-backbone" in str_repr
