"""Read-only ledger + payments views for the ops portal (V2 §11.2 Ledger).

The ledger itself is read-only for staff — balances are derived from
immutable entries and the trial balance must always net to zero. The one
mutating action here, admin_retry_payout, doesn't touch the ledger directly;
it re-submits an already-approved disbursement through the normal
payments.flows path, which only ever posts a journal once the provider
confirms success (see payments/flows.py::_disburse_seller_payout).
"""

from decimal import ROUND_HALF_UP, Decimal, InvalidOperation

from django.db import transaction
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from ledger.models import LedgerJournal
from ledger.services import trial_balance
from payments.fees import compute_fee
from payments.flows import retry_payout
from payments.models import FeeSchedule, Payout, Refund

from .admin_api import _audit, _operator, _paginate, _require_admin
from .admin_rbac import _require_superuser


@api_view(["GET"])
def admin_ledger_summary(request):
    err = _require_admin(request)
    if err:
        return err
    tb = trial_balance()
    return Response({
        "accounts": [
            {**row, "debit": str(row["debit"]), "credit": str(row["credit"]),
             "balance": str(row["debit"] - row["credit"])}
            for row in tb["rows"]
        ],
        "total_debit": str(tb["total_debit"]),
        "total_credit": str(tb["total_credit"]),
        "imbalance": str(tb["imbalance"]),
        "journal_count": LedgerJournal.objects.count(),
    })


def _serialize_journal(j: LedgerJournal):
    return {
        "id": str(j.id),
        "reference": j.reference,
        "description": j.description,
        "deal_code": j.deal.code if j.deal_id else None,
        "created_by": j.created_by,
        "created_at": j.created_at,
        "entries": [
            {"account": e.account.code, "account_name": e.account.name,
             "debit": str(e.debit), "credit": str(e.credit), "memo": e.memo}
            for e in j.entries.select_related("account").all()
        ],
    }


@api_view(["GET"])
def admin_ledger_journals(request):
    err = _require_admin(request)
    if err:
        return err
    qs = LedgerJournal.objects.prefetch_related("entries__account").select_related("deal").order_by("-created_at")
    deal_code = request.query_params.get("deal_code")
    if deal_code:
        qs = qs.filter(deal__code__iexact=deal_code)
    return Response(_paginate(request, qs, _serialize_journal))


def _serialize_fee_schedule(f, request=None):
    example = None
    if request is not None:
        raw = str(request.query_params.get("preview_amount") or "100000")
        try:
            example = str(compute_fee(Decimal(raw), at=f.effective_from))
        except InvalidOperation:
            example = None
    return {
        "id": str(f.id), "name": f.name, "percent_bps": f.percent_bps,
        "flat_fee": str(f.flat_fee), "min_fee": str(f.min_fee), "currency": f.currency,
        "effective_from": f.effective_from, "effective_to": f.effective_to, "is_active": f.is_active,
        "preview_fee": example,
    }


@api_view(["GET", "POST"])
def admin_fee_schedules(request):
    err = _require_admin(request)
    if err:
        return err

    if request.method == "POST":
        err = _require_superuser(request)
        if err:
            return err

        name = str(request.data.get("name") or "").strip()
        if not name:
            return Response({"detail": "name is required."}, status=status.HTTP_400_BAD_REQUEST)

        try:
            percent = Decimal(str(request.data.get("percent") or "0"))
            cents = Decimal("0.01")
            flat_fee = Decimal(str(request.data.get("flat_fee") or "0")).quantize(cents, rounding=ROUND_HALF_UP)
            min_fee = Decimal(str(request.data.get("min_fee") or "0")).quantize(cents, rounding=ROUND_HALF_UP)
        except InvalidOperation:
            return Response({"detail": "percent, flat_fee, and min_fee must be decimals."},
                             status=status.HTTP_400_BAD_REQUEST)

        if percent < 0 or percent > 100:
            return Response({"detail": "percent must be between 0 and 100."}, status=status.HTTP_400_BAD_REQUEST)
        if flat_fee < 0 or min_fee < 0:
            return Response({"detail": "flat_fee and min_fee cannot be negative."}, status=status.HTTP_400_BAD_REQUEST)

        currency = str(request.data.get("currency") or "TZS").strip().upper()[:3]
        now = timezone.now()

        with transaction.atomic():
            # Effective-dated, not overwritten in place — the old schedule
            # stays on the books for deals it already priced; it just stops
            # being the one new deals resolve against.
            FeeSchedule.objects.filter(is_active=True, effective_to__isnull=True).update(effective_to=now)
            schedule = FeeSchedule.objects.create(
                name=name,
                percent_bps=int((percent * 100).to_integral_value()),
                flat_fee=flat_fee,
                min_fee=min_fee,
                currency=currency,
                effective_from=now,
                is_active=True,
            )
        _audit(request, "settings.fee_schedule.create", schedule, {
            "name": name, "percent_bps": schedule.percent_bps,
            "flat_fee": str(flat_fee), "min_fee": str(min_fee),
        })
        return Response(_serialize_fee_schedule(schedule, request), status=status.HTTP_201_CREATED)

    schedules = FeeSchedule.objects.all().order_by("-effective_from")
    return Response({"results": [_serialize_fee_schedule(f, request) for f in schedules]})


@api_view(["GET"])
def admin_payouts_refunds(request):
    err = _require_admin(request)
    if err:
        return err
    payouts = [
        {"id": str(p.id), "deal_code": p.deal.code, "amount": str(p.amount),
         "status": p.status, "retry_count": p.retry_count, "provider": p.provider,
         "provider_ref": p.provider_ref, "created_at": p.created_at, "updated_at": p.updated_at}
        for p in Payout.objects.select_related("deal").order_by("-created_at")[:100]
    ]
    refunds = [
        {"id": str(r.id), "deal_code": r.deal.code, "amount": str(r.amount),
         "status": r.status, "reason": r.reason, "provider": r.provider,
         "provider_ref": r.provider_ref, "created_at": r.created_at}
        for r in Refund.objects.select_related("deal").order_by("-created_at")[:100]
    ]
    return Response({"payouts": payouts, "refunds": refunds})


@api_view(["POST"])
def admin_retry_payout(request, payout_id):
    err = _require_admin(request)
    if err:
        return err
    payout = Payout.objects.select_related("deal", "seller").filter(id=payout_id).first()
    if payout is None:
        return Response({"detail": "Payout not found."}, status=status.HTTP_404_NOT_FOUND)
    if payout.status not in {Payout.Status.FAILED_RETRYABLE, Payout.Status.FAILED_FINAL}:
        return Response(
            {"detail": f"Payout is {payout.status} — only a failed payout can be retried."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    operator = _operator(request)
    updated = retry_payout(payout, operator=operator)
    _audit(request, "payout.retry", updated, {
        "deal_code": updated.deal.code, "result_status": updated.status, "operator": operator,
    })
    return Response({
        "id": str(updated.id), "deal_code": updated.deal.code, "amount": str(updated.amount),
        "status": updated.status, "retry_count": updated.retry_count, "provider": updated.provider,
        "provider_ref": updated.provider_ref, "created_at": updated.created_at, "updated_at": updated.updated_at,
    })
