"""
Billing and invoice management endpoints.
"""

import math
from datetime import datetime
from typing import Annotated
from uuid import UUID

from fastapi import APIRouter, Depends, Query, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.auth_deps import get_current_user, require_permission
from app.core.dependencies import get_db
from app.core.exceptions import ForbiddenException
from app.core.pagination import PaginationParams
from app.core.permissions import PermissionCode
from app.core.responses import PaginatedResponse, SuccessResponse
from app.models.billing import Bill, BillStatus, Payment
from app.models.user import User
from app.repositories.lab_repository import LabRepository
from app.schemas.billing import (
    BillCreate,
    BillDetailResponse,
    BillResponse,
    BillSampleItem,
    BillUpdate,
    BillVoidRequest,
    PaymentCreate,
    PaymentResponse,
)
from app.services.billing_service import BillingService
from app.services.payment_service import PaymentService

router = APIRouter(prefix="/bills", tags=["Billing & Invoicing"])
_billing_service = BillingService()
_payment_service = PaymentService()


async def _resolve_lab_id(
    db: AsyncSession,
    user: User,
    requested_lab_id: UUID | None,
) -> UUID:
    """Resolve and authorize the target branch lab_id."""
    if user.is_superadmin:
        if requested_lab_id:
            return requested_lab_id
        if user.lab_id:
            return user.lab_id
        return await LabRepository().get_default_lab_id(db)

    if not user.lab_id:
        return await LabRepository().get_default_lab_id(db)

    if requested_lab_id and requested_lab_id != user.lab_id:
        raise ForbiddenException("Access denied to another lab branch.")

    return user.lab_id


def _map_payment(p: Payment) -> PaymentResponse:
    """Map SQLAlchemy Payment model to PaymentResponse schema."""
    return PaymentResponse(
        id=p.id,
        lab_id=p.lab_id,
        bill_id=p.bill_id,
        received_by=p.received_by,
        amount=p.amount,
        payment_mode=p.payment_mode,
        transaction_reference=p.transaction_reference,
        paid_at=p.paid_at,
        notes=p.notes,
        created_at=p.created_at,
        updated_at=p.updated_at,
        received_by_name=p.received_by_user.name if p.received_by_user else None,
    )


def _map_bill_detail(bill: Bill, portal_pin: str | None = None) -> BillDetailResponse:
    """Map SQLAlchemy Bill model to BillDetailResponse schema."""
    resolved_pin = portal_pin
    if not resolved_pin and bill.patient and getattr(bill.patient, "portal_pin_enc", None):
        from app.core.portal_pin import decrypt_pin

        resolved_pin = decrypt_pin(bill.patient.portal_pin_enc)

    return BillDetailResponse(
        id=bill.id,
        lab_id=bill.lab_id,
        order_id=bill.order_id,
        patient_id=bill.patient_id,
        bill_number=bill.bill_number,
        status=bill.status,
        subtotal=bill.subtotal,
        discount_amount=bill.discount_amount,
        discount_rule_id=bill.discount_rule_id,
        tax_amount=bill.tax_amount,
        total_amount=bill.total_amount,
        paid_amount=bill.paid_amount,
        due_amount=bill.due_amount,
        notes=bill.notes,
        created_at=bill.created_at,
        updated_at=bill.updated_at,
        patient_name=bill.patient.name if bill.patient else None,
        patient_mrn=bill.patient.patient_code if bill.patient else None,
        patient_phone=bill.patient.phone if bill.patient else None,
        order_number=bill.order.order_number if bill.order else None,
        discount_rule_name=bill.discount_rule.name if bill.discount_rule else None,
        portal_pin=resolved_pin,
        payments=[_map_payment(p) for p in bill.payments] if bill.payments else [],
        samples=[
            BillSampleItem(
                sample_code=s.sample_code,
                barcode=s.barcode,
                sample_type=s.sample_type,
                status=s.status.value if hasattr(s.status, "value") else str(s.status),
            )
            for s in (bill.order.samples if bill.order and bill.order.samples else [])
        ],
    )


@router.get(
    "",
    response_model=PaginatedResponse[BillResponse],
    status_code=status.HTTP_200_OK,
    summary="List bills",
    description="Retrieve paginated invoices for a lab branch with optional status/patient filters.",
    dependencies=[Depends(require_permission(PermissionCode.BILL_READ))],
)
async def list_bills(
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    pagination: Annotated[PaginationParams, Depends()],
    lab_id: UUID | None = Query(None, description="Filter by lab branch ID"),
    bill_status: BillStatus | None = Query(None, alias="status", description="Filter by bill status"),
    patient_id: UUID | None = Query(None, description="Filter by patient ID"),
    order_id: UUID | None = Query(None, description="Filter by visit order ID"),
    has_due: bool | None = Query(None, description="Filter bills with outstanding due balance"),
    is_abroad: bool | None = Query(None, description="Filter bills for abroad visits"),
    destination_country: str | None = Query(None, description="Filter bills by destination country"),
    q: str | None = Query(None, description="Search by invoice number"),
    created_from: datetime | None = Query(None, description="Created at or after (inclusive, ISO datetime)"),
    created_to: datetime | None = Query(None, description="Created before (exclusive, ISO datetime)"),
) -> PaginatedResponse[BillResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    items, total = await _billing_service.list_bills(
        db,
        lab_id=target_lab_id,
        status=bill_status,
        patient_id=patient_id,
        order_id=order_id,
        has_due=has_due,
        is_abroad=is_abroad,
        destination_country=destination_country,
        search=q,
        page=pagination.page,
        limit=pagination.limit,
        created_from=created_from,
        created_to=created_to,
    )
    pages = math.ceil(total / pagination.limit) if total > 0 else 1
    return PaginatedResponse(
        data=[BillResponse.model_validate(i) for i in items],
        meta={
            "page": pagination.page,
            "limit": pagination.limit,
            "total": total,
            "pages": pages,
        },
    )


@router.post(
    "",
    response_model=SuccessResponse[BillDetailResponse],
    status_code=status.HTTP_201_CREATED,
    summary="Create bill",
    description="Generate a 1:1 invoice for a visit order with test and package prices.",
    dependencies=[Depends(require_permission(PermissionCode.BILL_WRITE))],
)
async def create_bill(
    data: BillCreate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Target branch lab ID"),
) -> SuccessResponse[BillDetailResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    bill, portal_pin = await _billing_service.create_bill(db, target_lab_id, data)
    detailed_bill = await _billing_service.get_bill(db, bill.id, target_lab_id)
    return SuccessResponse(
        data=_map_bill_detail(detailed_bill, portal_pin=portal_pin),
        message=f"Bill '{bill.bill_number}' created successfully.",
    )


@router.get(
    "/order/{order_id}",
    response_model=SuccessResponse[BillDetailResponse],
    status_code=status.HTTP_200_OK,
    summary="Get bill by order ID",
    description="Retrieve the invoice associated with a specific visit order.",
    dependencies=[Depends(require_permission(PermissionCode.BILL_READ))],
)
async def get_bill_by_order(
    order_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Lab branch ID"),
) -> SuccessResponse[BillDetailResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    bill = await _billing_service.get_bill_by_order(db, order_id, target_lab_id)
    return SuccessResponse(data=_map_bill_detail(bill))


@router.get(
    "/{bill_id}",
    response_model=SuccessResponse[BillDetailResponse],
    status_code=status.HTTP_200_OK,
    summary="Get bill",
    description="Retrieve detailed invoice by ID.",
    dependencies=[Depends(require_permission(PermissionCode.BILL_READ))],
)
async def get_bill(
    bill_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Lab branch ID"),
) -> SuccessResponse[BillDetailResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    bill = await _billing_service.get_bill(db, bill_id, target_lab_id)
    return SuccessResponse(data=_map_bill_detail(bill))


@router.patch(
    "/{bill_id}",
    response_model=SuccessResponse[BillResponse],
    status_code=status.HTTP_200_OK,
    summary="Update bill notes",
    description="Update billing remarks or notes.",
    dependencies=[Depends(require_permission(PermissionCode.BILL_WRITE))],
)
async def update_bill(
    bill_id: UUID,
    data: BillUpdate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Lab branch ID"),
) -> SuccessResponse[BillResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    bill = await _billing_service.update_bill(db, bill_id, target_lab_id, data)
    return SuccessResponse(
        data=BillResponse.model_validate(bill),
        message="Bill updated successfully.",
    )


@router.post(
    "/{bill_id}/void",
    response_model=SuccessResponse[BillResponse],
    status_code=status.HTTP_200_OK,
    summary="Void bill",
    description="Void an unpaid invoice with documented audit rationale.",
    dependencies=[Depends(require_permission(PermissionCode.BILL_WRITE))],
)
async def void_bill(
    bill_id: UUID,
    data: BillVoidRequest,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Lab branch ID"),
) -> SuccessResponse[BillResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    bill = await _billing_service.void_bill(db, bill_id, target_lab_id, data)
    return SuccessResponse(
        data=BillResponse.model_validate(bill),
        message=f"Bill '{bill.bill_number}' has been voided successfully.",
    )


@router.post(
    "/{bill_id}/payments",
    response_model=SuccessResponse[PaymentResponse],
    status_code=status.HTTP_201_CREATED,
    summary="Record payment against bill",
    description="Record a tender payment and settle bill balance in real time.",
    dependencies=[Depends(require_permission(PermissionCode.PAYMENT_WRITE))],
)
async def record_payment_for_bill(
    bill_id: UUID,
    data: PaymentCreate,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Lab branch ID"),
) -> SuccessResponse[PaymentResponse]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    payment = await _payment_service.record_payment(
        db=db,
        lab_id=target_lab_id,
        bill_id=bill_id,
        data=data,
        received_by=current_user,
    )
    return SuccessResponse(
        data=_map_payment(payment),
        message="Payment recorded successfully.",
    )


@router.get(
    "/{bill_id}/payments",
    response_model=SuccessResponse[list[PaymentResponse]],
    status_code=status.HTTP_200_OK,
    summary="List payments for bill",
    description="Retrieve all payment receipts associated with a bill.",
    dependencies=[Depends(require_permission(PermissionCode.PAYMENT_READ))],
)
async def list_payments_for_bill(
    bill_id: UUID,
    db: Annotated[AsyncSession, Depends(get_db)],
    current_user: Annotated[User, Depends(get_current_user)],
    lab_id: UUID | None = Query(None, description="Lab branch ID"),
) -> SuccessResponse[list[PaymentResponse]]:
    target_lab_id = await _resolve_lab_id(db, current_user, lab_id)
    payments = await _payment_service.list_payments_for_bill(db, bill_id, target_lab_id)
    return SuccessResponse(
        data=[_map_payment(p) for p in payments],
    )
