"""
inventory/views.py
───────────────────
DRF ViewSets for the inventory app.
All endpoints require staff authentication.
PDF generation endpoints use WeasyPrint.
"""

import io
import logging
from datetime import date, timedelta

from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from catalog_app.models import Author, Book
from inventory.models import (
    AuditLog,
    AuthorPayout,
    Bookstore,
    Event,
    Expense,
    ExpenseCategory,
    InventoryBalance,
    InventoryTransaction,
    Sale,
    SaleItem,
    StockLocation,
)
from inventory.permissions import (
    IsFinanceManager,
    IsInventoryManager,
    IsInventoryManagerOrReadOnlyForStaff,
    IsInventoryOrFinanceManager,
    IsSuperAdmin,
)
from inventory.serializers import (
    AuditLogSerializer,
    AuthorPayoutSerializer,
    BookstoreSerializer,
    EventSerializer,
    ExpenseCategorySerializer,
    ExpenseSerializer,
    GeneratePayoutSerializer,
    InventoryBalanceSerializer,
    InventoryTransactionSerializer,
    MarginPreviewSerializer,
    MarkPayoutPaidSerializer,
    ProfitLossSerializer,
    SaleCreateSerializer,
    SaleItemReadSerializer,
    SaleReadSerializer,
    StockAdjustmentSerializer,
    StockLocationSerializer,
    StockReceiptSerializer,
    StockTransferSerializer,
)
from inventory.services import (
    expense_service,
    inventory_service,
    pdf_service,
    payout_service,
    report_service,
    sales_service,
)

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Pagination
# ---------------------------------------------------------------------------

class StandardPagination(PageNumberPagination):
    page_size = 50
    page_size_query_param = "page_size"
    max_page_size = 500


# ---------------------------------------------------------------------------
# PDF helper (see inventory/services/pdf_service.py)
# ---------------------------------------------------------------------------

_render_pdf = pdf_service.render_pdf


# ---------------------------------------------------------------------------
# Stock Locations
# ---------------------------------------------------------------------------

class StockLocationViewSet(viewsets.ModelViewSet):
    queryset = StockLocation.objects.all()
    serializer_class = StockLocationSerializer
    permission_classes = [IsInventoryManagerOrReadOnlyForStaff]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["name", "code", "location_type"]
    ordering_fields = ["name", "code", "location_type"]
    ordering = ["name"]
    pagination_class = StandardPagination


# ---------------------------------------------------------------------------
# Inventory Transactions (create + list only; never update/delete)
# ---------------------------------------------------------------------------

class InventoryTransactionViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = (
        InventoryTransaction.objects
        .select_related("book", "source_location", "destination_location", "performed_by")
        .all()
    )
    serializer_class = InventoryTransactionSerializer
    permission_classes = [IsInventoryManagerOrReadOnlyForStaff]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["book__title", "transaction_reference", "transaction_type"]
    ordering_fields = ["created_at", "transaction_type", "quantity"]
    ordering = ["-created_at"]
    pagination_class = StandardPagination

    def get_queryset(self):
        qs = super().get_queryset()
        book_id = self.request.query_params.get("book")
        location_id = self.request.query_params.get("location")
        tx_type = self.request.query_params.get("type")
        start = self.request.query_params.get("start_date")
        end = self.request.query_params.get("end_date")
        if book_id:
            qs = qs.filter(book_id=book_id)
        if location_id:
            qs = qs.filter(
                source_location_id=location_id
            ) | qs.filter(destination_location_id=location_id)
        if tx_type:
            qs = qs.filter(transaction_type=tx_type)
        if start:
            qs = qs.filter(created_at__date__gte=start)
        if end:
            qs = qs.filter(created_at__date__lte=end)
        return qs

    @action(detail=True, methods=["post"], permission_classes=[IsInventoryManager])
    def reverse(self, request, pk=None):
        tx = self.get_object()
        notes = request.data.get("notes", "")
        try:
            reversal = inventory_service.reverse_transaction(
                original=tx, notes=notes, user=request.user
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(InventoryTransactionSerializer(reversal).data)


# ---------------------------------------------------------------------------
# Inventory Balances
# ---------------------------------------------------------------------------

class InventoryBalanceViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = (
        InventoryBalance.objects
        .select_related("book", "book__author", "location")
        .filter(location__is_active=True)
    )
    serializer_class = InventoryBalanceSerializer
    permission_classes = [IsInventoryManagerOrReadOnlyForStaff]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["book__title", "book__author__name", "location__name"]
    ordering_fields = ["quantity_on_hand", "book__title", "location__name"]
    ordering = ["book__title"]
    pagination_class = StandardPagination

    def get_queryset(self):
        qs = super().get_queryset()
        book_id = self.request.query_params.get("book")
        location_id = self.request.query_params.get("location")
        low_stock = self.request.query_params.get("low_stock")
        if book_id:
            qs = qs.filter(book_id=book_id)
        if location_id:
            qs = qs.filter(location_id=location_id)
        if low_stock == "true":
            qs = [b for b in qs if b.quantity_on_hand <= b.book.reorder_level]
        return qs


# ---------------------------------------------------------------------------
# Stock Operations (receive, transfer, adjust, write-off, dispatch)
# ---------------------------------------------------------------------------

class StockReceiveView(APIView):
    permission_classes = [IsInventoryManager]

    def post(self, request):
        ser = StockReceiptSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        try:
            tx = inventory_service.receive_stock(
                book=d["book"], location=d["location"],
                quantity=d["quantity"], reference=d["reference"],
                notes=d["notes"], user=request.user,
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(InventoryTransactionSerializer(tx).data, status=status.HTTP_201_CREATED)


class StockTransferView(APIView):
    permission_classes = [IsInventoryManager]

    def post(self, request):
        ser = StockTransferSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        try:
            tx = inventory_service.transfer_stock(
                book=d["book"],
                source=d["source_location"],
                destination=d["destination_location"],
                quantity=d["quantity"],
                reference=d["reference"],
                notes=d["notes"],
                user=request.user,
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(InventoryTransactionSerializer(tx).data, status=status.HTTP_201_CREATED)


class StockAdjustView(APIView):
    permission_classes = [IsInventoryManager]

    def post(self, request):
        ser = StockAdjustmentSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        try:
            tx = inventory_service.adjust_stock(
                book=d["book"], location=d["location"],
                new_quantity=d["new_quantity"],
                reference=d["reference"], notes=d["notes"],
                user=request.user,
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(InventoryTransactionSerializer(tx).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Bookstores
# ---------------------------------------------------------------------------

class BookstoreViewSet(viewsets.ModelViewSet):
    queryset = Bookstore.objects.select_related("stock_location").all()
    serializer_class = BookstoreSerializer
    permission_classes = [IsInventoryManagerOrReadOnlyForStaff]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["name", "contact_person", "email"]
    ordering = ["name"]
    pagination_class = StandardPagination

    @action(detail=True, methods=["post"], permission_classes=[IsInventoryManager])
    def dispatch_stock(self, request, pk=None):
        bookstore = self.get_object()
        book_id = request.data.get("book")
        source_id = request.data.get("source_location")
        quantity = request.data.get("quantity")
        reference = request.data.get("reference", "")
        notes = request.data.get("notes", "")

        try:
            book = Book.objects.get(pk=book_id)
            source = StockLocation.objects.get(pk=source_id)
            tx = inventory_service.dispatch_to_bookstore(
                book=book, bookstore=bookstore, quantity=int(quantity),
                source=source, reference=reference, notes=notes, user=request.user,
            )
        except (Book.DoesNotExist, StockLocation.DoesNotExist):
            return Response({"error": "Invalid book or location."}, status=status.HTTP_400_BAD_REQUEST)
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

        return Response(InventoryTransactionSerializer(tx).data, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=["get"], permission_classes=[IsInventoryManagerOrReadOnlyForStaff])
    def pdf_delivery_note(self, request, pk=None):
        bookstore = self.get_object()
        book_id = request.query_params.get("book")
        book = get_object_or_404(Book, pk=book_id) if book_id else None
        quantity = request.query_params.get("quantity", 0)
        context = {
            "bookstore": bookstore,
            "book": book,
            "quantity": quantity,
            "date": date.today(),
            "reference": request.query_params.get("reference", ""),
        }
        return _render_pdf(
            "inventory/pdf/delivery_note.html",
            context,
            f"DN-{date.today().strftime('%Y%m%d')}-{bookstore.pk:04d}.pdf",
        )


# ---------------------------------------------------------------------------
# Events
# ---------------------------------------------------------------------------

class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.select_related("stock_location").all()
    serializer_class = EventSerializer
    permission_classes = [IsInventoryManagerOrReadOnlyForStaff]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["name", "venue"]
    ordering_fields = ["start_date", "name"]
    ordering = ["-start_date"]
    pagination_class = StandardPagination


# ---------------------------------------------------------------------------
# Sales
# ---------------------------------------------------------------------------

class SaleViewSet(viewsets.ModelViewSet):
    queryset = (
        Sale.objects
        .select_related("bookstore", "event", "linked_order", "recorded_by")
        .prefetch_related("items__book__author", "items__stock_location")
        .all()
    )
    permission_classes = [IsInventoryOrFinanceManager]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["invoice_number", "customer_name", "customer_phone"]
    ordering_fields = ["sale_date", "total_net", "total_profit"]
    ordering = ["-sale_date"]
    pagination_class = StandardPagination

    def get_serializer_class(self):
        if self.action in ("create",):
            return SaleCreateSerializer
        return SaleReadSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        channel = self.request.query_params.get("channel")
        start = self.request.query_params.get("start_date")
        end = self.request.query_params.get("end_date")
        if channel:
            qs = qs.filter(sale_channel=channel)
        if start:
            qs = qs.filter(sale_date__gte=start)
        if end:
            qs = qs.filter(sale_date__lte=end)
        return qs

    def create(self, request, *args, **kwargs):
        ser = SaleCreateSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        try:
            sale = sales_service.record_sale(
                items=d["items"],
                sale_channel=d["sale_channel"],
                sale_date=d["sale_date"],
                customer_name=d["customer_name"],
                customer_phone=d["customer_phone"],
                bookstore=d.get("bookstore"),
                event=d.get("event"),
                stock_location=d.get("stock_location"),
                notes=d.get("notes", ""),
                user=request.user,
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(SaleReadSerializer(sale).data, status=status.HTTP_201_CREATED)

    def update(self, request, *args, **kwargs):
        return Response(
            {"error": "Sales cannot be edited. Create a reversal transaction instead."},
            status=status.HTTP_405_METHOD_NOT_ALLOWED,
        )

    @action(detail=True, methods=["get"], permission_classes=[IsInventoryOrFinanceManager])
    def pdf_invoice(self, request, pk=None):
        sale = self.get_object()
        context = {"sale": sale, "items": sale.items.select_related("book", "stock_location")}
        return _render_pdf(
            "inventory/pdf/sale_invoice.html",
            context,
            f"{sale.invoice_number}.pdf",
        )

    @action(detail=False, methods=["get"], permission_classes=[IsInventoryOrFinanceManager])
    def margin_preview(self, request):
        ser = MarginPreviewSerializer(data=request.query_params)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        result = sales_service.calculate_item_margins(
            book=d["book"],
            quantity=d["quantity"],
            unit_price=d["unit_price"],
            discount_percent=d["discount_percent"],
        )
        return Response(result)


# ---------------------------------------------------------------------------
# Author Payouts
# ---------------------------------------------------------------------------

class AuthorPayoutViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = (
        AuthorPayout.objects
        .select_related("author", "prepared_by", "approved_by")
        .prefetch_related("items__book")
        .all()
    )
    serializer_class = AuthorPayoutSerializer
    permission_classes = [IsFinanceManager]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["author__name"]
    ordering_fields = ["period_end", "total_earnings", "status"]
    ordering = ["-period_end"]
    pagination_class = StandardPagination

    def get_queryset(self):
        qs = super().get_queryset()
        author_id = self.request.query_params.get("author")
        payout_status = self.request.query_params.get("status")
        if author_id:
            qs = qs.filter(author_id=author_id)
        if payout_status:
            qs = qs.filter(status=payout_status)
        return qs

    @action(detail=False, methods=["post"], permission_classes=[IsFinanceManager])
    def generate(self, request):
        ser = GeneratePayoutSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        try:
            payout = payout_service.generate_payout(
                payee=d["author"],
                period_type=d["period_type"],
                period_start=d["period_start"],
                period_end=d["period_end"],
                notes=d.get("notes", ""),
                user=request.user,
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(AuthorPayoutSerializer(payout).data, status=status.HTTP_201_CREATED)

    @action(detail=True, methods=["post"], permission_classes=[IsFinanceManager])
    def approve(self, request, pk=None):
        payout = self.get_object()
        try:
            payout = payout_service.approve_payout(payout=payout, user=request.user)
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(AuthorPayoutSerializer(payout).data)

    @action(detail=True, methods=["post"], permission_classes=[IsFinanceManager])
    def mark_paid(self, request, pk=None):
        payout = self.get_object()
        ser = MarkPayoutPaidSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        try:
            payout = payout_service.mark_payout_paid(
                payout=payout,
                payment_method=d["payment_method"],
                payment_reference=d["payment_reference"],
                payment_date=d["payment_date"],
                amount_paid=d.get("amount_paid"),
                user=request.user,
            )
        except ValueError as exc:
            return Response({"error": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(AuthorPayoutSerializer(payout).data)

    @action(detail=True, methods=["get"], permission_classes=[IsFinanceManager])
    def pdf_statement(self, request, pk=None):
        payout = self.get_object()
        context = {
            "payout": payout,
            "items": payout.items.select_related("book", "sale_item__sale"),
        }
        return _render_pdf(
            "inventory/pdf/author_statement.html",
            context,
            f"statement-{payout.payee.name.replace(' ', '-')}-{payout.period_end}.pdf",
        )

    @action(detail=False, methods=["get"], permission_classes=[IsFinanceManager])
    def pending_summary(self, request):
        return Response(report_service.pending_payouts_by_author())


# ---------------------------------------------------------------------------
# Expenses
# ---------------------------------------------------------------------------

class ExpenseCategoryViewSet(viewsets.ModelViewSet):
    queryset = ExpenseCategory.objects.all()
    serializer_class = ExpenseCategorySerializer
    permission_classes = [IsFinanceManager]
    filter_backends = [SearchFilter]
    search_fields = ["name"]


class ExpenseViewSet(viewsets.ModelViewSet):
    queryset = (
        Expense.objects
        .select_related("category", "linked_book", "linked_event", "linked_sale", "recorded_by")
        .all()
    )
    serializer_class = ExpenseSerializer
    permission_classes = [IsFinanceManager]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["description", "category__name"]
    ordering_fields = ["date", "amount"]
    ordering = ["-date"]
    pagination_class = StandardPagination

    def get_queryset(self):
        qs = super().get_queryset()
        start = self.request.query_params.get("start_date")
        end = self.request.query_params.get("end_date")
        category = self.request.query_params.get("category")
        if start:
            qs = qs.filter(date__gte=start)
        if end:
            qs = qs.filter(date__lte=end)
        if category:
            qs = qs.filter(category_id=category)
        return qs

    def perform_create(self, serializer):
        serializer.save(recorded_by=self.request.user)


# ---------------------------------------------------------------------------
# Reports & Dashboard
# ---------------------------------------------------------------------------

class DashboardView(APIView):
    permission_classes = [IsInventoryOrFinanceManager]

    def get(self, request):
        today = date.today()
        month_start = today.replace(day=1)

        pl = report_service.profit_and_loss(month_start, today)
        top_books = report_service.top_selling_books(month_start, today, limit=5)
        by_channel = report_service.sales_by_channel(month_start, today)
        pending_payouts = report_service.pending_payouts_by_author()
        low_stock = list(
            inventory_service.get_low_stock_books()
            .values("id", "title", "stock", "reorder_level")[:10]
        )

        return Response({
            "period": {"start": month_start, "end": today},
            "profit_and_loss": ProfitLossSerializer(pl).data,
            "top_selling_books": top_books,
            "sales_by_channel": by_channel,
            "pending_author_payouts": pending_payouts,
            "low_stock_alerts": low_stock,
            "inventory_valuation": str(inventory_service.get_inventory_valuation()),
        })


class ProfitLossView(APIView):
    permission_classes = [IsFinanceManager]

    def get(self, request):
        start = request.query_params.get("start_date")
        end = request.query_params.get("end_date")
        if not start or not end:
            today = date.today()
            end = today
            start = today.replace(day=1)
        else:
            from datetime import date as _date
            start = _date.fromisoformat(start)
            end = _date.fromisoformat(end)

        data = report_service.profit_and_loss(start, end)
        return Response(ProfitLossSerializer(data).data)

    @action(detail=False, methods=["get"])
    def pdf(self, request):
        start_str = request.query_params.get("start_date")
        end_str = request.query_params.get("end_date")
        today = date.today()
        start = date.fromisoformat(start_str) if start_str else today.replace(day=1)
        end = date.fromisoformat(end_str) if end_str else today

        data = report_service.profit_and_loss(start, end)
        expenses = report_service.expenses_by_category(start, end)
        by_channel = report_service.sales_by_channel(start, end)
        context = {
            "pl": data,
            "expenses_by_category": expenses,
            "sales_by_channel": by_channel,
            "generated_at": timezone.now(),
        }
        return _render_pdf(
            "inventory/pdf/profit_loss.html",
            context,
            f"PL-{start}-{end}.pdf",
        )


class InventoryReportView(APIView):
    permission_classes = [IsInventoryOrFinanceManager]

    def get(self, request):
        return Response({
            "stock_by_location": report_service.stock_by_location(),
            "valuation_by_book": report_service.inventory_valuation_by_book(),
        })

    def pdf(self, request):
        stock_by_location = {}
        for row in report_service.stock_by_location():
            stock_by_location.setdefault(row["location_name"], []).append(row)

        context = {
            "stock_by_location": stock_by_location,
            "valuation": report_service.inventory_valuation_by_book(),
            "generated_at": timezone.now(),
        }
        return _render_pdf(
            "inventory/pdf/inventory_report.html",
            context,
            f"inventory-report-{date.today()}.pdf",
        )


class SalesReportView(APIView):
    permission_classes = [IsInventoryOrFinanceManager]

    def get(self, request):
        start_str = request.query_params.get("start_date")
        end_str = request.query_params.get("end_date")
        today = date.today()
        start = date.fromisoformat(start_str) if start_str else today.replace(day=1)
        end = date.fromisoformat(end_str) if end_str else today
        granularity = request.query_params.get("granularity", "month")

        return Response({
            "period": {"start": start, "end": end},
            "by_channel": report_service.sales_by_channel(start, end),
            "by_author": report_service.sales_by_author(start, end),
            "by_book": report_service.sales_by_book(start, end),
            "revenue_over_time": report_service.revenue_over_time(start, end, granularity),
        })


class AuthorEarningsView(APIView):
    permission_classes = [IsFinanceManager]

    def get(self, request, author_id):
        author = get_object_or_404(Author, pk=author_id)
        start_str = request.query_params.get("start_date")
        end_str = request.query_params.get("end_date")
        today = date.today()
        start = date.fromisoformat(start_str) if start_str else today.replace(month=1, day=1)
        end = date.fromisoformat(end_str) if end_str else today

        data = report_service.author_earnings_detail(author, start, end)
        return Response({
            "author": {"id": author.pk, "name": author.name},
            "period": {"start": start, "end": end},
            "total_units": data["total_units"],
            "total_net": str(data["total_net"]),
            "total_earnings": str(data["total_earnings"]),
            "outstanding_balance": str(payout_service.get_author_outstanding_balance(author)),
        })

    def pdf(self, request, author_id):
        author = get_object_or_404(Author, pk=author_id)
        payout_id = request.query_params.get("payout_id")
        payout = get_object_or_404(AuthorPayout, pk=payout_id, author=author) if payout_id else None

        if payout:
            context = {"payout": payout, "generated_at": timezone.now()}
        else:
            start_str = request.query_params.get("start_date")
            end_str = request.query_params.get("end_date")
            today = date.today()
            start = date.fromisoformat(start_str) if start_str else today.replace(month=1, day=1)
            end = date.fromisoformat(end_str) if end_str else today
            detail = report_service.author_earnings_detail(author, start, end)
            context = {"detail": detail, "generated_at": timezone.now()}

        return _render_pdf(
            "inventory/pdf/author_statement.html",
            context,
            f"statement-{author.name.replace(' ', '-')}.pdf",
        )


# ---------------------------------------------------------------------------
# Audit Log (read-only)
# ---------------------------------------------------------------------------

class AuditLogViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = AuditLog.objects.select_related("user").all()
    serializer_class = AuditLogSerializer
    permission_classes = [IsSuperAdmin]
    filter_backends = [SearchFilter, OrderingFilter]
    search_fields = ["model_name", "action", "object_repr"]
    ordering_fields = ["timestamp"]
    ordering = ["-timestamp"]
    pagination_class = StandardPagination

    def get_queryset(self):
        qs = super().get_queryset()
        model = self.request.query_params.get("model")
        action = self.request.query_params.get("action")
        if model:
            qs = qs.filter(model_name=model)
        if action:
            qs = qs.filter(action=action)
        return qs
