from __future__ import annotations

from rest_framework import viewsets, status
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from django.utils import timezone
from django.contrib.auth import get_user_model

from .models import (
    Organization,
    MaterialCategory,
    Material,
    PriceList,
    PriceListItem,
    Customer,
    CustomerSite,
    ServiceAgreement,
    PickupOrder,
    PickupItem,
    WeighTicket,
    WeighLine,
    Invoice,
    InvoiceLine,
    Payment,
    Payout,
    ScrapListing,
    ScrapListingItem,
    ScrapBid,
    ScrapAward,
    ScrapListingInvite,
    Document,
    AuditLog,
)
from .serializers import (
    OrganizationSerializer,
    MaterialCategorySerializer,
    MaterialSerializer,
    PriceListSerializer,
    PriceListItemSerializer,
    CustomerSerializer,
    CustomerSiteSerializer,
    ServiceAgreementSerializer,
    PickupOrderSerializer,
    PickupItemSerializer,
    WeighTicketSerializer,
    WeighLineSerializer,
    InvoiceSerializer,
    InvoiceLineSerializer,
    PaymentSerializer,
    PayoutSerializer,
    ScrapListingSerializer,
    ScrapListingItemSerializer,
    ScrapBidSerializer,
    ScrapAwardSerializer,
    ScrapListingInviteSerializer,
    DocumentSerializer,
    AuditLogSerializer,
)
from .services.billing import generate_invoice_for_pickup
from .services.audit import log_action


User = get_user_model()


class BaseViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend]


class OrganizationViewSet(BaseViewSet):
    queryset = Organization.objects.all()
    serializer_class = OrganizationSerializer
    filterset_fields = {"name": ["icontains", "exact"], "code": ["exact"]}


class MaterialCategoryViewSet(BaseViewSet):
    queryset = MaterialCategory.objects.all()
    serializer_class = MaterialCategorySerializer
    filterset_fields = {"organization": ["exact"], "name": ["icontains", "exact"]}


class MaterialViewSet(BaseViewSet):
    queryset = Material.objects.all()
    serializer_class = MaterialSerializer
    filterset_fields = {"organization": ["exact"], "category": ["exact"], "name": ["icontains", "exact"]}


class PriceListViewSet(BaseViewSet):
    queryset = PriceList.objects.all()
    serializer_class = PriceListSerializer
    filterset_fields = {"organization": ["exact"], "name": ["icontains", "exact"], "currency_code": ["exact"]}


class PriceListItemViewSet(BaseViewSet):
    queryset = PriceListItem.objects.all()
    serializer_class = PriceListItemSerializer
    filterset_fields = {"price_list": ["exact"], "material": ["exact"], "direction": ["exact"]}


class CustomerViewSet(BaseViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer
    filterset_fields = {"organization": ["exact"], "name": ["icontains", "exact"], "email": ["icontains", "exact"]}


class CustomerSiteViewSet(BaseViewSet):
    queryset = CustomerSite.objects.all()
    serializer_class = CustomerSiteSerializer
    filterset_fields = {"customer": ["exact"], "name": ["icontains", "exact"]}


class ServiceAgreementViewSet(BaseViewSet):
    queryset = ServiceAgreement.objects.all()
    serializer_class = ServiceAgreementSerializer
    filterset_fields = {"customer": ["exact"], "site": ["exact"], "price_list": ["exact"]}


class PickupOrderViewSet(BaseViewSet):
    queryset = PickupOrder.objects.select_related("customer", "site", "assigned_driver")
    serializer_class = PickupOrderSerializer
    filterset_fields = {
        "organization": ["exact"],
        "customer": ["exact"],
        "site": ["exact"],
        "status": ["exact"],
        "scheduled_at": ["gte", "lte"],
        "assigned_driver": ["exact"],
    }

    @action(detail=True, methods=["post"])
    def assign(self, request, pk=None):
        pickup = self.get_object()
        driver_id = request.data.get("driver_id")
        if not driver_id:
            return Response({"detail": "driver_id required"}, status=status.HTTP_400_BAD_REQUEST)
        try:
            driver = User.objects.get(id=driver_id)
        except User.DoesNotExist:
            return Response({"detail": "Driver not found"}, status=status.HTTP_404_NOT_FOUND)
        pickup.assigned_driver = driver
        pickup.status = PickupOrder.STATUS_SCHEDULED
        pickup.save()
        try:
            log_action(
                organization=pickup.organization,
                user=request.user,
                action="pickup.assign",
                obj=pickup,
                metadata={"driver_id": driver.id},
            )
        except Exception:
            pass
        return Response(self.get_serializer(pickup).data)

    @action(detail=True, methods=["post"])
    def set_status(self, request, pk=None):
        pickup = self.get_object()
        status_val = request.data.get("status")
        valid = dict(PickupOrder.STATUS_CHOICES)
        if status_val not in valid:
            return Response({"detail": "invalid status"}, status=status.HTTP_400_BAD_REQUEST)
        pickup.status = status_val
        if status_val == PickupOrder.STATUS_COMPLETED:
            pickup.completed_at = timezone.now()
        pickup.save()
        try:
            log_action(
                organization=pickup.organization,
                user=request.user,
                action="pickup.set_status",
                obj=pickup,
                metadata={"status": status_val},
            )
        except Exception:
            pass
        return Response(self.get_serializer(pickup).data)


class PickupItemViewSet(BaseViewSet):
    queryset = PickupItem.objects.all()
    serializer_class = PickupItemSerializer
    filterset_fields = {"pickup": ["exact"], "material": ["exact"]}


class WeighTicketViewSet(BaseViewSet):
    queryset = WeighTicket.objects.select_related("pickup")
    serializer_class = WeighTicketSerializer
    filterset_fields = {"pickup": ["exact"], "recorded_at": ["gte", "lte"]}

    @action(detail=True, methods=["post"])
    def generate_invoice(self, request, pk=None):
        ticket = self.get_object()
        try:
            invoice = generate_invoice_for_pickup(ticket.pickup)
        except ValueError as e:
            return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
        try:
            log_action(
                organization=ticket.pickup.organization,
                user=request.user,
                action="invoice.generated",
                obj=invoice,
                metadata={"pickup_id": ticket.pickup_id},
            )
        except Exception:
            pass
        return Response(InvoiceSerializer(invoice).data)


class WeighLineViewSet(BaseViewSet):
    queryset = WeighLine.objects.select_related("ticket", "material")
    serializer_class = WeighLineSerializer
    filterset_fields = {"ticket": ["exact"], "material": ["exact"]}


class InvoiceViewSet(BaseViewSet):
    queryset = Invoice.objects.select_related("customer", "pickup")
    serializer_class = InvoiceSerializer
    filterset_fields = {"organization": ["exact"], "customer": ["exact"], "status": ["exact"], "currency_code": ["exact"]}


class InvoiceLineViewSet(BaseViewSet):
    queryset = InvoiceLine.objects.select_related("invoice")
    serializer_class = InvoiceLineSerializer
    filterset_fields = {"invoice": ["exact"], "material": ["exact"]}


class PaymentViewSet(BaseViewSet):
    queryset = Payment.objects.select_related("invoice")
    serializer_class = PaymentSerializer
    filterset_fields = {"invoice": ["exact"], "received_at": ["gte", "lte"]}

    def perform_create(self, serializer):
        payment = serializer.save()
        try:
            org = payment.invoice.organization if payment.invoice_id else None
            if org:
                log_action(
                    organization=org,
                    user=self.request.user,
                    action="payment.create",
                    obj=payment,
                    metadata={"invoice_id": payment.invoice_id, "amount": str(payment.amount)},
                )
        except Exception:
            pass


class PayoutViewSet(BaseViewSet):
    queryset = Payout.objects.select_related("customer", "pickup")
    serializer_class = PayoutSerializer
    filterset_fields = {"organization": ["exact"], "customer": ["exact"], "paid_at": ["gte", "lte"]}

    def perform_create(self, serializer):
        payout = serializer.save()
        try:
            log_action(
                organization=payout.organization,
                user=self.request.user,
                action="payout.create",
                obj=payout,
                metadata={"amount": str(payout.amount), "customer_id": payout.customer_id},
            )
        except Exception:
            pass


class ScrapListingViewSet(BaseViewSet):
    queryset = ScrapListing.objects.select_related("customer", "site", "organization", "created_by")
    serializer_class = ScrapListingSerializer
    filterset_fields = {
        "organization": ["exact"],
        "customer": ["exact"],
        "status": ["exact"],
        "starts_at": ["gte", "lte"],
        "ends_at": ["gte", "lte"],
    }

    @action(detail=True, methods=["post"])
    def open(self, request, pk=None):
        listing = self.get_object()
        # Only creator's org managers/owners/customers can open
        self._require_listing_org_manager(request.user, listing)
        listing.status = ScrapListing.STATUS_OPEN
        listing.starts_at = listing.starts_at or timezone.now()
        listing.save(update_fields=["status", "starts_at"])
        try:
            log_action(
                organization=listing.organization,
                user=request.user,
                action="listing.open",
                obj=listing,
            )
        except Exception:
            pass
        return Response(self.get_serializer(listing).data)

    @action(detail=True, methods=["post"])
    def close(self, request, pk=None):
        listing = self.get_object()
        self._require_listing_org_manager(request.user, listing)
        listing.status = ScrapListing.STATUS_CLOSED
        listing.ends_at = listing.ends_at or timezone.now()
        listing.save(update_fields=["status", "ends_at"])
        try:
            log_action(
                organization=listing.organization,
                user=request.user,
                action="listing.close",
                obj=listing,
            )
        except Exception:
            pass
        return Response(self.get_serializer(listing).data)

    def _require_listing_org_manager(self, user, listing: ScrapListing):
        from django.core.exceptions import PermissionDenied
        profile = getattr(user, "recycle_profile", None)
        if not profile or profile.organization_id != listing.organization_id or profile.role not in ("owner", "manager", "customer"):
            raise PermissionDenied("Not allowed for this listing")

    def perform_create(self, serializer):
        profile = getattr(self.request.user, "recycle_profile", None)
        if not profile or profile.role not in ("owner", "manager", "customer"):
            raise ValidationError({"detail": "Only organization owner/manager/customer can create listings"})
        serializer.save(created_by=self.request.user)


class ScrapListingItemViewSet(BaseViewSet):
    queryset = ScrapListingItem.objects.select_related("listing", "material")
    serializer_class = ScrapListingItemSerializer
    filterset_fields = {"listing": ["exact"], "material": ["exact"]}


class ScrapBidViewSet(BaseViewSet):
    queryset = ScrapBid.objects.select_related("listing", "bidder_org", "bidder_user")
    serializer_class = ScrapBidSerializer
    filterset_fields = {"listing": ["exact"], "bidder_org": ["exact"], "status": ["exact"]}

    def perform_create(self, serializer):
        listing = serializer.validated_data.get("listing")
        if listing.status != ScrapListing.STATUS_OPEN:
            raise ValidationError({"detail": "Listing is not open for bids"})
        if listing.ends_at and timezone.now() > listing.ends_at:
            raise ValidationError({"detail": "Listing has ended"})
        # Only invited orgs if not public
        bidder_org = serializer.validated_data.get("bidder_org")
        if not listing.is_public:
            invited = ScrapListingInvite.objects.filter(listing=listing, invited_org=bidder_org).exists()
            if not invited:
                raise ValidationError({"detail": "Your organization is not invited to bid on this listing"})
        # minimal min_increment logic
        if listing.min_increment:
            top = (
                ScrapBid.objects.filter(listing=listing, status=ScrapBid.STATUS_ACTIVE)
                .order_by("-price_total")
                .values_list("price_total", flat=True)
                .first()
            )
            if top is not None and serializer.validated_data.get("price_total") < top + listing.min_increment:
                raise ValidationError({"price_total": "Must exceed current top by min_increment"})
        # Ensure bidder user belongs to bidder org and has role
        profile = getattr(self.request.user, "recycle_profile", None)
        if not profile or profile.organization_id != bidder_org.id or profile.role not in ("owner", "manager"):
            raise ValidationError({"detail": "You must be an owner/manager of the bidder organization"})
        serializer.save(bidder_user=self.request.user)

    @action(detail=True, methods=["post"])
    def retract(self, request, pk=None):
        bid = self.get_object()
        if bid.status != ScrapBid.STATUS_ACTIVE:
            return Response({"detail": "Only active bids can be retracted"}, status=status.HTTP_400_BAD_REQUEST)
        bid.status = ScrapBid.STATUS_RETRACTED
        bid.save(update_fields=["status"])
        return Response(self.get_serializer(bid).data)


class ScrapAwardViewSet(BaseViewSet):
    queryset = ScrapAward.objects.select_related("listing", "winning_bid", "pickup")
    serializer_class = ScrapAwardSerializer
    filterset_fields = {"listing": ["exact"]}

    @action(detail=False, methods=["post"], url_path="award-listing")
    def award_listing(self, request):
        listing_id = request.data.get("listing_id")
        if not listing_id:
            return Response({"detail": "listing_id required"}, status=status.HTTP_400_BAD_REQUEST)
        try:
            listing = ScrapListing.objects.get(id=listing_id)
        except ScrapListing.DoesNotExist:
            return Response({"detail": "Listing not found"}, status=status.HTTP_404_NOT_FOUND)
        # Only listing org owner/manager can award
        profile = getattr(request.user, "recycle_profile", None)
        if not profile or profile.organization_id != listing.organization_id or profile.role not in ("owner", "manager"):
            return Response({"detail": "Not allowed"}, status=status.HTTP_403_FORBIDDEN)
        if listing.status not in (ScrapListing.STATUS_OPEN, ScrapListing.STATUS_CLOSED):
            return Response({"detail": "Listing must be open or closed to award"}, status=status.HTTP_400_BAD_REQUEST)
        # Pick highest active bid meeting reserve
        top_bid = (
            ScrapBid.objects.filter(listing=listing, status=ScrapBid.STATUS_ACTIVE)
            .order_by("-price_total")
            .first()
        )
        if not top_bid:
            return Response({"detail": "No bids to award"}, status=status.HTTP_400_BAD_REQUEST)
        if listing.reserve_price and top_bid.price_total < listing.reserve_price:
            return Response({"detail": "Top bid below reserve price"}, status=status.HTTP_400_BAD_REQUEST)
        # Mark win/lose
        ScrapBid.objects.filter(listing=listing, status=ScrapBid.STATUS_ACTIVE).exclude(id=top_bid.id).update(status=ScrapBid.STATUS_REJECTED)
        top_bid.status = ScrapBid.STATUS_ACCEPTED
        top_bid.save(update_fields=["status"])
        listing.status = ScrapListing.STATUS_AWARDED
        listing.save(update_fields=["status"])
        # Optionally create a pickup order for the seller
        pickup = PickupOrder.objects.create(
            organization=listing.organization,
            customer=listing.customer,
            site=listing.site,
            status=PickupOrder.STATUS_SCHEDULED,
            notes=f"Awarded to org {top_bid.bidder_org_id} for listing {listing.id}",
        )
        award = ScrapAward.objects.create(listing=listing, winning_bid=top_bid, pickup=pickup)
        try:
            log_action(
                organization=listing.organization,
                user=request.user,
                action="listing.award",
                obj=award,
                metadata={"winning_bid_id": top_bid.id, "pickup_id": pickup.id},
            )
        except Exception:
            pass
        return Response(self.get_serializer(award).data, status=status.HTTP_201_CREATED)


class ScrapListingInviteViewSet(BaseViewSet):
    queryset = ScrapListingInvite.objects.select_related("listing", "invited_org", "invited_user")
    serializer_class = ScrapListingInviteSerializer
    filterset_fields = {"listing": ["exact"], "invited_org": ["exact"]}


class DocumentViewSet(BaseViewSet):
    queryset = Document.objects.select_related("organization")
    serializer_class = DocumentSerializer
    filterset_fields = {
        "organization": ["exact"],
        "kind": ["exact", "icontains"],
        "content_type": ["exact"],
        "object_id": ["exact"],
        "uploaded_by": ["exact"],
    }

    def perform_create(self, serializer):
        doc = serializer.save(uploaded_by=self.request.user)
        try:
            log_action(
                organization=doc.organization,
                user=self.request.user,
                action="document.upload",
                obj=doc,
                metadata={"kind": doc.kind},
            )
        except Exception:
            pass


class AuditLogViewSet(BaseViewSet):
    queryset = AuditLog.objects.select_related("organization", "user")
    serializer_class = AuditLogSerializer
    filterset_fields = {
        "organization": ["exact"],
        "user": ["exact"],
        "action": ["exact", "icontains"],
        "content_type": ["exact"],
        "object_id": ["exact"],
        "created_at": ["gte", "lte"],
    }
