import pytest
from sqlalchemy.ext.asyncio import AsyncSession

from src import models
from src.crud.peer_card import construct_peer_card_label, get_peer_card, set_peer_card
from src.exceptions import ResourceNotFoundException


@pytest.mark.asyncio
async def test_peer_card_get_set_roundtrip(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Roundtrip set/get for peer card on a valid peer."""
    workspace, peer = sample_data

    # Initially absent
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer.name, observed=peer.name
        )
        is None
    )

    # Set and read back
    value_1 = ["Initial peer card text"]
    await set_peer_card(
        db_session,
        workspace.name,
        value_1,
        observer=peer.name,
        observed=peer.name,
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer.name, observed=peer.name
        )
        == value_1
    )

    # Update and read back
    value_2 = ["Updated peer card text", "Another line"]
    await set_peer_card(
        db_session, workspace.name, value_2, observer=peer.name, observed=peer.name
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer.name, observed=peer.name
        )
        == value_2
    )


@pytest.mark.asyncio
async def test_get_peer_card_missing_peer_raises(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Getting a peer card for a non-existent peer should raise ResourceNotFoundException."""
    workspace, _existing_peer = sample_data
    with pytest.raises(ResourceNotFoundException):
        await get_peer_card(
            db_session,
            workspace.name,
            observer="missing-peer",
            observed="missing-peer",
        )


@pytest.mark.asyncio
async def test_get_peer_card_missing_workspace_raises(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Getting a peer card for a non-existent workspace should raise ResourceNotFoundException."""
    _workspace, peer = sample_data
    with pytest.raises(ResourceNotFoundException):
        await get_peer_card(
            db_session,
            "missing-workspace",
            observer=peer.name,
            observed=peer.name,
        )


@pytest.mark.asyncio
async def test_peer_card_empty_list(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Setting and getting an empty peer card should work."""
    workspace, peer = sample_data

    empty_card: list[str] = []
    await set_peer_card(
        db_session,
        workspace.name,
        empty_card,
        observer=peer.name,
        observed=peer.name,
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer.name, observed=peer.name
        )
        == empty_card
    )


@pytest.mark.asyncio
async def test_peer_card_multiple_lines(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Peer card should support multiple lines."""
    workspace, peer = sample_data

    multi_line_card = [
        "First observation about the peer",
        "Second observation about the peer",
        "Third observation about the peer",
        "Fourth observation about the peer",
    ]
    await set_peer_card(
        db_session,
        workspace.name,
        multi_line_card,
        observer=peer.name,
        observed=peer.name,
    )
    result = await get_peer_card(
        db_session, workspace.name, observer=peer.name, observed=peer.name
    )
    assert result is not None
    assert result == multi_line_card
    assert len(result) == 4


@pytest.mark.asyncio
async def test_peer_card_different_observer_observed(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Test peer card with different observer and observed peers."""
    workspace, peer1 = sample_data

    # Create second peer
    peer2 = models.Peer(name="test-peer-2", workspace_name=workspace.name)
    db_session.add(peer2)
    await db_session.flush()

    # Peer1 observes peer2
    card_1_to_2 = ["Peer 1's observation of peer 2"]
    await set_peer_card(
        db_session,
        workspace.name,
        card_1_to_2,
        observer=peer1.name,
        observed=peer2.name,
    )

    # Peer2 observes peer1
    card_2_to_1 = ["Peer 2's observation of peer 1"]
    await set_peer_card(
        db_session,
        workspace.name,
        card_2_to_1,
        observer=peer2.name,
        observed=peer1.name,
    )

    # Verify each card is independent
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer1.name, observed=peer2.name
        )
        == card_1_to_2
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer2.name, observed=peer1.name
        )
        == card_2_to_1
    )


@pytest.mark.asyncio
async def test_peer_card_self_and_other_observations(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Test that a peer can have both a self-observation and observations of others."""
    workspace, peer1 = sample_data

    # Create second peer
    peer2 = models.Peer(name="test-peer-2", workspace_name=workspace.name)
    db_session.add(peer2)
    await db_session.flush()

    # Peer1's self-observation
    self_card = ["Self observation"]
    await set_peer_card(
        db_session,
        workspace.name,
        self_card,
        observer=peer1.name,
        observed=peer1.name,
    )

    # Peer1's observation of peer2
    other_card = ["Observation of other peer"]
    await set_peer_card(
        db_session,
        workspace.name,
        other_card,
        observer=peer1.name,
        observed=peer2.name,
    )

    # Both should be retrievable independently
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer1.name, observed=peer1.name
        )
        == self_card
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer1.name, observed=peer2.name
        )
        == other_card
    )


@pytest.mark.asyncio
async def test_peer_card_multiple_observers_same_observed(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Test that multiple peers can observe the same peer with different cards."""
    workspace, peer1 = sample_data

    # Create two more peers
    peer2 = models.Peer(name="test-peer-2", workspace_name=workspace.name)
    peer3 = models.Peer(name="test-peer-3", workspace_name=workspace.name)
    db_session.add_all([peer2, peer3])
    await db_session.flush()

    # Both peer2 and peer3 observe peer1
    card_2_to_1 = ["Peer 2's view of peer 1"]
    card_3_to_1 = ["Peer 3's view of peer 1"]

    await set_peer_card(
        db_session,
        workspace.name,
        card_2_to_1,
        observer=peer2.name,
        observed=peer1.name,
    )
    await set_peer_card(
        db_session,
        workspace.name,
        card_3_to_1,
        observer=peer3.name,
        observed=peer1.name,
    )

    # Each observer should have their own independent card
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer2.name, observed=peer1.name
        )
        == card_2_to_1
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer3.name, observed=peer1.name
        )
        == card_3_to_1
    )


@pytest.mark.asyncio
async def test_peer_card_update_does_not_affect_others(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Test that updating one peer card doesn't affect other peer cards."""
    workspace, peer1 = sample_data

    # Create second peer
    peer2 = models.Peer(name="test-peer-2", workspace_name=workspace.name)
    db_session.add(peer2)
    await db_session.flush()

    # Set initial cards
    card_1 = ["Peer 1 self observation"]
    card_2 = ["Peer 1's observation of peer 2"]

    await set_peer_card(
        db_session,
        workspace.name,
        card_1,
        observer=peer1.name,
        observed=peer1.name,
    )
    await set_peer_card(
        db_session,
        workspace.name,
        card_2,
        observer=peer1.name,
        observed=peer2.name,
    )

    # Update one card
    new_card_1 = ["Updated self observation"]
    await set_peer_card(
        db_session,
        workspace.name,
        new_card_1,
        observer=peer1.name,
        observed=peer1.name,
    )

    # Verify only the updated card changed
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer1.name, observed=peer1.name
        )
        == new_card_1
    )
    assert (
        await get_peer_card(
            db_session, workspace.name, observer=peer1.name, observed=peer2.name
        )
        == card_2
    )


@pytest.mark.asyncio
async def test_peer_card_special_characters(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Test that peer cards handle special characters correctly."""
    workspace, peer = sample_data

    special_card = [
        "Line with special chars: @#$%^&*()",
        "Line with unicode: 你好世界 🌍",
        "Line with quotes: \"double\" and 'single'",
        "Line with newlines embedded\\n",
    ]
    await set_peer_card(
        db_session,
        workspace.name,
        special_card,
        observer=peer.name,
        observed=peer.name,
    )
    result = await get_peer_card(
        db_session, workspace.name, observer=peer.name, observed=peer.name
    )
    assert result == special_card


@pytest.mark.asyncio
async def test_peer_card_large_content(
    db_session: AsyncSession, sample_data: tuple[models.Workspace, models.Peer]
):
    """Test that peer cards can handle large amounts of content."""
    workspace, peer = sample_data

    # Create a large peer card with many observations
    large_card = [f"Observation number {i}" for i in range(100)]
    await set_peer_card(
        db_session,
        workspace.name,
        large_card,
        observer=peer.name,
        observed=peer.name,
    )
    result = await get_peer_card(
        db_session, workspace.name, observer=peer.name, observed=peer.name
    )
    assert result == large_card
    assert result is not None
    assert len(result) == 100


def test_construct_helpers_labels():
    """Helper label constructors return expected values."""
    assert construct_peer_card_label(observer="a", observed="a") == "peer_card"
    assert construct_peer_card_label(observer="a", observed="b") == "b_peer_card"


def test_construct_peer_card_label_with_special_chars():
    """Test label construction with special characters in peer names."""
    # Test with dashes and underscores
    assert (
        construct_peer_card_label(observer="peer-1", observed="peer-2")
        == "peer-2_peer_card"
    )
    assert (
        construct_peer_card_label(observer="peer_1", observed="peer_2")
        == "peer_2_peer_card"
    )

    # Test with same observer and observed with special chars
    assert (
        construct_peer_card_label(observer="peer-1", observed="peer-1") == "peer_card"
    )


def test_construct_peer_card_label_edge_cases():
    """Test label construction edge cases."""
    # Test with empty strings (though this shouldn't happen in practice)
    assert construct_peer_card_label(observer="", observed="") == "peer_card"
    assert construct_peer_card_label(observer="a", observed="") == "_peer_card"

    # Test with very long peer names
    long_name = "a" * 100
    assert (
        construct_peer_card_label(observer=long_name, observed=long_name) == "peer_card"
    )
    assert (
        construct_peer_card_label(observer="a", observed=long_name)
        == f"{long_name}_peer_card"
    )
