"""
Payment service handling payment recording, real-time ledger settlement, and overpayment guards.
"""

from uuid import UUID

import structlog
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import NotFoundException, ValidationException
from app.core.money import CENT, npr
from app.models.billing import BillStatus, Payment, PaymentMode
from app.models.order import OrderStatus
from app.models.user import User
from app.repositories.bill_repository import BillRepository
from app.repositories.order_repository import OrderRepository
from app.repositories.payment_repository import PaymentRepository
from app.schemas.billing import PaymentCreate

logger = structlog.get_logger(__name__)


class PaymentService:
    """Service handling payment receipts and real-time invoice balance settlement."""

    def __init__(
        self,
        repository: PaymentRepository | None = None,
        bill_repository: BillRepository | None = None,
    ) -> None:
        self.repo = repository or PaymentRepository()
        self.bill_repo = bill_repository or BillRepository()
        self.order_repo = OrderRepository()

    async def record_payment(
        self,
        db: AsyncSession,
        lab_id: UUID,
        bill_id: UUID,
        data: PaymentCreate,
        received_by: User,
    ) -> Payment:
        """
        Record a payment transaction against a bill.

        Enforces:
        1. Bill exists and belongs to the specified lab branch.
        2. Bill is not voided.
        3. Bill is not already fully settled.
        4. Payment amount does not exceed the outstanding due amount.
        5. Updates the bill ledger in real time (paid_amount, due_amount, status).
        """
        bill = await self.bill_repo.get_by_id(db, bill_id, lab_id)
        if not bill:
            raise NotFoundException(f"Bill with ID '{bill_id}' was not found.")

        if bill.status == BillStatus.VOID:
            raise ValidationException("Cannot accept payment on a voided bill.")

        if bill.status == BillStatus.PAID or npr(bill.due_amount) <= CENT:
            raise ValidationException("Bill is already fully settled.")

        # Quantize tendered float artifacts (e.g. 2074.5500000000002) to paisa first.
        amount = npr(data.amount)
        if amount <= CENT:
            raise ValidationException("Payment amount must be greater than zero.")

        if amount > npr(bill.due_amount):
            msg = f"Payment amount ({amount}) exceeds outstanding bill due amount ({bill.due_amount})."
            raise ValidationException(msg)

        # Record payment transaction
        payment = Payment(
            lab_id=lab_id,
            bill_id=bill.id,
            received_by=received_by.id,
            amount=amount,
            payment_mode=data.payment_mode,
            transaction_reference=data.transaction_reference,
            notes=data.notes,
        )

        # Real-time ledger update on parent Bill (quantized — no paisa residue)
        bill.paid_amount = npr(bill.paid_amount + amount)
        bill.due_amount = npr(bill.total_amount - bill.paid_amount)
        if bill.due_amount == CENT:
            bill.status = BillStatus.PAID
        else:
            bill.status = BillStatus.PARTIAL

        order = await self.order_repo.get_by_id(db, bill.order_id)
        if (
            order
            and bill.status == BillStatus.PAID
            and order.status
            not in (
                OrderStatus.CANCELLED,
                OrderStatus.COMPLETED,
            )
        ):
            if order.status == OrderStatus.PENDING:
                order.status = OrderStatus.CONFIRMED

        # Persist payment and flush changes
        created_payment = await self.repo.create(db, payment)

        # Accrue referral commission if order has an associated partner
        try:
            from app.services.referral_service import ReferralService

            await ReferralService().accrue_commission_for_order(db, bill.order_id, user_id=received_by.id)
        except Exception as exc:
            logger.warning("commission_accrual_failed", bill_id=str(bill.id), error=str(exc))

        # Reload payment with relationships
        full_payment = await self.repo.get_by_id(db, created_payment.id, lab_id)
        return full_payment or created_payment

    async def get_payment(
        self,
        db: AsyncSession,
        payment_id: UUID,
        lab_id: UUID | None = None,
    ) -> Payment:
        """Retrieve a single payment receipt by ID."""
        payment = await self.repo.get_by_id(db, payment_id, lab_id)
        if not payment:
            raise NotFoundException(f"Payment with ID '{payment_id}' was not found.")
        return payment

    async def list_payments_for_bill(
        self,
        db: AsyncSession,
        bill_id: UUID,
        lab_id: UUID | None = None,
    ) -> list[Payment]:
        """Retrieve all payment receipts associated with a bill."""
        bill = await self.bill_repo.get_by_id(db, bill_id, lab_id)
        if not bill:
            raise NotFoundException(f"Bill with ID '{bill_id}' was not found.")
        return await self.repo.list_by_bill(db, bill_id, lab_id)

    async def list_payments(
        self,
        db: AsyncSession,
        lab_id: UUID,
        payment_mode: PaymentMode | None = None,
        page: int = 1,
        limit: int = 50,
    ) -> tuple[list[Payment], int]:
        """List paginated payment transactions for a lab branch."""
        offset = (page - 1) * limit
        return await self.repo.list_payments(
            db,
            lab_id=lab_id,
            payment_mode=payment_mode,
            offset=offset,
            limit=limit,
        )
