"""
inventory/services/payout_service.py
──────────────────────────────────────
Generates, approves, and tracks author payouts.

A payout covers all SaleItems for an author in a date range where
payout_included=False. After generation, those items are marked
payout_included=True so they cannot be double-counted.
"""

import logging
from datetime import date
from decimal import Decimal

from django.db import transaction
from django.utils import timezone

from catalog_app.models import Author, Publisher
from inventory.models import (
    AuditLog,
    AuthorPayout,
    AuthorPayoutItem,
    SaleItem,
)

logger = logging.getLogger(__name__)


def _payee_filter(payee) -> dict:
    """Books route to their publisher when one is set, else their author."""
    if isinstance(payee, Publisher):
        return {"book__publisher": payee}
    return {"book__author": payee, "book__publisher__isnull": True}


# ---------------------------------------------------------------------------
# Calculate pending earnings for a payee (Author or Publisher)
# ---------------------------------------------------------------------------

def get_pending_earnings(payee, start_date: date, end_date: date) -> dict:
    """
    Return unpaid SaleItems owed to `payee` (an Author or Publisher) within
    the date range, and totals.
    """
    sale_items = (
        SaleItem.objects
        .filter(
            payout_included=False,
            sale__sale_date__gte=start_date,
            sale__sale_date__lte=end_date,
            sale__is_voided=False, is_voided=False,
            **_payee_filter(payee),
        )
        .select_related("book", "sale")
        .order_by("sale__sale_date")
    )

    total = sum(item.author_payout_amount for item in sale_items)
    return {"sale_items": sale_items, "total_earnings": total}


# ---------------------------------------------------------------------------
# Generate a payout record
# ---------------------------------------------------------------------------

@transaction.atomic
def generate_payout(
    *,
    payee,
    period_type: str,
    period_start: date,
    period_end: date,
    notes: str = "",
    user=None,
) -> AuthorPayout:
    """
    Create an AuthorPayout + AuthorPayoutItems for all unpaid sale items
    in the date range owed to `payee` (an Author or Publisher instance).

    After creation the SaleItems are NOT yet marked payout_included.
    That happens only on approval (two-step process for safety).
    """
    data = get_pending_earnings(payee, period_start, period_end)
    sale_items = list(data["sale_items"].select_for_update())

    if not sale_items:
        raise ValueError(
            f"No unpaid earnings for {payee.name} "
            f"between {period_start} and {period_end}."
        )

    total_earnings = data["total_earnings"]
    is_publisher = isinstance(payee, Publisher)

    payout = AuthorPayout.objects.create(
        author=None if is_publisher else payee,
        publisher=payee if is_publisher else None,
        period_type=period_type,
        period_start=period_start,
        period_end=period_end,
        total_earnings=total_earnings,
        outstanding_balance=total_earnings,
        prepared_by=user,
        notes=notes,
    )

    for item in sale_items:
        AuthorPayoutItem.objects.create(
            payout=payout,
            sale_item=item,
            book=item.book,
            gross_earnings=item.author_payout_amount,
            net_earnings=item.author_payout_amount,
        )

    AuditLog.objects.create(
        user=user,
        action=AuditLog.ACTION_CREATE,
        model_name="AuthorPayout",
        object_id=str(payout.pk),
        object_repr=str(payout),
        new_values={
            "publisher" if is_publisher else "author": payee.pk,
            "period": f"{period_start} → {period_end}",
            "total_earnings": str(total_earnings),
        },
    )
    logger.info(
        "Payout generated: payee=%s | period=%s→%s | KES %s",
        payee.name, period_start, period_end, total_earnings,
    )
    return payout


# ---------------------------------------------------------------------------
# Approve a payout
# ---------------------------------------------------------------------------

@transaction.atomic
def approve_payout(*, payout: AuthorPayout, user=None) -> AuthorPayout:
    """
    Approve a pending payout.
    Marks all linked SaleItems as payout_included so they won't be
    counted again in the next payout cycle.
    """
    if payout.status != AuthorPayout.STATUS_PENDING:
        raise ValueError(f"Payout is not pending (current: {payout.status}).")

    old_status = payout.status
    payout.status = AuthorPayout.STATUS_APPROVED
    payout.approved_by = user
    payout.approved_at = timezone.now()
    payout.save(update_fields=["status", "approved_by", "approved_at", "updated_at"])

    # Lock in the SaleItems
    sale_item_ids = payout.items.values_list("sale_item_id", flat=True)
    SaleItem.objects.filter(pk__in=sale_item_ids).update(payout_included=True)

    AuditLog.objects.create(
        user=user,
        action=AuditLog.ACTION_APPROVE,
        model_name="AuthorPayout",
        object_id=str(payout.pk),
        object_repr=str(payout),
        old_values={"status": old_status},
        new_values={"status": payout.status},
    )
    return payout


# ---------------------------------------------------------------------------
# Mark a payout as paid
# ---------------------------------------------------------------------------

@transaction.atomic
def mark_payout_paid(
    *,
    payout: AuthorPayout,
    payment_method: str,
    payment_reference: str,
    payment_date: date,
    amount_paid: Decimal | None = None,
    user=None,
) -> AuthorPayout:
    if payout.status != AuthorPayout.STATUS_APPROVED:
        raise ValueError("Payout must be approved before marking as paid.")

    paid_amount = amount_paid or payout.outstanding_balance

    old = {
        "status": payout.status,
        "total_paid": str(payout.total_paid),
        "outstanding_balance": str(payout.outstanding_balance),
    }

    payout.status = AuthorPayout.STATUS_PAID
    payout.payment_method = payment_method
    payout.payment_reference = payment_reference
    payout.payment_date = payment_date
    payout.total_paid = paid_amount
    payout.outstanding_balance = payout.total_earnings + payout.total_adjustments - paid_amount
    payout.paid_at = timezone.now()
    payout.save(update_fields=[
        "status", "payment_method", "payment_reference", "payment_date",
        "total_paid", "outstanding_balance", "paid_at", "updated_at",
    ])

    AuditLog.objects.create(
        user=user,
        action=AuditLog.ACTION_PAY,
        model_name="AuthorPayout",
        object_id=str(payout.pk),
        object_repr=str(payout),
        old_values=old,
        new_values={"status": "paid", "amount_paid": str(paid_amount)},
    )
    logger.info(
        "Payout paid: %s | payee=%s | KES %s via %s",
        payout.pk, payout.payee.name, paid_amount, payment_method,
    )
    return payout


# ---------------------------------------------------------------------------
# Outstanding balance per payee
# ---------------------------------------------------------------------------

def get_author_outstanding_balance(payee) -> Decimal:
    """Sum of all unpaid SaleItem.author_payout_amount owed to this payee (Author or Publisher)."""
    from django.db.models import Sum
    result = (
        SaleItem.objects
        .filter(payout_included=False, sale__is_voided=False, is_voided=False, **_payee_filter(payee))
        .aggregate(total=Sum("author_payout_amount"))["total"]
    )
    return result or Decimal("0")
