from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from rest_framework import generics, mixins, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView

from accounts.permissions import HasPermission
from accounts.services import ManagerApprovalError
from invoices.pdf import generate_invoice_pdf
from reports.export import pdf_response, xlsx_response

from .models import Sale
from .serializers import (
    PublicSaleSerializer,
    SaleCreateSerializer,
    SaleReturnCreateSerializer,
    SaleReturnSerializer,
    SaleSerializer,
)
from .services import (
    ExcessiveReturnError,
    InsufficientStockError,
    PriceRangeError,
    create_return,
    create_sale,
)


class SaleViewSet(
    mixins.CreateModelMixin,
    mixins.ListModelMixin,
    mixins.RetrieveModelMixin,
    viewsets.GenericViewSet,
):
    """Sales are immutable financial records: no update or delete."""

    date_field = 'sold_at'
    query_filters = {'payment_method': 'payment_method', 'sold_by': 'sold_by_id'}
    queryset = Sale.objects.select_related('sold_by').prefetch_related(
        'items__spare_part', 'returns__items__sale_item__spare_part',
    ).all()
    ordering_fields = ('sold_at', 'invoice_number')
    search_fields = ('invoice_number', 'customer_name', 'customer_phone')

    def get_serializer_class(self):
        if self.action == 'create':
            return SaleCreateSerializer
        return SaleSerializer

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'list' and self.request.query_params.get('flagged') == '1':
            queryset = queryset.filter(items__price_alert__gt='').distinct()
        return queryset

    def get_permissions(self):
        if self.action == 'returns':
            return [HasPermission('sales.process_returns')()]
        return super().get_permissions()

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            sale = create_sale(
                sold_by=request.user,
                customer_name=serializer.validated_data.get('customer_name', ''),
                customer_phone=serializer.validated_data.get('customer_phone', ''),
                payment_method=serializer.validated_data['payment_method'],
                items=serializer.validated_data['items'],
                manager_approval=serializer.validated_data.get('manager_approval'),
            )
        except (InsufficientStockError, PriceRangeError, ManagerApprovalError) as exc:
            raise ValidationError({'detail': str(exc)})
        return Response(SaleSerializer(sale).data, status=201)

    @action(detail=True, methods=['get'])
    def invoice(self, request, pk=None):
        sale = self.get_object()
        pdf_bytes = generate_invoice_pdf(sale)
        response = HttpResponse(pdf_bytes, content_type='application/pdf')
        response['Content-Disposition'] = f'inline; filename="{sale.invoice_number}.pdf"'
        return response

    @action(detail=False, methods=['get'])
    def export(self, request):
        fmt = request.query_params.get('type', 'xlsx')
        sales = self.filter_queryset(self.get_queryset())
        show_profit = request.user.has_perm_code('columns.sales.profit')
        headers = ['Invoice #', 'Date', 'Customer', 'Phone', 'Payment', 'Total']
        if show_profit:
            headers.append('Profit')
        headers.append('Sold By')
        rows = []
        for s in sales[:2000]:
            total = sum(i.quantity * i.unit_price for i in s.items.all())
            row = [
                s.invoice_number,
                s.sold_at.strftime('%Y-%m-%d %H:%M'),
                s.customer_name or '',
                s.customer_phone or '',
                s.payment_method,
                float(total),
            ]
            if show_profit:
                profit = sum(i.quantity * (i.unit_price - i.unit_cost) for i in s.items.all())
                row.append(float(profit))
            row.append(s.sold_by.get_full_name() if s.sold_by else '')
            rows.append(row)
        if fmt == 'pdf':
            return pdf_response('Sales Report', headers, rows, 'sales-report')
        return xlsx_response('Sales Report', headers, rows, 'sales-report')

    @action(detail=True, methods=['get', 'post'], url_path='returns')
    def returns(self, request, pk=None):
        sale = self.get_object()
        if request.method == 'GET':
            return Response(SaleReturnSerializer(sale.returns.all(), many=True).data)

        serializer = SaleReturnCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            sale_return = create_return(
                sale=sale,
                processed_by=request.user,
                reason=serializer.validated_data.get('reason', ''),
                items=serializer.validated_data['items'],
            )
        except ExcessiveReturnError as exc:
            raise ValidationError({'detail': str(exc)})
        return Response(SaleReturnSerializer(sale_return).data, status=201)


class PublicInvoiceView(generics.RetrieveAPIView):
    """Unauthenticated, customer-facing invoice lookup by unguessable token (QR/share link)."""

    queryset = Sale.objects.prefetch_related('items__spare_part').all()
    serializer_class = PublicSaleSerializer
    authentication_classes = []
    permission_classes = [AllowAny]
    lookup_field = 'public_token'
    lookup_url_kwarg = 'token'


class PublicInvoicePdfView(APIView):
    """Unauthenticated PDF download for the same public invoice link."""

    authentication_classes = []
    permission_classes = [AllowAny]

    def get(self, request, token=None):
        sale = get_object_or_404(Sale, public_token=token)
        pdf_bytes = generate_invoice_pdf(sale)
        response = HttpResponse(pdf_bytes, content_type='application/pdf')
        response['Content-Disposition'] = f'inline; filename="{sale.invoice_number}.pdf"'
        return response
