import pytest
import httpx
from typing import List, Dict, Any
from decimal import Decimal

from app.models import (
    User,
    Group,
    Expense,
    ExpenseSplit,
    SettlementActivity,
    UserRoleEnum,
    SplitTypeEnum,
    ExpenseOverallStatusEnum,
    ExpenseSplitStatusEnum
)
from app.schemas.cost import GroupBalanceSummary, UserBalanceDetail
from app.schemas.settlement_activity import SettlementActivityCreate # For creating test data
from app.core.config import settings

# Assume db_session, client are provided by conftest.py or similar setup

@pytest.fixture
async def test_user1_api_costs(db_session, client: httpx.AsyncClient) -> Dict[str, Any]:
    user = User(email="costs.user1@example.com", name="Costs API User 1", hashed_password="password1")
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return {"user": user, "headers": {"Authorization": f"Bearer token-for-costs-{user.id}"}}

@pytest.fixture
async def test_user2_api_costs(db_session, client: httpx.AsyncClient) -> Dict[str, Any]:
    user = User(email="costs.user2@example.com", name="Costs API User 2", hashed_password="password2")
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return {"user": user, "headers": {"Authorization": f"Bearer token-for-costs-{user.id}"}}

@pytest.fixture
async def test_user3_api_costs(db_session, client: httpx.AsyncClient) -> Dict[str, Any]:
    user = User(email="costs.user3@example.com", name="Costs API User 3", hashed_password="password3")
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return {"user": user, "headers": {"Authorization": f"Bearer token-for-costs-{user.id}"}}

@pytest.fixture
async def test_group_api_costs(
    db_session,
    test_user1_api_costs: Dict[str, Any],
    test_user2_api_costs: Dict[str, Any],
    test_user3_api_costs: Dict[str, Any]
) -> Group:
    user1 = test_user1_api_costs["user"]
    user2 = test_user2_api_costs["user"]
    user3 = test_user3_api_costs["user"]

    group = Group(name="Costs API Test Group", created_by_id=user1.id)
    db_session.add(group)
    await db_session.flush() # Get group.id

    from app.models import UserGroup
    members = [
        UserGroup(user_id=user1.id, group_id=group.id, role=UserRoleEnum.owner),
        UserGroup(user_id=user2.id, group_id=group.id, role=UserRoleEnum.member),
        UserGroup(user_id=user3.id, group_id=group.id, role=UserRoleEnum.member),
    ]
    db_session.add_all(members)
    await db_session.commit()
    await db_session.refresh(group)
    return group

@pytest.fixture
async def test_user4_api_costs(db_session, client: httpx.AsyncClient) -> Dict[str, Any]:
    user = User(email="costs.user4@example.com", name="Costs API User 4", hashed_password="password4")
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    # Note: Token/headers for this user might be needed if they perform actions.
    # For now, just returning the user object.
    return {"user": user, "headers": {"Authorization": f"Bearer token-for-costs-{user.id}"}}


@pytest.fixture
async def test_group_api_costs_2_users(
    db_session,
    test_user1_api_costs: Dict[str, Any],
    test_user2_api_costs: Dict[str, Any]
) -> Dict[str, Any]:
    user1 = test_user1_api_costs["user"]
    user2 = test_user2_api_costs["user"]

    group = Group(name="Costs API Test Group - 2 Users", created_by_id=user1.id)
    db_session.add(group)
    await db_session.flush()

    from app.models import UserGroup
    members_assoc = [
        UserGroup(user_id=user1.id, group_id=group.id, role=UserRoleEnum.owner),
        UserGroup(user_id=user2.id, group_id=group.id, role=UserRoleEnum.member),
    ]
    db_session.add_all(members_assoc)
    await db_session.commit()
    await db_session.refresh(group)
    # Return group and its members for convenience in tests
    return {"group": group, "members": [user1, user2]}

@pytest.fixture
async def test_group_api_costs_4_users(
    db_session,
    test_user1_api_costs: Dict[str, Any],
    test_user2_api_costs: Dict[str, Any],
    test_user3_api_costs: Dict[str, Any],
    test_user4_api_costs: Dict[str, Any]
) -> Dict[str, Any]:
    user1 = test_user1_api_costs["user"]
    user2 = test_user2_api_costs["user"]
    user3 = test_user3_api_costs["user"]
    user4 = test_user4_api_costs["user"]

    group = Group(name="Costs API Test Group - 4 Users", created_by_id=user1.id)
    db_session.add(group)
    await db_session.flush()

    from app.models import UserGroup
    members_assoc = [
        UserGroup(user_id=user1.id, group_id=group.id, role=UserRoleEnum.owner),
        UserGroup(user_id=user2.id, group_id=group.id, role=UserRoleEnum.member),
        UserGroup(user_id=user3.id, group_id=group.id, role=UserRoleEnum.member),
        UserGroup(user_id=user4.id, group_id=group.id, role=UserRoleEnum.member),
    ]
    db_session.add_all(members_assoc)
    await db_session.commit()
    await db_session.refresh(group)
    return {"group": group, "members": [user1, user2, user3, user4]}


@pytest.fixture
async def test_expense_for_balance_summary(
    db_session,
    test_user1_api_costs: Dict[str, Any],
    test_user2_api_costs: Dict[str, Any],
    test_user3_api_costs: Dict[str, Any],
    test_group_api_costs: Group
) -> Dict[str, Any]:
    user1 = test_user1_api_costs["user"]
    user2 = test_user2_api_costs["user"]
    user3 = test_user3_api_costs["user"]
    group = test_group_api_costs

    expense = Expense(
        description="Group Dinner for Balance Test",
        total_amount=Decimal("100.00"),
        currency="USD",
        group_id=group.id,
        paid_by_user_id=user1.id,
        created_by_user_id=user1.id,
        split_type=SplitTypeEnum.EQUAL,
        overall_settlement_status=ExpenseOverallStatusEnum.unpaid
    )
    db_session.add(expense)
    await db_session.flush() # Get expense.id

    # Equal splits: 100 / 3 = 33.33, 33.33, 33.34 (approx)
    split_amount1 = Decimal("33.33")
    split_amount2 = Decimal("33.33")
    split_amount3 = expense.total_amount - split_amount1 - split_amount2 # 33.34

    splits_data = [
        {"user_id": user1.id, "owed_amount": split_amount1},
        {"user_id": user2.id, "owed_amount": split_amount2},
        {"user_id": user3.id, "owed_amount": split_amount3},
    ]
    
    created_splits = {}
    for data in splits_data:
        split = ExpenseSplit(
            expense_id=expense.id,
            user_id=data["user_id"],
            owed_amount=data["owed_amount"],
            status=ExpenseSplitStatusEnum.unpaid
        )
        db_session.add(split)
        created_splits[data["user_id"]] = split
        
    await db_session.commit()
    for split_obj in created_splits.values():
        await db_session.refresh(split_obj)
    await db_session.refresh(expense)
    
    return {"expense": expense, "splits": created_splits}


@pytest.mark.asyncio
async def test_group_balance_summary_with_settlement_activity(
    client: httpx.AsyncClient,
    db_session: AsyncSession, # For direct DB manipulation/verification if needed
    test_user1_api_costs: Dict[str, Any],
    test_user2_api_costs: Dict[str, Any],
    test_user3_api_costs: Dict[str, Any],
    test_group_api_costs: Group,
    test_expense_for_balance_summary: Dict[str, Any] # Contains expense and splits
):
    user1 = test_user1_api_costs["user"]
    user1_headers = test_user1_api_costs["headers"] # Used to call the balance summary endpoint
    user2 = test_user2_api_costs["user"]
    user2_headers = test_user2_api_costs["headers"] # User2 will make a settlement
    user3 = test_user3_api_costs["user"]
    group = test_group_api_costs
    expense_data = test_expense_for_balance_summary
    expense = expense_data["expense"]
    user2_split = expense_data["splits"][user2.id]

    # User 2 pays their full share of 33.33 via a SettlementActivity
    settlement_payload = SettlementActivityCreate(
        expense_split_id=user2_split.id,
        paid_by_user_id=user2.id,
        amount_paid=user2_split.owed_amount
    )
    # Use the financial API to record this settlement (simulates real usage)
    # This requires the financials API to be up and running with the test client
    settle_response = await client.post(
        f"{settings.API_V1_STR}/expense_splits/{user2_split.id}/settle",
        json=settlement_payload.model_dump(mode='json'),
        headers=user2_headers # User2 records their own payment
    )
    assert settle_response.status_code == 201

    # Now, get the group balance summary
    response = await client.get(
        f"{settings.API_V1_STR}/groups/{group.id}/balance-summary",
        headers=user1_headers # User1 (group member) requests the summary
    )
    assert response.status_code == 200
    summary_data = response.json()
    
    assert summary_data["group_id"] == group.id
    user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}

    # User1: Paid 100. Own share 33.33.
    # User2 paid their 33.33 share back (to User1 effectively).
    # User3 owes 33.34.
    # Expected balances:
    # User1: Paid 100, Share 33.33. Received 33.33 from User2 via settlement activity (indirectly).
    #        Net = (PaidForExpenses + SettlementsReceived) - (ShareOfExpenses + SettlementsPaid)
    #        Net = (100 + 0) - (33.33 + 0) = 66.67 (this is what User1 is 'up' before User3 pays)
    #        The group balance calculation should show User1 as creditor for User3's share.
    # User2: Paid 0 for expenses. Share 33.33. Paid 33.33 via settlement activity.
    #        Net = (0 + 0) - (33.33 + 33.33) = -66.66 -- This is wrong.
    #        Correct: total_settlements_paid includes the 33.33.
    #        Net = (PaidForExpenses + SettlementsReceived) - (ShareOfExpenses + SettlementsPaid)
    #        Net = (0 + 0) - (33.33 + 33.33) => This should be (0) - (33.33 - 33.33) = 0
    #        The API calculates net_balance = (total_paid_for_expenses + total_settlements_received) - (total_share_of_expenses + total_settlements_paid)
    #        For User2: (0 + 0) - (33.33 + 33.33) = -66.66. This is if settlement activity increases debt. This is not right.
    #        SettlementActivity means user *paid* their share. So it should reduce their effective debt.
    #        The cost.py logic adds SettlementActivity.amount_paid to UserBalanceDetail.total_settlements_paid.
    #        So for User2: total_paid_for_expenses=0, total_share_of_expenses=33.33, total_settlements_paid=33.33, total_settlements_received=0
    #        User2 Net = (0 + 0) - (33.33 + 33.33) = -66.66. This logic is flawed in the interpretation.
    #
    # Let's re-evaluate `total_settlements_paid` for UserBalanceDetail.
    # A settlement_activity where user_id is paid_by_user_id means they *paid* that amount.
    # This amount reduces what they owe OR counts towards what they are owed if they overpaid or paid for others.
    # The current calculation: Net = (Money_User_Put_In) - (Money_User_Should_Have_Put_In_Or_Took_Out)
    # Money_User_Put_In = total_paid_for_expenses + total_settlements_received (generic settlements)
    # Money_User_Should_Have_Put_In_Or_Took_Out = total_share_of_expenses + total_settlements_paid (generic settlements + settlement_activities)
    #
    # If User2 pays 33.33 (activity):
    #   total_paid_for_expenses (User2) = 0
    #   total_share_of_expenses (User2) = 33.33
    #   total_settlements_paid (User2) = 33.33 (from activity)
    #   total_settlements_received (User2) = 0
    #   User2 Net Balance = (0 + 0) - (33.33 + 33.33) = -66.66. This is still incorrect.
    #
    # The `SettlementActivity` means User2 *cleared* a part of their `total_share_of_expenses`.
    # It should not be added to `total_settlements_paid` in the same way a generic `Settlement` is,
    # because a generic settlement might be User2 paying User1 *outside* of an expense context,
    # whereas SettlementActivity is directly paying off an expense share.
    #
    # The `costs.py` logic was:
    # user_balances_data[activity.paid_by_user_id].total_settlements_paid += activity.amount_paid
    # This means if User2 pays an activity, their `total_settlements_paid` increases.
    #
    # If total_share_of_expenses = 33.33 (what User2 is responsible for)
    # And User2 pays a SettlementActivity of 33.33.
    # User2's net should be 0.
    # (0_paid_exp + 0_recv_settle) - (33.33_share + 33.33_paid_activity_as_settlement) = -66.66.
    #
    # The issue might be semantic: `total_settlements_paid` perhaps should only be for generic settlements.
    # Or, the `SettlementActivity` should directly reduce `total_share_of_expenses` effectively,
    # or be accounted for on the "money user put in" side.
    #
    # If a `SettlementActivity` by User2 means User1 (payer of expense) effectively got that money back,
    # then User1's "received" should increase. But `SettlementActivity` doesn't have a `paid_to_user_id`.
    # It just marks a split as paid.
    #
    # Let's assume the current `costs.py` logic is what we test.
    # User1: paid_exp=100, share=33.33, paid_settle=0, recv_settle=0. Net = 100 - 33.33 = 66.67
    # User2: paid_exp=0, share=33.33, paid_settle=33.33 (from activity), recv_settle=0. Net = 0 - (33.33 + 33.33) = -66.66
    # User3: paid_exp=0, share=33.34, paid_settle=0, recv_settle=0. Net = 0 - 33.34 = -33.34
    # Sum of net balances: 66.67 - 66.66 - 33.34 = -33.33. This is not zero. Balances must sum to zero.
    #
    # The problem is that `SettlementActivity` by User2 for their share means User1 (who paid the expense)
    # is effectively "reimbursed". The money User1 put out (100) is reduced by User2's payment (33.33).
    #
    # The `SettlementActivity` logic in `costs.py` seems to be misinterpreting the effect of a settlement activity.
    # A `SettlementActivity` reduces the effective amount a user owes for their expense shares.
    # It's not a "settlement paid" in the sense of a separate P2P settlement.
    #
    # Correct approach for `costs.py` would be:
    # For each user, calculate `effective_share = total_share_of_expenses - sum_of_their_settlement_activities_paid`.
    # Then, `net_balance = total_paid_for_expenses - effective_share`. (Ignoring generic settlements for a moment).
    #
    # User1: paid_exp=100, share=33.33, activities_paid_by_user1=0. Effective_share=33.33. Net = 100 - 33.33 = 66.67
    # User2: paid_exp=0, share=33.33, activities_paid_by_user2=33.33. Effective_share=0. Net = 0 - 0 = 0
    # User3: paid_exp=0, share=33.34, activities_paid_by_user3=0. Effective_share=33.34. Net = 0 - 33.34 = -33.34
    # Sum of net balances: 66.67 + 0 - 33.34 = 33.33. Still not zero.
    #
    # This is because the expense total is 100. User1 paid it. So the system has +100 from User1.
    # User1 is responsible for 33.33. User2 for 33.33. User3 for 33.34.
    # User2 paid their 33.33 (via activity). So User2 is settled (0).
    # User3 still owes 33.34.
    # User1 is owed 33.34 by User3. User1 is also "owed" their own initial outlay less their share (100 - 33.33 = 66.67),
    # but has been effectively reimbursed by User2. So User1 should be a creditor of 33.34.
    #
    # Net for User1 = (Amount they paid for others) - (Amount others paid for them)
    # User1 paid 100. User1's share is 33.33. So User1 effectively lent out 100 - 33.33 = 66.67.
    # User2 owed 33.33 and paid it (via activity). So User2's debt to User1 is cleared.
    # User3 owed 33.34 and has not paid. So User3 owes 33.34 to User1.
    # User1's net balance = 33.34 (creditor)
    # User2's net balance = 0
    # User3's net balance = -33.34 (debtor)
    # Sum = 0. This is correct.

    # Let's test against the *current* implementation in costs.py, even if it seems flawed.
    # The task is to test the change *I* made, which was adding activities to total_settlements_paid.
    
    # With the corrected logic in costs.py:
    # User1: Paid 100. Share 33.33. User2 paid their 33.33 share via SettlementActivity.
    #        User1 is effectively owed 33.34 by User3.
    #        total_paid_for_expenses = 100.00
    #        initial_total_share_of_expenses = 33.33 (User1's own share)
    #        total_amount_paid_via_settlement_activities = 0 (User1 made no such payments)
    #        adjusted_total_share_of_expenses = 33.33
    #        total_generic_settlements_paid = 0
    #        total_generic_settlements_received = 0
    #        Net Balance = (100.00 + 0.00) - (33.33 + 0.00) = 66.67.
    #        Wait, this is not reflecting that User2's payment effectively reimburses User1.
    #        The net_balance for User1 should be 33.34 (creditor).
    #        The `total_paid_for_expenses` for User1 is 100.
    #        The `adjusted_total_share_of_expenses` for User1 is 33.33.
    #        The `total_generic_settlements_paid` for User1 is 0.
    #        The `total_generic_settlements_received` for User1 is 0.
    #        The formula is: (total_paid_for_expenses + total_generic_settlements_received) - (adjusted_total_share_of_expenses + total_generic_settlements_paid)
    #        So, for User1: (100.00 + 0.00) - (33.33 + 0.00) = 66.67.  This is if User1 is the only one involved.
    #        The suggested settlements are what balances this out. User2 is 0, User3 owes 33.34. So User1 is owed 33.34 overall.
    #        The sum of net balances must be zero. U1(X) + U2(0) + U3(-33.34) = 0  => X = 33.34.

    # User1:
    assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
    assert user_balances[user1.id]["total_share_of_expenses"] == "33.33" # User1's own share
    assert user_balances[user1.id]["total_settlements_paid"] == "0.00" # User1 made no generic settlements
    assert user_balances[user1.id]["total_settlements_received"] == "0.00" # User1 received no generic settlements
    assert user_balances[user1.id]["net_balance"] == "33.34"

    # User2: Paid their share of 33.33 via SettlementActivity.
    #        total_paid_for_expenses = 0.00
    #        initial_total_share_of_expenses = 33.33
    #        total_amount_paid_via_settlement_activities = 33.33
    #        adjusted_total_share_of_expenses = 33.33 - 33.33 = 0.00
    #        total_generic_settlements_paid = 0.00
    #        total_generic_settlements_received = 0.00
    #        Net Balance = (0.00 + 0.00) - (0.00 + 0.00) = 0.00
    assert user_balances[user2.id]["total_paid_for_expenses"] == "0.00"
    assert user_balances[user2.id]["total_share_of_expenses"] == "0.00" # Initial 33.33 minus 33.33 from activity
    assert user_balances[user2.id]["total_settlements_paid"] == "0.00" # The activity is not a generic settlement
    assert user_balances[user2.id]["total_settlements_received"] == "0.00"
    assert user_balances[user2.id]["net_balance"] == "0.00"

    # User3: Owes their share of 33.34.
    #        total_paid_for_expenses = 0.00
    #        initial_total_share_of_expenses = 33.34
    #        total_amount_paid_via_settlement_activities = 0.00
    #        adjusted_total_share_of_expenses = 33.34
    #        total_generic_settlements_paid = 0.00
    #        total_generic_settlements_received = 0.00
    #        Net Balance = (0.00 + 0.00) - (33.34 + 0.00) = -33.34
    assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
    assert user_balances[user3.id]["total_share_of_expenses"] == "33.34"
    assert user_balances[user3.id]["total_settlements_paid"] == "0.00"
    assert user_balances[user3.id]["total_settlements_received"] == "0.00"
    assert user_balances[user3.id]["net_balance"] == "-33.34"

    # Verify sum of net balances is zero
    sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
    assert sum_net_balances == Decimal("0.00")

    # Suggested settlements should reflect the corrected balances: User3 pays User1 33.34
    suggested_settlements = summary_data["suggested_settlements"]
    assert isinstance(suggested_settlements, list)
    
    if suggested_settlements: # Check if the list is not empty
        assert len(suggested_settlements) == 1
        settlement = suggested_settlements[0]
        assert settlement["from_user_id"] == user3.id
        assert settlement["to_user_id"] == user1.id
        assert settlement["amount"] == "33.34"
    else:
        # If all balances are zero, no settlements are suggested.
        # This case should not happen here as User3 owes User1.
        assert False, "Suggested settlements should not be empty in this scenario."
    
    # Ensure overall_total_expenses and overall_total_settlements are present in GroupBalanceSummary
    # These fields were added in a later version of the schema in the problem description.
    # For now, just ensure the response structure for user_balances and suggested_settlements is correct.
    # If these fields (`overall_total_expenses`, `overall_total_settlements`) are indeed part of the
    # GroupBalanceSummary model being used by the endpoint, the test should ideally check them.
    # However, the primary focus here is the UserBalanceDetail and resulting suggested settlements.
    # The fixture data does not include generic settlements, so overall_total_settlements would be 0.
    # overall_total_expenses would be 100.00
    assert "overall_total_expenses" in summary_data 
    assert "overall_total_settlements" in summary_data
    if "overall_total_expenses" in summary_data:
        assert summary_data["overall_total_expenses"] == "100.00" # From the single expense
    if "overall_total_settlements" in summary_data:
        assert summary_data["overall_total_settlements"] == "0.00" # No generic settlements in this test


@pytest.mark.asyncio
class TestEqualSplitExpenses:
    async def test_equal_split_basic(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Basic Equal Split Dinner",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EQUAL.value,
            # For EQUAL split, splits_in is typically not provided, 
            # relies on group members.
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "100.00"
        assert created_expense_data["split_type"] == SplitTypeEnum.EQUAL.value
        assert created_expense_data["paid_by_user_id"] == user1.id
        
        # Verify splits
        expense_id = created_expense_data["id"]
        # Fetch expense details to check splits (assuming GET endpoint returns splits)
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 3
        
        # Expected: 33.33, 33.33, 33.34 (sorted by user_id, assuming user1, user2, user3 have ascending ids)
        # The order of users in test_group_api_costs is user1, user2, user3.
        # Their IDs will be ascending.
        expected_amounts = ["33.33", "33.33", "33.34"] 
        user_ids = sorted([user1.id, user2.id, user3.id])

        for i, split in enumerate(splits):
            assert split["user_id"] == user_ids[i]
            assert split["owed_amount"] == expected_amounts[i]
            assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("100.00")

    async def test_equal_split_divisible(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any], # For headers
        test_group_api_costs_2_users: Dict[str, Any], 
    ):
        user1_headers = test_user1_api_costs["headers"] # Payer will be user1
        
        group_data = test_group_api_costs_2_users
        group_object = group_data["group"]
        group_members = group_data["members"]
        assert len(group_members) == 2
        
        # Ensure user1 (whose headers are used) is part of this group for valid payment
        payer = test_user1_api_costs["user"]
        assert any(member.id == payer.id for member in group_members), "Payer (user1) must be in the 2-user group"

        expense_payload = {
            "description": "Divisible Equal Split Lunch",
            "total_amount": "50.00",
            "currency": "USD",
            "group_id": group_object.id,
            "paid_by_user_id": payer.id, 
            "split_type": SplitTypeEnum.EQUAL.value,
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "50.00"
        assert created_expense_data["split_type"] == SplitTypeEnum.EQUAL.value
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()
        
        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 2
        
        user_ids_in_group = sorted([m.id for m in group_members])

        for split in splits:
            assert split["owed_amount"] == "25.00"
            assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
            assert split["user_id"] in user_ids_in_group
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("50.00")

    async def test_equal_split_with_remainder_distribution(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession, 
        test_user1_api_costs: Dict[str, Any], # For headers
        test_group_api_costs_4_users: Dict[str, Any], 
    ):
        user1_headers = test_user1_api_costs["headers"] # Payer will be user1

        group_data = test_group_api_costs_4_users
        group_object = group_data["group"]
        group_members = group_data["members"]
        assert len(group_members) == 4

        # Ensure user1 (whose headers are used) is part of this group for valid payment
        payer = test_user1_api_costs["user"]
        assert any(member.id == payer.id for member in group_members), "Payer (user1) must be in the 4-user group"

        expense_payload = {
            "description": "Remainder Test",
            "total_amount": "101.00",
            "currency": "USD",
            "group_id": group_object.id,
            "paid_by_user_id": payer.id,
            "split_type": SplitTypeEnum.EQUAL.value,
        }
        
        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers, # User1 is the payer
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "101.00"
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 4
        
        # 101.00 / 4 = 25.25. Pennies should be distributed.
        # The penny distribution (e.g. first N users get an extra penny) depends on implementation.
        # Let's assume it's 25.25 for all due to ROUND_HALF_UP or similar for base, and then pennies added.
        # Or, 3 users get 25.25 and one gets 25.25, or some get X and some X+0.01
        # The crud function uses: base_share = (total_amount / num_splits).quantize(Decimal("0.01"), rounding=ROUND_DOWN)
        # Then distributes pennies. So for 101/4 = 25.25, ROUND_DOWN is 25.25. No pennies to distribute.
        # All should be 25.25.

        # If amount was 100.00 / 3 = 33.333...
        # base_share = 33.33. Remaining = 0.01. First user gets 33.34.
        # For 101.00 / 4 = 25.25.
        # base_share = 25.25. total_rounded = 25.25 * 4 = 101.00. Remainder = 0.
        # So all should be 25.25.
        
        for split in splits:
            assert split["owed_amount"] == "25.25"
            assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("101.00")

    async def test_equal_split_single_user_in_group(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]

        # Create a new group with only user1
        single_user_group = Group(name="Single User Group for Equal Split", created_by_id=user1.id)
        db_session.add(single_user_group)
        await db_session.flush()
        
        from app.models import UserGroup # Ensure import
        user_group_assoc = UserGroup(user_id=user1.id, group_id=single_user_group.id, role=UserRoleEnum.owner)
        db_session.add(user_group_assoc)
        await db_session.commit()
        await db_session.refresh(single_user_group)

        expense_payload = {
            "description": "Single User Equal Split",
            "total_amount": "75.00",
            "currency": "USD",
            "group_id": single_user_group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EQUAL.value,
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "75.00"
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = expense_details["splits"]
        assert len(splits) == 1
        
        split = splits[0]
        assert split["user_id"] == user1.id
        assert split["owed_amount"] == "75.00"
        assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("75.00")

# Need to import selectinload for the group member fetching in divisible/remainder tests
from sqlalchemy.orm import selectinload 
# Need AsyncSession for type hinting db_session
from sqlalchemy.ext.asyncio import AsyncSession


@pytest.mark.asyncio
class TestExactAmountsSplitExpenses:
    async def test_exact_amounts_basic(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Exact Amounts Split Event",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value,
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "20.00"},
                {"user_id": user2.id, "owed_amount": "30.00"},
                {"user_id": user3.id, "owed_amount": "50.00"},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "100.00"
        assert created_expense_data["split_type"] == SplitTypeEnum.EXACT_AMOUNTS.value
        assert created_expense_data["paid_by_user_id"] == user1.id
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 3
        
        expected_splits = {
            user1.id: "20.00",
            user2.id: "30.00",
            user3.id: "50.00",
        }

        for split in splits:
            assert split["owed_amount"] == expected_splits[split["user_id"]]
            assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("100.00")

    async def test_exact_amounts_validation_sum_mismatch(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Sum Mismatch Test",
            "total_amount": "100.00", # Total amount
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value,
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "20.00"},
                {"user_id": user2.id, "owed_amount": "30.00"}, # Sums to 50, not 100
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        # Expecting 400 due to sum mismatch from crud_expense validation
        assert response.status_code == 400 
        error_detail = response.json()["detail"]
        assert "Sum of split amounts must equal total expense amount" in error_detail


    async def test_exact_amounts_validation_negative_amount(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Negative Amount Test",
            "total_amount": "10.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value,
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "-5.00"}, # Negative amount
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        # Pydantic validation on SplitCreate should catch negative owed_amount
        assert response.status_code == 422 
        # Example check, details might vary based on Pydantic error formatting
        assert "value_error" in response.json()["detail"][0]["type"] 
        assert "must be greater than 0" in response.json()["detail"][0]["msg"]


    async def test_exact_amounts_validation_missing_user_in_split(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        group = test_group_api_costs
        non_existent_user_id = 99999 

        expense_payload = {
            "description": "Missing User in Split Test",
            "total_amount": "50.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value,
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "25.00"},
                {"user_id": non_existent_user_id, "owed_amount": "25.00"},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        # crud_expense._validate_and_get_users_for_splitting should catch this
        assert response.status_code == 404 
        error_detail = response.json()["detail"]
        assert f"User with ID {non_existent_user_id} not found or not part of group {group.id}" in error_detail


@pytest.mark.asyncio
class TestPercentageSplitExpenses:
    async def test_percentage_basic(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Basic Percentage Split",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.PERCENTAGE.value,
            "splits_in": [
                {"user_id": user1.id, "percentage": "25.00"},
                {"user_id": user2.id, "percentage": "25.00"},
                {"user_id": user3.id, "percentage": "50.00"},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "100.00"
        assert created_expense_data["split_type"] == SplitTypeEnum.PERCENTAGE.value
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 3
        
        expected_amounts = {
            user1.id: "25.00",
            user2.id: "25.00",
            user3.id: "50.00",
        }
        for split in splits:
            assert split["owed_amount"] == expected_amounts[split["user_id"]]
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("100.00")

    async def test_percentage_with_rounding(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Percentage Split with Rounding",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.PERCENTAGE.value,
            "splits_in": [
                {"user_id": user1.id, "percentage": "33.33"},
                {"user_id": user2.id, "percentage": "33.33"},
                {"user_id": user3.id, "percentage": "33.34"}, # Sums to 100%
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 3

        # Based on current penny distribution (first user gets remainder)
        # User1: 100 * 0.3333 = 33.33
        # User2: 100 * 0.3333 = 33.33
        # User3: 100 * 0.3334 = 33.34
        # Sum = 100.00. The crud logic for percentage splits distributes pennies.
        
        user_ids_sorted = sorted([user1.id, user2.id, user3.id])
        expected_amounts = {
            user_ids_sorted[0]: "33.33", # User1
            user_ids_sorted[1]: "33.33", # User2
            user_ids_sorted[2]: "33.34", # User3
        }
        # Check if penny went to the one with higher percentage if not perfectly distributed by input already
        # The current logic in `_create_percentage_splits` calculates shares and then distributes pennies
        # similar to equal split. It doesn't necessarily assign the remainder based on the input percentages
        # if those percentages themselves don't perfectly sum to 100.00 after calculation.
        # However, if percentages sum to 100, the calculated amounts should match.
        # Let's verify the actual amounts from the test:
        # User1 gets 33.33. User2 gets 33.33. User3 gets 33.34.
        # This matches the input percentages directly applied.
        # The penny distribution logic in `_distribute_remainder` might adjust if the sum of calculated shares is off.

        for split in splits:
             assert split["owed_amount"] == expected_amounts[split["user_id"]]
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("100.00")

    async def test_percentage_validation_sum_not_100(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Percentage Sum Not 100",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.PERCENTAGE.value,
            "splits_in": [
                {"user_id": user1.id, "percentage": "20.00"},
                {"user_id": user2.id, "percentage": "30.00"}, # Sums to 50%
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 400
        error_detail = response.json()["detail"]
        assert "Percentages must sum to 100" in error_detail

    async def test_percentage_validation_invalid_percentage(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Invalid Percentage Value",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.PERCENTAGE.value,
            "splits_in": [
                {"user_id": user1.id, "percentage": "110.00"}, # Invalid percentage
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 422 # Pydantic validation on percentage value (0-100)
        # Example check, details might vary
        assert "value_error" in response.json()["detail"][0]["type"] 
        assert "Percentage must be between 0 and 100" in response.json()["detail"][0]["msg"]


@pytest.mark.asyncio
class TestSharesSplitExpenses:
    async def test_shares_basic(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Basic Shares Split",
            "total_amount": "60.00", # Total 6 shares (1+2+3), 60/6 = 10 per share
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.SHARES.value,
            "splits_in": [
                {"user_id": user1.id, "shares": 1},
                {"user_id": user2.id, "shares": 2},
                {"user_id": user3.id, "shares": 3},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "60.00"
        assert created_expense_data["split_type"] == SplitTypeEnum.SHARES.value
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 3
        
        expected_amounts = {
            user1.id: "10.00", # 1 share * 10
            user2.id: "20.00", # 2 shares * 10
            user3.id: "30.00", # 3 shares * 10
        }
        for split in splits:
            assert split["owed_amount"] == expected_amounts[split["user_id"]]
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("60.00")

    async def test_shares_with_rounding(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Shares Split with Rounding",
            "total_amount": "100.00", # Total 3 shares (1+1+1)
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.SHARES.value,
            "splits_in": [
                {"user_id": user1.id, "shares": 1},
                {"user_id": user2.id, "shares": 1},
                {"user_id": user3.id, "shares": 1},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        assert len(splits) == 3
        
        # 100.00 / 3 shares = 33.333... per share.
        # Expected: 33.33, 33.33, 33.34 (penny distribution applies)
        user_ids_sorted = sorted([user1.id, user2.id, user3.id])
        expected_amounts = {
            user_ids_sorted[0]: "33.33",
            user_ids_sorted[1]: "33.33",
            user_ids_sorted[2]: "33.34",
        }
        for split in splits:
            assert split["owed_amount"] == expected_amounts[split["user_id"]]
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("100.00")

    async def test_shares_validation_zero_total_shares(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Zero Total Shares",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.SHARES.value,
            "splits_in": [
                {"user_id": user1.id, "shares": 0},
                {"user_id": user2.id, "shares": 0},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 400
        error_detail = response.json()["detail"]
        assert "Total shares must be greater than zero" in error_detail

    async def test_shares_validation_negative_shares(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        group = test_group_api_costs

        expense_payload = {
            "description": "Invalid Negative Shares",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.SHARES.value,
            "splits_in": [
                {"user_id": user1.id, "shares": -1},
            ],
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 422 # Pydantic validation on shares value (>=0)
        # Example check, details might vary
        assert "value_error" in response.json()["detail"][0]["type"]
        assert "Shares must be non-negative" in response.json()["detail"][0]["msg"] # Adjusted expected message


@pytest.mark.asyncio
class TestItemBasedSplitExpenses:
    @pytest.fixture
    async def test_list_for_item_based_split(
        self,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ) -> ListModel:
        user1 = test_user1_api_costs["user"]
        group = test_group_api_costs
        
        shopping_list = ListModel(
            name="Shopping List for Item-Based Split",
            group_id=group.id,
            created_by_id=user1.id,
        )
        db_session.add(shopping_list)
        await db_session.commit()
        await db_session.refresh(shopping_list)
        return shopping_list

    async def test_item_based_split_basic(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
        test_list_for_item_based_split: ListModel,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        shopping_list = test_list_for_item_based_split

        # Add items to the list
        items_data = [
            {"description": "Item 1", "price": Decimal("10.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
            {"description": "Item 2", "price": Decimal("20.00"), "added_by_user_id": user2.id, "list_id": shopping_list.id},
            {"description": "Item 3", "price": Decimal("30.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
        ]
        for item_data in items_data:
            item = ItemModel(**item_data)
            db_session.add(item)
        await db_session.commit()

        expense_payload = {
            "description": "Item-Based Split Dinner",
            "total_amount": "60.00", # Sum of item prices (10+20+30)
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "list_id": shopping_list.id,
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "60.00"
        assert created_expense_data["split_type"] == SplitTypeEnum.ITEM_BASED.value
        assert created_expense_data["paid_by_user_id"] == user1.id
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = sorted(expense_details["splits"], key=lambda s: s["user_id"])
        # User3 is in the group but added no items, so should not have a split.
        assert len(splits) == 2 
        
        expected_splits = {
            user1.id: "40.00", # 10 + 30
            user2.id: "20.00",
        }

        for split in splits:
            assert split["owed_amount"] == expected_splits[split["user_id"]]
            assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("60.00")

    async def test_item_based_split_single_user_adds_all_items(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group, # Group contains user1, user2, user3
        test_list_for_item_based_split: ListModel,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        shopping_list = test_list_for_item_based_split

        items_data = [
            {"description": "Item A", "price": Decimal("15.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
            {"description": "Item B", "price": Decimal("25.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
        ]
        for item_data in items_data:
            item = ItemModel(**item_data)
            db_session.add(item)
        await db_session.commit()

        expense_payload = {
            "description": "Single User All Items",
            "total_amount": "40.00",
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "list_id": shopping_list.id,
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "40.00"
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = expense_details["splits"]
        assert len(splits) == 1
        
        split = splits[0]
        assert split["user_id"] == user1.id
        assert split["owed_amount"] == "40.00"
        assert split["status"] == ExpenseSplitStatusEnum.unpaid.value
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("40.00")

    async def test_item_based_split_with_zero_price_items(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any], # User2 adds zero price item
        test_group_api_costs: Group,
        test_list_for_item_based_split: ListModel,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        shopping_list = test_list_for_item_based_split

        items_data = [
            {"description": "Item X1", "price": Decimal("10.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
            {"description": "Item X2", "price": Decimal("0.00"), "added_by_user_id": user2.id, "list_id": shopping_list.id},
            {"description": "Item X3", "price": Decimal("5.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
        ]
        for item_data in items_data:
            item = ItemModel(**item_data)
            db_session.add(item)
        await db_session.commit()

        expense_payload = {
            "description": "Zero Price Items Test",
            "total_amount": "15.00", # 10 + 5
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "list_id": shopping_list.id,
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "15.00"
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = expense_details["splits"]
        assert len(splits) == 1 # Only User1 has splits from priced items
        
        split = splits[0]
        assert split["user_id"] == user1.id
        assert split["owed_amount"] == "15.00"
        
        sum_of_splits = sum(Decimal(s["owed_amount"]) for s in splits)
        assert sum_of_splits == Decimal("15.00")

    async def test_item_based_split_list_with_no_priced_items(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
        test_list_for_item_based_split: ListModel,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        shopping_list = test_list_for_item_based_split

        items_data = [
            {"description": "NoPrice 1", "price": Decimal("0.00"), "added_by_user_id": user1.id, "list_id": shopping_list.id},
            {"description": "NoPrice 2", "added_by_user_id": user2.id, "list_id": shopping_list.id}, # price is None
        ]
        for item_data in items_data:
            item = ItemModel(**item_data)
            db_session.add(item)
        await db_session.commit()

        expense_payload = {
            "description": "No Priced Items Test",
            "total_amount": "0.00", # This will be the sum from items
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "list_id": shopping_list.id,
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        # crud_expense.py: _create_item_based_splits raises ValueError if no relevant items or total is zero.
        # This translates to a 400 error.
        assert response.status_code == 400
        error_detail = response.json()["detail"]
        assert "No items with a positive price found for this list to create an item-based split" in error_detail \
            or "Total amount for item-based split must be greater than zero" in error_detail

    async def test_item_based_split_linked_to_specific_item(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
        test_list_for_item_based_split: ListModel,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        shopping_list = test_list_for_item_based_split

        item_a = ItemModel(description="Item A", price=Decimal("50.00"), added_by_user_id=user1.id, list_id=shopping_list.id)
        item_b = ItemModel(description="Item B", price=Decimal("70.00"), added_by_user_id=user2.id, list_id=shopping_list.id)
        db_session.add_all([item_a, item_b])
        await db_session.commit()
        await db_session.refresh(item_a)
        await db_session.refresh(item_b)

        # Scenario 1: Correct amount linked to item_a
        expense_payload_item_a = {
            "description": "Expense for Item A",
            "total_amount": "50.00",
            "currency": "USD",
            "group_id": test_group_api_costs.id, # Group ID is still relevant for context
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "item_id": item_a.id, # Linking to specific item
        }

        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload_item_a,
            headers=user1_headers,
        )
        assert response.status_code == 201
        created_expense_data = response.json()

        assert created_expense_data["total_amount"] == "50.00"
        assert created_expense_data["item_id"] == item_a.id
        
        expense_id = created_expense_data["id"]
        detail_response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=user1_headers)
        assert detail_response.status_code == 200
        expense_details = detail_response.json()

        splits = expense_details["splits"]
        assert len(splits) == 1
        assert splits[0]["user_id"] == user1.id
        assert splits[0]["owed_amount"] == "50.00"

        # Scenario 2: Mismatched total_amount for item_a
        expense_payload_item_a_mismatch = {
            "description": "Expense for Item A Mismatch",
            "total_amount": "40.00", # Incorrect amount
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "item_id": item_a.id,
        }
        response_mismatch = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload_item_a_mismatch,
            headers=user1_headers,
        )
        # crud_expense.py validation: "Total amount for item-based split on a specific item must match item's price."
        assert response_mismatch.status_code == 400
        assert "must match item's price" in response_mismatch.json()["detail"]

    async def test_item_based_split_validation_list_not_found(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        non_existent_list_id = 9999

        expense_payload = {
            "description": "List Not Found Test",
            "total_amount": "10.00",
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "list_id": non_existent_list_id,
        }
        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        # crud_expense.py raises ListNotFoundError if list_id is provided but not found.
        assert response.status_code == 404
        assert f"List with ID {non_existent_list_id} not found" in response.json()["detail"]

    async def test_item_based_split_validation_total_amount_mismatch_with_list_items(
        self,
        client: httpx.AsyncClient,
        db_session: AsyncSession,
        test_user1_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
        test_list_for_item_based_split: ListModel,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        shopping_list = test_list_for_item_based_split

        # Items sum to 50.00
        item1 = ItemModel(description="Item C1", price=Decimal("20.00"), added_by_user_id=user1.id, list_id=shopping_list.id)
        item2 = ItemModel(description="Item C2", price=Decimal("30.00"), added_by_user_id=user1.id, list_id=shopping_list.id)
        db_session.add_all([item1, item2])
        await db_session.commit()

        expense_payload = {
            "description": "Total Amount Mismatch with List Items",
            "total_amount": "45.00", # Mismatched amount
            "currency": "USD",
            "group_id": test_group_api_costs.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.ITEM_BASED.value,
            "list_id": shopping_list.id,
        }
        response = await client.post(
            f"{settings.API_V1_STR}/expenses/",
            json=expense_payload,
            headers=user1_headers,
        )
        # crud_expense.py validation: "Total amount for item-based split must equal the sum of relevant item prices from the list."
        assert response.status_code == 400
        assert "must equal the sum of relevant item prices" in response.json()["detail"]

# Need to import ListModel and ItemModel
from app.models import ListModel, ItemModel, Settlement as SettlementModel


@pytest.mark.asyncio
class TestGroupBalanceSummaryCalculations:
    async def _get_expense_splits(self, client: httpx.AsyncClient, expense_id: int, headers: Dict[str, str]) -> List[Dict[str, Any]]:
        """Helper to fetch expense details and return its splits."""
        response = await client.get(f"{settings.API_V1_STR}/expenses/{expense_id}", headers=headers)
        response.raise_for_status()
        return response.json()["splits"]

    async def _create_settlement_activity(
        self, 
        client: httpx.AsyncClient, 
        expense_split_id: int, 
        paid_by_user_id: int, 
        amount: str, 
        headers: Dict[str, str]
    ):
        """Helper to create a settlement activity."""
        payload = {
            "expense_split_id": expense_split_id,
            "paid_by_user_id": paid_by_user_id,
            "amount_paid": amount,
        }
        response = await client.post(
            f"{settings.API_V1_STR}/expense_splits/{expense_split_id}/settle",
            json=payload,
            headers=headers,
        )
        response.raise_for_status()
        return response.json()

    async def _create_generic_settlement(
        self,
        client: httpx.AsyncClient,
        group_id: int,
        paid_by_user_id: int,
        paid_to_user_id: int,
        amount: str,
        headers: Dict[str, str]
    ):
        """Helper to create a generic settlement."""
        payload = {
            "group_id": group_id,
            "paid_by_user_id": paid_by_user_id,
            "paid_to_user_id": paid_to_user_id,
            "amount": amount,
            "description": "Generic settlement"
        }
        response = await client.post(
            f"{settings.API_V1_STR}/settlements/",
            json=payload,
            headers=headers
        )
        response.raise_for_status()
        return response.json()


    async def test_balance_summary_no_settlement_activities(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group, # Group with U1, U2, U3
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        # Create an expense: User1 paid 100, split EQUAL among User1, User2, User3
        expense_payload = {
            "description": "Dinner - No Settlements",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EQUAL.value,
        }
        response = await client.post(
            f"{settings.API_V1_STR}/expenses/", json=expense_payload, headers=user1_headers
        )
        assert response.status_code == 201
        
        # Get balance summary
        summary_response = await client.get(
            f"{settings.API_V1_STR}/groups/{group.id}/balance-summary", headers=user1_headers
        )
        assert summary_response.status_code == 200
        summary_data = summary_response.json()

        user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}

        # Expected:
        # User1: paid 100, share 33.33. Net: 100 - 33.33 = +66.67
        # User2: paid 0, share 33.33. Net: 0 - 33.33 = -33.33
        # User3: paid 0, share 33.34. Net: 0 - 33.34 = -33.34
        # Sum = 0

        # User1 assertions
        assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
        assert user_balances[user1.id]["total_share_of_expenses"] == "33.33" # Adjusted share is initial share here
        assert user_balances[user1.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user1.id]["total_settlements_received"] == "0.00"
        assert user_balances[user1.id]["net_balance"] == "66.67"

        # User2 assertions
        assert user_balances[user2.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user2.id]["total_share_of_expenses"] == "33.33"
        assert user_balances[user2.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user2.id]["total_settlements_received"] == "0.00"
        assert user_balances[user2.id]["net_balance"] == "-33.33"

        # User3 assertions
        assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user3.id]["total_share_of_expenses"] == "33.34"
        assert user_balances[user3.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user3.id]["total_settlements_received"] == "0.00"
        assert user_balances[user3.id]["net_balance"] == "-33.34"
        
        # Sum of net balances
        sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
        assert sum_net_balances == Decimal("0.00")

        # Suggested settlements
        # User2 pays User1 33.33. User3 pays User1 33.34.
        suggested = sorted(summary_data["suggested_settlements"], key=lambda s: s["from_user_id"])
        assert len(suggested) == 2
        
        assert suggested[0]["from_user_id"] == user2.id
        assert suggested[0]["to_user_id"] == user1.id
        assert suggested[0]["amount"] == "33.33"
        
        assert suggested[1]["from_user_id"] == user3.id
        assert suggested[1]["to_user_id"] == user1.id
        assert suggested[1]["amount"] == "33.34"

    async def test_balance_summary_multiple_settlement_activities_different_expenses(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group, # Group with U1, U2, U3
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user2_headers = test_user2_api_costs["headers"]
        user3 = test_user3_api_costs["user"]
        user3_headers = test_user3_api_costs["headers"] # For User3 to make a settlement
        group = test_group_api_costs

        # Expense 1: User1 pays 100, split U1, U2 (50, 50)
        # Forcing specific users in EQUAL split by creating a temporary specific split definition
        exp1_payload = {
            "description": "Expense 1 - U1, U2",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value, # Use EXACT to specify participants easily
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "50.00"},
                {"user_id": user2.id, "owed_amount": "50.00"},
            ]
        }
        exp1_response = await client.post(
            f"{settings.API_V1_STR}/expenses/", json=exp1_payload, headers=user1_headers
        )
        assert exp1_response.status_code == 201
        expense1_id = exp1_response.json()["id"]

        exp1_splits = await self._get_expense_splits(client, expense1_id, user1_headers)
        user2_exp1_split = next(s for s in exp1_splits if s["user_id"] == user2.id)
        
        # User2 pays their 50 for Expense 1 via SA
        await self._create_settlement_activity(
            client, user2_exp1_split["id"], user2.id, "50.00", user2_headers
        )

        # Expense 2: User2 pays 60, split U2, U3 (30, 30)
        exp2_payload = {
            "description": "Expense 2 - U2, U3",
            "total_amount": "60.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user2.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value, # Use EXACT
            "splits_in": [
                {"user_id": user2.id, "owed_amount": "30.00"},
                {"user_id": user3.id, "owed_amount": "30.00"},
            ]
        }
        exp2_response = await client.post(
            f"{settings.API_V1_STR}/expenses/", json=exp2_payload, headers=user2_headers # User2 creates
        )
        assert exp2_response.status_code == 201
        expense2_id = exp2_response.json()["id"]

        exp2_splits = await self._get_expense_splits(client, expense2_id, user2_headers)
        user3_exp2_split = next(s for s in exp2_splits if s["user_id"] == user3.id)

        # User3 pays their 30 for Expense 2 via SA
        await self._create_settlement_activity(
            client, user3_exp2_split["id"], user3.id, "30.00", user3_headers
        )
        
        # Get balance summary
        summary_response = await client.get(
            f"{settings.API_V1_STR}/groups/{group.id}/balance-summary", headers=user1_headers
        )
        assert summary_response.status_code == 200
        summary_data = summary_response.json()
        user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}

        # Expected balances: All should be 0.00
        # User1: Exp1(paid 100, share 50, U2 SA covered their part). Exp2(not involved). Net = 0.
        #        total_paid_for_expenses = 100.00
        #        initial_total_share_of_expenses = 50.00 (from Exp1)
        #        total_amount_paid_via_settlement_activities = 0 (U1 made no SA payments)
        #        adjusted_total_share_of_expenses = 50.00
        #        net_balance = (100 + 0) - (50 + 0) = 50. This logic is for U1 in isolation.
        #        Considering U2 paid U1 50, U1 is 0.
        # User2: Exp1(share 50, paid 50 SA). Exp2(paid 60, share 30, U3 SA covered their part). Net = 0.
        #        total_paid_for_expenses = 60.00 (from Exp2)
        #        initial_total_share_of_expenses = 50.00 (Exp1) + 30.00 (Exp2) = 80.00
        #        total_amount_paid_via_settlement_activities = 50.00 (for Exp1)
        #        adjusted_total_share_of_expenses = 80.00 - 50.00 = 30.00
        #        net_balance = (60 + 0) - (30 + 0) = 30. This is if U3 hadn't paid U2.
        #        Considering U3 paid U2 30, U2 is 0.
        # User3: Exp1(not involved). Exp2(share 30, paid 30 SA). Net = 0.
        #        total_paid_for_expenses = 0.00
        #        initial_total_share_of_expenses = 30.00 (from Exp2)
        #        total_amount_paid_via_settlement_activities = 30.00 (for Exp2)
        #        adjusted_total_share_of_expenses = 30.00 - 30.00 = 0.00
        #        net_balance = (0 + 0) - (0 + 0) = 0.00

        # User1 assertions
        assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
        assert user_balances[user1.id]["total_share_of_expenses"] == "50.00" 
        assert user_balances[user1.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user1.id]["total_settlements_received"] == "0.00"
        assert user_balances[user1.id]["net_balance"] == "0.00"

        # User2 assertions
        assert user_balances[user2.id]["total_paid_for_expenses"] == "60.00"
        assert user_balances[user2.id]["total_share_of_expenses"] == "30.00" # (50 from E1 - 50 SA) + (30 from E2) = 30
        assert user_balances[user2.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user2.id]["total_settlements_received"] == "0.00"
        assert user_balances[user2.id]["net_balance"] == "0.00"

        # User3 assertions
        assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user3.id]["total_share_of_expenses"] == "0.00" # (30 from E2 - 30 SA) = 0
        assert user_balances[user3.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user3.id]["total_settlements_received"] == "0.00"
        assert user_balances[user3.id]["net_balance"] == "0.00"
        
        sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
        assert sum_net_balances == Decimal("0.00")

        # Suggested settlements: Should be empty
        suggested = summary_data["suggested_settlements"]
        assert len(suggested) == 0

    async def test_balance_summary_interaction_with_generic_settlements(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user2_headers = test_user2_api_costs["headers"]
        user3 = test_user3_api_costs["user"]
        user3_headers = test_user3_api_costs["headers"] # For User3 to make a generic settlement
        group = test_group_api_costs

        # Expense: User1 paid 100, split EQUAL among User1, User2, User3
        expense_payload = {
            "description": "Dinner - SA and Generic Settlement",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EQUAL.value,
        }
        exp_response = await client.post(
            f"{settings.API_V1_STR}/expenses/", json=expense_payload, headers=user1_headers
        )
        assert exp_response.status_code == 201
        expense_id = exp_response.json()["id"]

        splits = await self._get_expense_splits(client, expense_id, user1_headers)
        user2_split = next(s for s in splits if s["user_id"] == user2.id)
        
        # User2 pays their full share (33.33) via SettlementActivity
        await self._create_settlement_activity(
            client, user2_split["id"], user2.id, user2_split["owed_amount"], user2_headers
        )

        # User3 then pays User1 30.00 via a generic Settlement
        await self._create_generic_settlement(
            client, group.id, user3.id, user1.id, "30.00", user3_headers
        )
        
        summary_response = await client.get(
            f"{settings.API_V1_STR}/groups/{group.id}/balance-summary", headers=user1_headers
        )
        assert summary_response.status_code == 200
        summary_data = summary_response.json()
        user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}

        # Expected balances:
        # User1: Paid 100. Share 33.33. U2 SA clears U2's debt to U1. U3 pays U1 30 (Generic).
        #        total_paid_for_expenses = 100.00
        #        total_share_of_expenses = 33.33
        #        total_settlements_paid (generic) = 0.00
        #        total_settlements_received (generic) = 30.00
        #        Net: (100 + 30) - (33.33 + 0) = 130 - 33.33 = 96.67. This is U1's position.
        #        Overall group balance: U1 is owed 3.34 by U3. So U1 net is +3.34.
        # User2: Share 33.33. Paid 33.33 via SA. Net = 0.
        #        total_paid_for_expenses = 0.00
        #        total_share_of_expenses = 0.00 (33.33 initial - 33.33 SA)
        #        total_settlements_paid (generic) = 0.00
        #        total_settlements_received (generic) = 0.00
        #        Net: 0.00
        # User3: Share 33.34. Paid 30.00 via Generic Settlement to U1.
        #        total_paid_for_expenses = 0.00
        #        total_share_of_expenses = 33.34
        #        total_settlements_paid (generic) = 30.00
        #        total_settlements_received (generic) = 0.00
        #        Net: (0 + 0) - (33.34 + 30.00) = -63.34. This is U3's position.
        #        Overall group balance: U3 owes U1 3.34. So U3 net is -3.34.
        # Sum = 3.34 + 0 - 3.34 = 0

        # User1 assertions
        assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
        assert user_balances[user1.id]["total_share_of_expenses"] == "33.33"
        assert user_balances[user1.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user1.id]["total_settlements_received"] == "30.00"
        assert user_balances[user1.id]["net_balance"] == "3.34" # (100+30) - (33.33) = 96.67 ... error in manual calc for net.
                                                                # Net = (paid_exp + generic_recv) - (adj_share + generic_paid)
                                                                # U1: (100 + 30) - (33.33 + 0) = 96.67. This is if there were no other users.
                                                                # The suggested settlement re-balances this.
                                                                # Net effect: U1 paid 100 for an expense. U1's share was 33.33. U2 paid their 33.33 share (to U1).
                                                                # U3's share was 33.34. U3 paid U1 30.00. So U3 still owes U1 3.34.
                                                                # So U1 is +3.34.

        # User2 assertions
        assert user_balances[user2.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user2.id]["total_share_of_expenses"] == "0.00"
        assert user_balances[user2.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user2.id]["total_settlements_received"] == "0.00"
        assert user_balances[user2.id]["net_balance"] == "0.00"

        # User3 assertions
        assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user3.id]["total_share_of_expenses"] == "33.34"
        assert user_balances[user3.id]["total_settlements_paid"] == "30.00"
        assert user_balances[user3.id]["total_settlements_received"] == "0.00"
        assert user_balances[user3.id]["net_balance"] == "-3.34" # (0+0) - (33.34+30) = -63.34.
                                                                 # U3 owes 33.34 for expense. U3 paid U1 30. So U3 owes 3.34.
        
        sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
        assert sum_net_balances == Decimal("0.00")

        # Suggested settlements: User3 pays User1 3.34
        suggested = summary_data["suggested_settlements"]
        assert len(suggested) == 1
        
        assert suggested[0]["from_user_id"] == user3.id
        assert suggested[0]["to_user_id"] == user1.id
        assert suggested[0]["amount"] == "3.34"

    async def test_balance_summary_complex_scenario(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user2_headers = test_user2_api_costs["headers"]
        user3 = test_user3_api_costs["user"]
        user3_headers = test_user3_api_costs["headers"]
        group = test_group_api_costs

        # Expense 1: User1 pays 100. Split: U1=20, U2=40, U3=40.
        exp1_payload = {
            "description": "Complex Exp1", "total_amount": "100.00", "currency": "USD",
            "group_id": group.id, "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value,
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "20.00"},
                {"user_id": user2.id, "owed_amount": "40.00"},
                {"user_id": user3.id, "owed_amount": "40.00"},
            ]
        }
        exp1_res = await client.post(f"{settings.API_V1_STR}/expenses/", json=exp1_payload, headers=user1_headers)
        assert exp1_res.status_code == 201
        exp1_id = exp1_res.json()["id"]
        exp1_splits = await self._get_expense_splits(client, exp1_id, user1_headers)
        u2_exp1_split = next(s for s in exp1_splits if s["user_id"] == user2.id)
        u3_exp1_split = next(s for s in exp1_splits if s["user_id"] == user3.id)

        # Expense 2: User2 pays 50. Split: U1=10, U2=20, U3=20.
        exp2_payload = {
            "description": "Complex Exp2", "total_amount": "50.00", "currency": "USD",
            "group_id": group.id, "paid_by_user_id": user2.id,
            "split_type": SplitTypeEnum.EXACT_AMOUNTS.value,
            "splits_in": [
                {"user_id": user1.id, "owed_amount": "10.00"},
                {"user_id": user2.id, "owed_amount": "20.00"},
                {"user_id": user3.id, "owed_amount": "20.00"},
            ]
        }
        exp2_res = await client.post(f"{settings.API_V1_STR}/expenses/", json=exp2_payload, headers=user2_headers)
        assert exp2_res.status_code == 201
        exp2_id = exp2_res.json()["id"]
        exp2_splits = await self._get_expense_splits(client, exp2_id, user2_headers)
        u1_exp2_split = next(s for s in exp2_splits if s["user_id"] == user1.id)

        # Settlement Activities
        # User2 pays 10 towards their share of Exp1 via SA.
        await self._create_settlement_activity(client, u2_exp1_split["id"], user2.id, "10.00", user2_headers)
        # User3 pays 20 towards their share of Exp1 via SA.
        await self._create_settlement_activity(client, u3_exp1_split["id"], user3.id, "20.00", user3_headers)
        # User1 pays 5 towards their share of Exp2 via SA.
        await self._create_settlement_activity(client, u1_exp2_split["id"], user1.id, "5.00", user1_headers)

        # Get balance summary
        summary_response = await client.get(
            f"{settings.API_V1_STR}/groups/{group.id}/balance-summary", headers=user1_headers
        )
        assert summary_response.status_code == 200
        summary_data = summary_response.json()
        user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}
        
        # User1: total_paid_for_expenses=100. Initial Share (20+10)=30. SA paid by U1 for Exp2=5. Adjusted Share = 30-5=25. Net=+45
        assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
        assert user_balances[user1.id]["total_share_of_expenses"] == "25.00"
        assert user_balances[user1.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user1.id]["total_settlements_received"] == "0.00"
        assert user_balances[user1.id]["net_balance"] == "45.00"

        # User2: total_paid_for_expenses=50. Initial Share (40+20)=60. SA paid by U2 for Exp1=10. Adjusted Share = 60-10=50. Net=-5
        assert user_balances[user2.id]["total_paid_for_expenses"] == "50.00"
        assert user_balances[user2.id]["total_share_of_expenses"] == "50.00"
        assert user_balances[user2.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user2.id]["total_settlements_received"] == "0.00"
        assert user_balances[user2.id]["net_balance"] == "-5.00" # Corrected from 0 to -5 from manual calc

        # User3: total_paid_for_expenses=0. Initial Share (40+20)=60. SA paid by U3 for Exp1=20. Adjusted Share = 60-20=40. Net=-40
        assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user3.id]["total_share_of_expenses"] == "40.00"
        assert user_balances[user3.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user3.id]["total_settlements_received"] == "0.00"
        assert user_balances[user3.id]["net_balance"] == "-40.00"

        sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
        assert sum_net_balances == Decimal("0.00")

        # Suggested settlements: User2 pays User1 5.00, User3 pays User1 40.00
        suggested = sorted(summary_data["suggested_settlements"], key=lambda s: s["from_user_id"])
        assert len(suggested) == 2
        
        assert suggested[0]["from_user_id"] == user2.id
        assert suggested[0]["to_user_id"] == user1.id
        assert suggested[0]["amount"] == "5.00"
        
        assert suggested[1]["from_user_id"] == user3.id
        assert suggested[1]["to_user_id"] == user1.id
        assert suggested[1]["amount"] == "40.00"

    async def test_balance_summary_full_settlement_activity_one_user(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user2_headers = test_user2_api_costs["headers"] # For User2 to make a settlement
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        # Create an expense: User1 paid 100, split EQUAL among User1, User2, User3
        expense_payload = {
            "description": "Dinner - Full SA by User2",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EQUAL.value,
        }
        exp_response = await client.post(
            f"{settings.API_V1_STR}/expenses/", json=expense_payload, headers=user1_headers
        )
        assert exp_response.status_code == 201
        expense_id = exp_response.json()["id"]

        # Get splits to find User2's split_id
        splits = await self._get_expense_splits(client, expense_id, user1_headers)
        user2_split = next(s for s in splits if s["user_id"] == user2.id)
        assert user2_split["owed_amount"] == "33.33" # From EQUAL split

        # User2 pays their full share via SettlementActivity
        await self._create_settlement_activity(
            client, user2_split["id"], user2.id, user2_split["owed_amount"], user2_headers
        )
        
        # Get balance summary
        summary_response = await client.get(
            f"{settings.API_V1_STR}/groups/{group.id}/balance-summary", headers=user1_headers
        )
        assert summary_response.status_code == 200
        summary_data = summary_response.json()
        user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}

        # Expected:
        # User1: Paid 100. Initial Share 33.33. User2 paid their 33.33. U1 is now owed 33.34 by U3.
        #        total_paid_for_expenses = 100.00
        #        adjusted_total_share_of_expenses = 33.33 (own share, not affected by U2's SA payment)
        #        net_balance = (100.00 + 0) - (33.33 + 0) = 66.67. This is before considering who owes whom.
        #        The system calculates: User1 is owed 33.34 by User3. User2 is settled. So User1 is +33.34.
        # User2: Paid 0 for expenses. Initial Share 33.33. Paid 33.33 via SA.
        #        total_paid_for_expenses = 0.00
        #        adjusted_total_share_of_expenses = 33.33 - 33.33 = 0.00
        #        net_balance = (0 + 0) - (0 + 0) = 0.00
        # User3: Paid 0 for expenses. Initial Share 33.34. Paid 0.
        #        total_paid_for_expenses = 0.00
        #        adjusted_total_share_of_expenses = 33.34
        #        net_balance = (0 + 0) - (33.34 + 0) = -33.34
        # Sum = 0

        # User1 assertions
        assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
        assert user_balances[user1.id]["total_share_of_expenses"] == "33.33"
        assert user_balances[user1.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user1.id]["total_settlements_received"] == "0.00"
        assert user_balances[user1.id]["net_balance"] == "33.34"

        # User2 assertions
        assert user_balances[user2.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user2.id]["total_share_of_expenses"] == "0.00"
        assert user_balances[user2.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user2.id]["total_settlements_received"] == "0.00"
        assert user_balances[user2.id]["net_balance"] == "0.00"

        # User3 assertions
        assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user3.id]["total_share_of_expenses"] == "33.34"
        assert user_balances[user3.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user3.id]["total_settlements_received"] == "0.00"
        assert user_balances[user3.id]["net_balance"] == "-33.34"
        
        sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
        assert sum_net_balances == Decimal("0.00")

        # Suggested settlements: User3 pays User1 33.34
        suggested = summary_data["suggested_settlements"]
        assert len(suggested) == 1
        
        assert suggested[0]["from_user_id"] == user3.id
        assert suggested[0]["to_user_id"] == user1.id
        assert suggested[0]["amount"] == "33.34"

    async def test_balance_summary_partial_settlement_activity(
        self,
        client: httpx.AsyncClient,
        test_user1_api_costs: Dict[str, Any],
        test_user2_api_costs: Dict[str, Any],
        test_user3_api_costs: Dict[str, Any],
        test_group_api_costs: Group,
    ):
        user1 = test_user1_api_costs["user"]
        user1_headers = test_user1_api_costs["headers"]
        user2 = test_user2_api_costs["user"]
        user2_headers = test_user2_api_costs["headers"]
        user3 = test_user3_api_costs["user"]
        group = test_group_api_costs

        # Create an expense: User1 paid 100, split EQUAL among User1, User2, User3
        expense_payload = {
            "description": "Dinner - Partial SA by User2",
            "total_amount": "100.00",
            "currency": "USD",
            "group_id": group.id,
            "paid_by_user_id": user1.id,
            "split_type": SplitTypeEnum.EQUAL.value,
        }
        exp_response = await client.post(
            f"{settings.API_V1_STR}/expenses/", json=expense_payload, headers=user1_headers
        )
        assert exp_response.status_code == 201
        expense_id = exp_response.json()["id"]

        splits = await self._get_expense_splits(client, expense_id, user1_headers)
        user2_split = next(s for s in splits if s["user_id"] == user2.id)
        assert user2_split["owed_amount"] == "33.33"

        # User2 pays 10.00 of their 33.33 share via SettlementActivity
        await self._create_settlement_activity(
            client, user2_split["id"], user2.id, "10.00", user2_headers
        )
        
        summary_response = await client.get(
            f"{settings.API_V1_STR}/groups/{group.id}/balance-summary", headers=user1_headers
        )
        assert summary_response.status_code == 200
        summary_data = summary_response.json()
        user_balances = {ub["user_id"]: ub for ub in summary_data["user_balances"]}

        # Expected balances:
        # User1: Paid 100. Share 33.33. Effectively received 10 from User2. Net: +56.67
        # User2: Share 33.33. Paid 10.00 via SA. Adjusted Share: 23.33. Net: -23.33
        # User3: Share 33.34. Paid 0. Net: -33.34
        # Sum = 56.67 - 23.33 - 33.34 = 0

        # User1 assertions
        assert user_balances[user1.id]["total_paid_for_expenses"] == "100.00"
        assert user_balances[user1.id]["total_share_of_expenses"] == "33.33"
        assert user_balances[user1.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user1.id]["total_settlements_received"] == "0.00"
        assert user_balances[user1.id]["net_balance"] == "56.67"

        # User2 assertions
        assert user_balances[user2.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user2.id]["total_share_of_expenses"] == "23.33" # 33.33 initial - 10.00 SA
        assert user_balances[user2.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user2.id]["total_settlements_received"] == "0.00"
        assert user_balances[user2.id]["net_balance"] == "-23.33"

        # User3 assertions
        assert user_balances[user3.id]["total_paid_for_expenses"] == "0.00"
        assert user_balances[user3.id]["total_share_of_expenses"] == "33.34"
        assert user_balances[user3.id]["total_settlements_paid"] == "0.00"
        assert user_balances[user3.id]["total_settlements_received"] == "0.00"
        assert user_balances[user3.id]["net_balance"] == "-33.34"
        
        sum_net_balances = sum(Decimal(ub["net_balance"]) for ub in user_balances.values())
        assert sum_net_balances == Decimal("0.00")

        # Suggested settlements: User2 pays User1 23.33, User3 pays User1 33.34
        suggested = sorted(summary_data["suggested_settlements"], key=lambda s: (s["from_user_id"], s["amount"]))
        assert len(suggested) == 2
        
        assert suggested[0]["from_user_id"] == user2.id
        assert suggested[0]["to_user_id"] == user1.id
        assert suggested[0]["amount"] == "23.33"
        
        assert suggested[1]["from_user_id"] == user3.id
        assert suggested[1]["to_user_id"] == user1.id
        assert suggested[1]["amount"] == "33.34"
