"""Ratings, saved addresses, favorites (plan §8–9)."""
from datetime import timedelta

from django.conf import settings as dj_settings
from django.contrib.auth import get_user_model
from django.db.models import Avg, Count, FloatField, Q, Sum
from django.db.models.functions import Coalesce
from django.utils import timezone
from rest_framework import generics
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated

from accounts.models import KYCDocument, UserRole, Vehicle, VehicleDocument
from core.models import Bid, ProofOfDelivery, Trip, TripReview, TripShipperReview, SavedAddress, FavoriteTransporter
from .permissions import IsFleetDriverTransporter, IsIndividualDriverTransporter, IsShipper, IsTransporter
from .response import api_response
from .serializers import (
    IndividualDriverRatingSerializer,
    TripReviewCreateSerializer,
    TripReviewSerializer,
    TripShipperReviewCreateSerializer,
    TripShipperReviewSerializer,
    SavedAddressSerializer,
    FavoriteTransporterSerializer,
)

User = get_user_model()


def _on_time_delivery_stats(trips_qs):
    """Share of eligible completed trips delivered on time (POD + pickup_scheduled_at)."""
    grace_hours = int(getattr(dj_settings, 'ON_TIME_DELIVERY_GRACE_HOURS', 24))
    grace = timedelta(hours=grace_hours)
    on_time = 0
    on_time_eligible = 0
    on_time_insufficient = 0
    for trip in trips_qs.select_related('shipment'):
        try:
            pod = trip.pod
        except ProofOfDelivery.DoesNotExist:
            continue
        sch = trip.shipment.pickup_scheduled_at
        if not sch:
            on_time_insufficient += 1
            continue
        on_time_eligible += 1
        if pod.delivered_at <= sch + grace:
            on_time += 1
    rate = (on_time / on_time_eligible) if on_time_eligible else None
    percentage = round(rate * 100, 2) if rate is not None else None
    return {
        'on_time_delivery_rate': rate,
        'on_time_delivery_percentage': percentage,
        'on_time_deliveries_count': on_time,
        'on_time_eligible_trips_count': on_time_eligible,
        'on_time_insufficient_schedule_count': on_time_insufficient,
        'on_time_definition': (
            'Among completed trips with POD and non-null shipment.pickup_scheduled_at: '
            'share where proof_of_delivery.delivered_at <= pickup_scheduled_at + '
            f'ON_TIME_DELIVERY_GRACE_HOURS ({grace_hours}h).'
        ),
    }


def _individual_driver_trip_qs(user):
    """Trips the individual driver won, was assigned to, or holds the accepted bid for."""
    return Trip.objects.filter(
        Q(transporter=user)
        | Q(assigned_driver=user)
        | Q(accepted_bid__transporter=user)
    ).distinct()


def _individual_driver_reviews_filter(user):
    """Reviews on trips the user participated in (by trip linkage, not review FKs alone)."""
    return Q(trip_id__in=_individual_driver_trip_qs(user).values('id'))


@api_view(['POST'])
@permission_classes([IsAuthenticated, IsShipper])
def shipper_submit_trip_review(request, trip_pk):
    try:
        trip = Trip.objects.select_related('shipment').get(pk=trip_pk, shipment__shipper=request.user)
    except Trip.DoesNotExist:
        return api_response(404, 'Trip not found.', error='Not found.')
    if trip.status not in (Trip.Status.DELIVERED, Trip.Status.COMPLETED, Trip.Status.CLOSED):
        return api_response(400, 'Trip must be delivered or completed before review.', error='Invalid trip status.')
    if TripReview.objects.filter(trip=trip).exists():
        return api_response(400, 'Review already submitted.', error='Duplicate.')
    ser = TripReviewCreateSerializer(data=request.data)
    if not ser.is_valid():
        return api_response(400, 'Validation failed.', error=ser.errors)
    transporter = trip.transporter
    driver_user = trip.assigned_driver or trip.transporter
    did = ser.validated_data.get('driver_id')
    if did is not None:
        try:
            driver_user = User.objects.get(pk=did)
        except User.DoesNotExist:
            return api_response(400, 'Invalid driver_id.', error='Invalid driver.')
    rev = TripReview.objects.create(
        trip=trip,
        shipper=request.user,
        transporter=transporter,
        driver=driver_user,
        transporter_rating=ser.validated_data['transporter_rating'],
        driver_rating=ser.validated_data.get('driver_rating'),
        comment=ser.validated_data.get('comment') or '',
    )
    return api_response(201, 'Review submitted successfully.', data=TripReviewSerializer(rev).data)


@api_view(['GET'])
@permission_classes([IsAuthenticated, IsShipper])
def shipper_transporter_reviews(request, transporter_id):
    try:
        u = User.objects.get(pk=transporter_id)
    except User.DoesNotExist:
        return api_response(404, 'User not found.', error='Not found.')
    try:
        if u.role.role != UserRole.Role.TRANSPORTER:
            return api_response(400, 'User is not a transporter.', error='Invalid role.')
    except Exception:
        return api_response(400, 'User is not a transporter.', error='Invalid role.')
    qs = TripReview.objects.filter(transporter=u).order_by('-created_at')[:50]
    return api_response(200, 'Reviews retrieved successfully.', data=TripReviewSerializer(qs, many=True).data)


def _submit_trip_shipper_review(request, trip_pk, *, access):
    """
    access: 'participant' (transporter or assigned driver), 'assigned_driver', or 'transporter'.
    """
    try:
        trip = Trip.objects.select_related('shipment', 'shipment__shipper', 'transporter', 'assigned_driver').get(
            pk=trip_pk
        )
    except Trip.DoesNotExist:
        return api_response(404, 'Trip not found.', error='Not found.')

    if access == 'assigned_driver':
        if trip.assigned_driver_id != request.user.id:
            return api_response(404, 'Trip not found.', error='Not found.')
    elif access == 'transporter':
        if trip.transporter_id != request.user.id:
            return api_response(404, 'Trip not found.', error='Not found.')
    elif request.user.id not in (trip.transporter_id, trip.assigned_driver_id):
        return api_response(404, 'Trip not found.', error='Not found.')

    if trip.status not in (Trip.Status.DELIVERED, Trip.Status.COMPLETED, Trip.Status.CLOSED):
        return api_response(400, 'Trip must be delivered or completed before review.', error='Invalid trip status.')
    if TripShipperReview.objects.filter(trip=trip).exists():
        return api_response(400, 'Review already submitted.', error='Duplicate.')

    ser = TripShipperReviewCreateSerializer(data=request.data)
    if not ser.is_valid():
        return api_response(400, 'Validation failed.', error=ser.errors)

    rev = TripShipperReview.objects.create(
        trip=trip,
        shipper=trip.shipment.shipper,
        transporter=trip.transporter,
        reviewer=request.user,
        shipper_rating=ser.validated_data['shipper_rating'],
        comment=ser.validated_data.get('comment') or '',
    )
    return api_response(201, 'Review submitted successfully.', data=TripShipperReviewSerializer(rev).data)


@api_view(['POST'])
@permission_classes([IsAuthenticated, IsTransporter])
def transporter_submit_shipper_review(request, trip_pk):
    """Fleet owner, individual driver, or assigned fleet driver on the trip."""
    return _submit_trip_shipper_review(request, trip_pk, access='participant')


@api_view(['POST'])
@permission_classes([IsAuthenticated, IsFleetDriverTransporter])
def fleet_driver_submit_shipper_review(request, trip_pk):
    """Fleet driver rates the shipper for a trip they are assigned to."""
    return _submit_trip_shipper_review(request, trip_pk, access='assigned_driver')


@api_view(['POST'])
@permission_classes([IsAuthenticated, IsIndividualDriverTransporter])
def individual_driver_submit_shipper_review(request, trip_pk):
    """Individual driver rates the shipper for their own trip."""
    return _submit_trip_shipper_review(request, trip_pk, access='transporter')


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def transporter_public_profile(request, transporter_id):
    try:
        transporter = User.objects.select_related('role', 'transporter_profile').get(pk=transporter_id)
    except User.DoesNotExist:
        return api_response(404, 'Transporter not found.', error='Not found.')

    try:
        if transporter.role.role != UserRole.Role.TRANSPORTER:
            return api_response(400, 'User is not a transporter.', error='Invalid role.')
    except Exception:
        return api_response(400, 'User is not a transporter.', error='Invalid role.')

    profile = getattr(transporter, 'transporter_profile', None)

    vehicle_q = Vehicle.objects.filter(
        Q(owner=transporter) | Q(assigned_driver=transporter),
        is_active=True,
        is_verified=True,
    ).order_by('-updated_at', '-created_at')
    current_vehicle = vehicle_q.first()

    review_qs = TripReview.objects.filter(transporter=transporter).order_by('-created_at')
    review_agg = review_qs.aggregate(
        review_count=Count('id'),
        rating_sum=Sum('transporter_rating'),
        average_rating=Avg('transporter_rating'),
    )

    recent = []
    for row in review_qs.select_related('shipper')[:5]:
        shipper_name = f'{row.shipper.first_name} {row.shipper.last_name}'.strip() or row.shipper.email
        recent.append(
            {
                'id': row.id,
                'rating': row.transporter_rating,
                'comment': row.comment or '',
                'shipper_name': shipper_name,
                'created_at': row.created_at,
            }
        )

    return api_response(
        200,
        'Transporter profile retrieved successfully.',
        data={
            'transporter': {
                'id': transporter.id,
                'email': transporter.email,
                'first_name': transporter.first_name,
                'last_name': transporter.last_name,
                'phone': getattr(getattr(transporter, 'role', None), 'phone', '') or '',
                'company_name': profile.company_name if profile else '',
                'documents_verified': bool(profile.documents_verified) if profile else False,
            },
            'current_vehicle': (
                {
                    'id': current_vehicle.id,
                    'vehicle_type': current_vehicle.vehicle_type,
                    'registration_number': current_vehicle.registration_number,
                    'is_active': current_vehicle.is_active,
                    'is_verified': current_vehicle.is_verified,
                }
                if current_vehicle
                else None
            ),
            'ratings': {
                'review_count': review_agg['review_count'] or 0,
                'rating_sum': int(review_agg['rating_sum'] or 0),
                'average_rating': (
                    round(float(review_agg['average_rating']), 2)
                    if review_agg['average_rating'] is not None
                    else None
                ),
                'recent_reviews': recent,
            },
        },
    )


@api_view(['GET'])
@permission_classes([IsAuthenticated, IsTransporter])
def transporter_ratings_summary(request):
    agg = TripReview.objects.filter(transporter=request.user).aggregate(
        avg_t=Avg('transporter_rating'),
        cnt=Count('id'),
    )
    return api_response(
        200,
        'Rating summary retrieved successfully.',
        data={
            'average_transporter_rating': float(agg['avg_t']) if agg['avg_t'] is not None else None,
            'review_count': agg['cnt'],
        },
    )


@api_view(['GET'])
@permission_classes([IsAuthenticated, IsTransporter])
def transporter_ratings_received(request):
    qs = TripReview.objects.filter(transporter=request.user).order_by('-created_at')[:100]
    return api_response(200, 'Reviews retrieved successfully.', data=TripReviewSerializer(qs, many=True).data)


@api_view(['GET'])
@permission_classes([IsAuthenticated, IsIndividualDriverTransporter])
def individual_driver_stats(request):
    """Performance summary for individual drivers (not fleet-linked)."""
    user = request.user
    completed_trips = Trip.objects.filter(transporter=user, status=Trip.Status.COMPLETED)
    completed_trips_count = completed_trips.count()

    reviews_filter = _individual_driver_reviews_filter(user)
    review_agg = TripReview.objects.filter(reviews_filter).aggregate(
        avg_rating=Avg(
            Coalesce('driver_rating', 'transporter_rating', output_field=FloatField()),
        ),
        review_count=Count('id'),
    )
    review_qs = (
        TripReview.objects.filter(reviews_filter)
        .select_related('shipper', 'trip', 'trip__shipment')
        .order_by('-created_at')
        .distinct()
    )
    average_rating = review_agg['avg_rating']
    on_time = _on_time_delivery_stats(completed_trips)

    return api_response(
        200,
        'Driver stats retrieved successfully.',
        data={
            'completed_trips_count': completed_trips_count,
            'average_rating': round(float(average_rating), 2) if average_rating is not None else None,
            'review_count': review_agg['review_count'] or 0,
            'on_time_delivery_percentage': on_time['on_time_delivery_percentage'],
            'on_time_deliveries_count': on_time['on_time_deliveries_count'],
            'on_time_eligible_trips_count': on_time['on_time_eligible_trips_count'],
            'on_time_insufficient_schedule_count': on_time['on_time_insufficient_schedule_count'],
            'on_time_definition': on_time['on_time_definition'],
            'ratings': IndividualDriverRatingSerializer(review_qs, many=True).data,
        },
    )


class SavedAddressListCreate(generics.ListCreateAPIView):
    permission_classes = [IsAuthenticated, IsShipper]
    serializer_class = SavedAddressSerializer

    def get_queryset(self):
        return SavedAddress.objects.filter(user=self.request.user)

    def list(self, request, *args, **kwargs):
        ser = self.get_serializer(self.get_queryset(), many=True)
        return api_response(200, 'Addresses retrieved successfully.', data=ser.data)

    def create(self, request, *args, **kwargs):
        ser = self.get_serializer(data=request.data)
        if not ser.is_valid():
            return api_response(400, 'Validation failed.', error=ser.errors)
        obj = ser.save(user=request.user)
        if obj.is_default:
            SavedAddress.objects.filter(user=request.user, is_default=True).exclude(pk=obj.pk).update(is_default=False)
        return api_response(201, 'Address saved successfully.', data=self.get_serializer(obj).data)


class SavedAddressDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [IsAuthenticated, IsShipper]
    serializer_class = SavedAddressSerializer

    def get_queryset(self):
        return SavedAddress.objects.filter(user=self.request.user)

    def retrieve(self, request, *args, **kwargs):
        obj = self.get_object()
        return api_response(200, 'Address retrieved successfully.', data=self.get_serializer(obj).data)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', True)
        obj = self.get_object()
        ser = self.get_serializer(obj, data=request.data, partial=partial)
        if not ser.is_valid():
            return api_response(400, 'Validation failed.', error=ser.errors)
        updated = ser.save()
        if updated.is_default:
            SavedAddress.objects.filter(user=request.user, is_default=True).exclude(pk=updated.pk).update(is_default=False)
        return api_response(200, 'Address updated successfully.', data=self.get_serializer(updated).data)

    def destroy(self, request, *args, **kwargs):
        self.get_object().delete()
        return api_response(200, 'Address deleted successfully.')


class FavoriteTransporterListCreate(generics.ListCreateAPIView):
    permission_classes = [IsAuthenticated, IsShipper]
    serializer_class = FavoriteTransporterSerializer

    def get_queryset(self):
        return FavoriteTransporter.objects.filter(shipper=self.request.user).select_related('transporter')

    def list(self, request, *args, **kwargs):
        ser = self.get_serializer(self.get_queryset(), many=True)
        return api_response(200, 'Favorites retrieved successfully.', data=ser.data)

    def create(self, request, *args, **kwargs):
        tid = request.data.get('transporter')
        if not tid:
            return api_response(400, 'transporter user id is required.', error='Missing transporter.')
        try:
            u = User.objects.get(pk=int(tid))
        except (User.DoesNotExist, TypeError, ValueError):
            return api_response(404, 'Transporter not found.', error='Not found.')
        try:
            if u.role.role != UserRole.Role.TRANSPORTER:
                return api_response(400, 'User is not a transporter.', error='Invalid role.')
        except Exception:
            return api_response(400, 'User is not a transporter.', error='Invalid role.')
        obj, created = FavoriteTransporter.objects.get_or_create(shipper=request.user, transporter=u)
        code = 201 if created else 200
        msg = 'Favorite added successfully.' if created else 'Favorite already exists.'
        return api_response(code, msg, data=self.get_serializer(obj).data)


@api_view(['DELETE'])
@permission_classes([IsAuthenticated, IsShipper])
def favorite_transporter_delete(request, pk):
    deleted, _ = FavoriteTransporter.objects.filter(pk=pk, shipper=request.user).delete()
    if not deleted:
        return api_response(404, 'Favorite not found.', error='Not found.')
    return api_response(200, 'Favorite removed successfully.')


@api_view(['GET'])
@permission_classes([IsAuthenticated, IsTransporter])
def transporter_analytics(request):
    window_days = int(getattr(dj_settings, 'ANALYTICS_TRANSPORTER_BID_WINDOW_DAYS', 90))
    since = timezone.now() - timedelta(days=window_days)
    bids = Bid.objects.filter(transporter=request.user, created_at__gte=since)
    total_bids = bids.count()
    accepted = bids.filter(status=Bid.Status.ACCEPTED).count()
    acceptance_rate = (accepted / total_bids) if total_bids else None

    agg = TripReview.objects.filter(transporter=request.user).aggregate(
        avg_t=Avg('transporter_rating'),
        cnt=Count('id'),
    )

    completed_trips = Trip.objects.filter(
        transporter=request.user,
        status__in=(Trip.Status.DELIVERED, Trip.Status.COMPLETED, Trip.Status.CLOSED),
        updated_at__gte=since,
    )
    on_time = _on_time_delivery_stats(completed_trips)

    return api_response(
        200,
        'Analytics retrieved successfully.',
        data={
            'window_days': window_days,
            'average_transporter_rating': float(agg['avg_t']) if agg['avg_t'] is not None else None,
            'review_count': agg['cnt'],
            'bids_submitted_count': total_bids,
            'bids_accepted_count': accepted,
            'acceptance_rate': acceptance_rate,
            'on_time_delivery_rate': on_time['on_time_delivery_rate'],
            'on_time_denominator_trips_with_schedule_and_pod': on_time['on_time_eligible_trips_count'],
            'on_time_insufficient_schedule_count': on_time['on_time_insufficient_schedule_count'],
            'on_time_definition': on_time['on_time_definition'],
        },
    )


@api_view(['GET'])
@permission_classes([IsAuthenticated, IsTransporter])
def transporter_compliance_summary(request):
    today = timezone.localdate()
    horizon_days = int(getattr(dj_settings, 'DOCUMENT_REMINDER_DAYS', 30))
    horizon = today + timedelta(days=horizon_days)

    def _counts(qs):
        expiring = qs.filter(expiry_date__gte=today, expiry_date__lte=horizon).count()
        expired = qs.filter(expiry_date__lt=today).count()
        missing = qs.filter(expiry_date__isnull=True).count()
        return {'expiring_within_window': expiring, 'expired': expired, 'no_expiry_set': missing}

    kyc = KYCDocument.objects.filter(user=request.user)
    vdoc = VehicleDocument.objects.filter(vehicle__owner=request.user)
    return api_response(
        200,
        'Compliance summary retrieved successfully.',
        data={
            'kyc_documents': _counts(kyc),
            'vehicle_documents': _counts(vdoc),
            'document_reminder_horizon_days': horizon_days,
        },
    )
