"""
Integration and unit tests for Referral Partner Management & Commission Ledger (Step 4.4).
"""

from decimal import Decimal

import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.security import create_access_token, hash_password
from app.models.billing import Bill, BillStatus
from app.models.lab import Lab
from app.models.order import OrderStatus, OrderTest, VisitOrder
from app.models.patient import Gender, Patient
from app.models.referral import CommissionEntry, Referral
from app.models.test import (
    Test as LabTest,
)
from app.models.test import (
    TestCategory as LabCategory,
)
from app.models.test import (
    TestResultType as LabResultType,
)
from app.models.user import User, UserRole
from app.repositories.permission_repository import PermissionRepository
from app.services.referral_service import ReferralService


@pytest_asyncio.fixture(autouse=True)
async def seed_perms(db_session: AsyncSession) -> None:
    """Seed system permissions for RBAC enforcement."""
    repo = PermissionRepository()
    await repo.seed_permissions(db_session)


@pytest_asyncio.fixture
async def referral_lab(db_session: AsyncSession) -> Lab:
    """Create primary test branch lab."""
    lab = Lab(name="Main Referral Lab", address="Kathmandu")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def secondary_lab(db_session: AsyncSession) -> Lab:
    """Create secondary branch lab for tenant isolation testing."""
    lab = Lab(name="Secondary Referral Lab", address="Pokhara")
    db_session.add(lab)
    await db_session.commit()
    await db_session.refresh(lab)
    return lab


@pytest_asyncio.fixture
async def accountant_token(db_session: AsyncSession, referral_lab: Lab) -> str:
    """Create accountant user token with REFERRAL_*, COMMISSION_* permissions."""
    accountant = User(
        lab_id=referral_lab.id,
        name="Referral Accountant",
        phone="9817000001",
        role=UserRole.ACCOUNTANT,
        hashed_password=hash_password("AccountantPass123!"),
        is_active=True,
    )
    db_session.add(accountant)
    await db_session.commit()
    await db_session.refresh(accountant)
    return create_access_token(
        subject=str(accountant.id),
        extra_claims={
            "phone": accountant.phone,
            "role": accountant.role.value,
            "lab_id": str(referral_lab.id),
        },
    )


@pytest_asyncio.fixture
async def secondary_accountant_token(db_session: AsyncSession, secondary_lab: Lab) -> str:
    """Create accountant user token in secondary branch."""
    accountant = User(
        lab_id=secondary_lab.id,
        name="Pokhara Accountant",
        phone="9817000002",
        role=UserRole.ACCOUNTANT,
        hashed_password=hash_password("AccountantPass123!"),
        is_active=True,
    )
    db_session.add(accountant)
    await db_session.commit()
    await db_session.refresh(accountant)
    return create_access_token(
        subject=str(accountant.id),
        extra_claims={
            "phone": accountant.phone,
            "role": accountant.role.value,
            "lab_id": str(secondary_lab.id),
        },
    )


@pytest_asyncio.fixture
async def unauthorized_user_token(db_session: AsyncSession, referral_lab: Lab) -> str:
    """User without COMMISSION_WRITE permissions (e.g. General User)."""
    user = User(
        lab_id=referral_lab.id,
        name="General User",
        phone="9817000003",
        role=UserRole.GENERAL_USER,
        hashed_password=hash_password("UserPass123!"),
        is_active=True,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return create_access_token(
        subject=str(user.id),
        extra_claims={
            "phone": user.phone,
            "role": user.role.value,
            "lab_id": str(referral_lab.id),
        },
    )


@pytest.mark.asyncio
async def test_create_referral_partner(
    client: AsyncClient,
    accountant_token: str,
) -> None:
    """Create a new referral doctor partner."""
    payload = {
        "name": "Dr. Sharma",
        "referral_type": "doctor",
        "code": "DOC-SHARMA",
        "phone": "9841234567",
        "commission_rate": "15.00",
        "notes": "Senior Consultant",
    }
    response = await client.post(
        "/api/v1/referrals",
        json=payload,
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert response.status_code == 201
    data = response.json()["data"]
    assert data["name"] == "Dr. Sharma"
    assert data["code"] == "DOC-SHARMA"
    assert Decimal(str(data["commission_rate"])) == Decimal("15.00")
    assert data["referral_type"] == "doctor"
    assert data["is_active"] is True


@pytest.mark.asyncio
async def test_create_referral_duplicate_code_conflict(
    client: AsyncClient,
    accountant_token: str,
) -> None:
    """Duplicate partner code in the same branch raises 409 Conflict."""
    payload = {
        "name": "Dr. First",
        "code": "DOC-DUP",
        "commission_rate": "10.00",
    }
    res1 = await client.post(
        "/api/v1/referrals",
        json=payload,
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert res1.status_code == 201

    # Second creation with identical code in same branch
    res2 = await client.post(
        "/api/v1/referrals",
        json={
            "name": "Dr. Second",
            "code": "DOC-DUP",
            "commission_rate": "12.00",
        },
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert res2.status_code == 409
    assert "already exists" in res2.text.lower()


@pytest.mark.asyncio
async def test_same_code_different_branch_allowed(
    client: AsyncClient,
    accountant_token: str,
    secondary_accountant_token: str,
) -> None:
    """Same partner code in different branches is allowed (tenant isolation)."""
    payload = {
        "name": "City Clinic",
        "code": "CLINIC-01",
        "commission_rate": "10.00",
    }
    # Branch 1
    r1 = await client.post(
        "/api/v1/referrals",
        json=payload,
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert r1.status_code == 201

    # Branch 2
    r2 = await client.post(
        "/api/v1/referrals",
        json=payload,
        headers={"Authorization": f"Bearer {secondary_accountant_token}"},
    )
    assert r2.status_code == 201


@pytest.mark.asyncio
async def test_update_and_soft_delete_referral(
    client: AsyncClient,
    accountant_token: str,
) -> None:
    """Update referral partner details and soft-delete."""
    create_res = await client.post(
        "/api/v1/referrals",
        json={"name": "Dr. Modifiable", "commission_rate": "10.00"},
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert create_res.status_code == 201
    partner_id = create_res.json()["data"]["id"]

    # Update
    patch_res = await client.patch(
        f"/api/v1/referrals/{partner_id}",
        json={"commission_rate": "18.50", "notes": "Updated rate agreement"},
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert patch_res.status_code == 200
    assert Decimal(str(patch_res.json()["data"]["commission_rate"])) == Decimal("18.50")

    # Soft-delete
    del_res = await client.delete(
        f"/api/v1/referrals/{partner_id}",
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert del_res.status_code == 200

    # Verify not returned
    get_res = await client.get(
        f"/api/v1/referrals/{partner_id}",
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert get_res.status_code == 404


@pytest.mark.asyncio
async def test_commission_accrual_and_idempotency(
    client: AsyncClient,
    accountant_token: str,
    db_session: AsyncSession,
    referral_lab: Lab,
) -> None:
    """Test commission accrual calculation: order amount * rate / 100 and idempotency."""
    # Create referral partner
    partner = Referral(
        lab_id=referral_lab.id,
        name="Dr. Specialist",
        code="SPEC-01",
        commission_rate=Decimal("12.50"),
        is_active=True,
    )
    db_session.add(partner)

    # Create patient & test
    patient = Patient(
        lab_id=referral_lab.id,
        patient_code="PAT-REF-001",
        name="Patient With Referral",
        age=40,
        gender=Gender.FEMALE,
        phone="9842000001",
    )
    db_session.add(patient)

    cat = LabCategory(lab_id=referral_lab.id, name="Pathology")
    db_session.add(cat)
    await db_session.flush()

    test = LabTest(
        lab_id=referral_lab.id,
        category_id=cat.id,
        name="Thyroid Panel",
        code="THY-01",
        price=Decimal("2000.00"),
        result_type=LabResultType.NUMERIC,
    )
    db_session.add(test)
    await db_session.flush()

    # Create order with referral_id
    order = VisitOrder(
        lab_id=referral_lab.id,
        patient_id=patient.id,
        referral_id=partner.id,
        order_number="ORD/26/007777",
        status=OrderStatus.CONFIRMED,
    )
    db_session.add(order)
    await db_session.flush()

    ot = OrderTest(
        order_id=order.id,
        test_id=test.id,
        price_at_order=Decimal("2000.00"),
    )
    db_session.add(ot)

    # Bill
    bill = Bill(
        lab_id=referral_lab.id,
        order_id=order.id,
        patient_id=patient.id,
        bill_number="INV/26/007777",
        status=BillStatus.UNPAID,
        subtotal=Decimal("2000.00"),
        discount_amount=Decimal("0.00"),
        tax_amount=Decimal("0.00"),
        total_amount=Decimal("2000.00"),
        paid_amount=Decimal("0.00"),
        due_amount=Decimal("2000.00"),
    )
    db_session.add(bill)
    await db_session.commit()

    # Trigger commission accrual
    service = ReferralService()
    entry1 = await service.accrue_commission_for_order(db_session, order.id)
    assert entry1 is not None
    # 2000 * 12.5% = 250.00
    assert entry1.order_amount == Decimal("2000.00")
    assert entry1.commission_rate == Decimal("12.50")
    assert entry1.commission_amount == Decimal("250.00")
    assert entry1.is_settled is False

    # Second call (idempotent) returns the exact same entry
    entry2 = await service.accrue_commission_for_order(db_session, order.id)
    assert entry2 is not None
    assert entry2.id == entry1.id


@pytest.mark.asyncio
async def test_settle_single_commission_entry(
    client: AsyncClient,
    accountant_token: str,
    db_session: AsyncSession,
    referral_lab: Lab,
) -> None:
    """Test individual commission settlement via POST /api/v1/commissions/{id}/settle."""
    partner = Referral(
        lab_id=referral_lab.id,
        name="Dr. Settle Test",
        commission_rate=Decimal("10.00"),
    )
    db_session.add(partner)

    patient = Patient(
        lab_id=referral_lab.id,
        patient_code="PAT-SETTLE-1",
        name="Settle Patient",
        age=30,
        gender=Gender.MALE,
        phone="9843000001",
    )
    db_session.add(patient)
    await db_session.flush()

    order = VisitOrder(
        lab_id=referral_lab.id,
        patient_id=patient.id,
        referral_id=partner.id,
        order_number="ORD/26/008888",
        status=OrderStatus.CONFIRMED,
    )
    db_session.add(order)
    await db_session.flush()

    entry = CommissionEntry(
        lab_id=referral_lab.id,
        referral_id=partner.id,
        order_id=order.id,
        order_amount=Decimal("1000.00"),
        commission_rate=Decimal("10.00"),
        commission_amount=Decimal("100.00"),
        is_settled=False,
    )
    db_session.add(entry)
    await db_session.commit()
    await db_session.refresh(entry)

    # Settle entry
    settle_res = await client.post(
        f"/api/v1/commissions/{entry.id}/settle",
        json={"notes": "Cash payout voucher #V-101"},
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert settle_res.status_code == 200
    sdata = settle_res.json()["data"]
    assert sdata["is_settled"] is True
    assert sdata["settled_at"] is not None
    assert sdata["notes"] == "Cash payout voucher #V-101"

    # Settle again -> 422
    again_res = await client.post(
        f"/api/v1/commissions/{entry.id}/settle",
        json={"notes": "Duplicate"},
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert again_res.status_code == 422
    assert "already been settled" in again_res.text.lower()


@pytest.mark.asyncio
async def test_bulk_settle_partner_commissions(
    client: AsyncClient,
    accountant_token: str,
    db_session: AsyncSession,
    referral_lab: Lab,
) -> None:
    """Test bulk commission settlement for all unsettled entries of a partner."""
    partner = Referral(
        lab_id=referral_lab.id,
        name="Dr. Bulk Settle",
        commission_rate=Decimal("10.00"),
    )
    db_session.add(partner)

    patient = Patient(
        lab_id=referral_lab.id,
        patient_code="PAT-BULK-1",
        name="Bulk Patient",
        age=30,
        gender=Gender.MALE,
        phone="9844000001",
    )
    db_session.add(patient)
    await db_session.flush()

    # Create 2 orders and 2 commission entries
    o1 = VisitOrder(
        lab_id=referral_lab.id,
        patient_id=patient.id,
        referral_id=partner.id,
        order_number="ORD/26/009111",
        status=OrderStatus.CONFIRMED,
    )
    o2 = VisitOrder(
        lab_id=referral_lab.id,
        patient_id=patient.id,
        referral_id=partner.id,
        order_number="ORD/26/009222",
        status=OrderStatus.CONFIRMED,
    )
    db_session.add_all([o1, o2])
    await db_session.flush()

    e1 = CommissionEntry(
        lab_id=referral_lab.id,
        referral_id=partner.id,
        order_id=o1.id,
        order_amount=Decimal("1000.00"),
        commission_rate=Decimal("10.00"),
        commission_amount=Decimal("100.00"),
        is_settled=False,
    )
    e2 = CommissionEntry(
        lab_id=referral_lab.id,
        referral_id=partner.id,
        order_id=o2.id,
        order_amount=Decimal("2000.00"),
        commission_rate=Decimal("10.00"),
        commission_amount=Decimal("200.00"),
        is_settled=False,
    )
    db_session.add_all([e1, e2])
    await db_session.commit()

    # Bulk settle
    bulk_res = await client.post(
        f"/api/v1/referrals/{partner.id}/settle-commissions",
        json={"notes": "Monthly cheque settlement"},
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert bulk_res.status_code == 200
    settled = bulk_res.json()["data"]
    assert len(settled) == 2
    assert all(item["is_settled"] is True for item in settled)

    # Check partner commissions query with filter
    list_res = await client.get(
        f"/api/v1/referrals/{partner.id}/commissions?is_settled=true",
        headers={"Authorization": f"Bearer {accountant_token}"},
    )
    assert list_res.status_code == 200
    assert list_res.json()["meta"]["total"] == 2


@pytest.mark.asyncio
async def test_rbac_referrals_and_commissions(
    client: AsyncClient,
    unauthorized_user_token: str,
) -> None:
    """Unauthorized user receives 403 on write endpoints."""
    # Attempt to create partner
    c_res = await client.post(
        "/api/v1/referrals",
        json={"name": "Dr. Hacker", "commission_rate": "10.00"},
        headers={"Authorization": f"Bearer {unauthorized_user_token}"},
    )
    assert c_res.status_code == 403

    # Attempt to list commission ledger
    l_res = await client.get(
        "/api/v1/commissions",
        headers={"Authorization": f"Bearer {unauthorized_user_token}"},
    )
    assert l_res.status_code == 403
