"""Dict builders — turn model instances into JSON-safe dicts."""
from django.db.models import Sum
from django.utils import timezone

from testing_app.models import Booking
from testing_app.views import ACTIVE_BOOKING_STATES


def dt(value, fmt='%Y-%m-%d %H:%M'):
    if not value:
        return None
    return timezone.localtime(value).strftime(fmt)


def dt_input(value):
    """For <input type=datetime-local> prefill."""
    if not value:
        return ''
    return timezone.localtime(value).strftime('%Y-%m-%dT%H:%M')


def user_dict(u):
    return {
        'id': u.id, 'full_name': u.full_name, 'email': u.email,
        'phone': u.phone, 'role': u.role.name, 'status': u.status,
        'email_verified': u.email_verified,
        'last_login_at': dt(u.last_login_at),
        'created_at': dt(u.created_at),
    }


def operator_dict(op):
    return {
        'id': op.id, 'company_name': op.company_name,
        'registration_number': op.registration_number,
        'verified': op.verified,
        'owner_name': op.user.full_name, 'owner_email': op.user.email,
        'google_only': (op.user.password_hash or '').startswith('!'),
    }


def city_dict(c):
    return {'id': c.id, 'city_name': c.city_name, 'province': c.province,
            'province_label': c.get_province_display()}


def route_dict(r):
    return {
        'id': r.id,
        'source_city_id': r.source_city_id,
        'destination_city_id': r.destination_city_id,
        'source': r.source_city.city_name,
        'destination': r.destination_city.city_name,
        'name': f'{r.source_city.city_name} → {r.destination_city.city_name}',
        'distance_km': float(r.distance_km) if r.distance_km is not None else None,
        'duration_minutes': r.estimated_duration_minutes,
        'is_active': r.is_active,
    }


def bus_dict(b):
    from testing_app.models import BusDocument
    docs = {d.doc_type: d.status for d in b.documents.all()}
    verified_count = sum(1 for t in BusDocument.REQUIRED_DOC_TYPES if docs.get(t) == 'VERIFIED')
    return {
        'id': b.id, 'bus_name': b.bus_name, 'registration_no': b.registration_no,
        'bus_type': b.bus_type, 'total_seats': b.total_seats, 'status': b.status,
        'approval_status': b.approval_status, 'review_note': b.review_note,
        'reviewed_at': dt(b.reviewed_at),
        'operator': b.operator.company_name if b.operator_id else None,
        'operator_id': b.operator_id,
        'has_seat_map': b.has_seat_map, 'deck_count': b.deck_count,
        'seat_rows': b.seat_rows, 'seat_cols': b.seat_cols,
        'amenities': b.amenity_list(),
        'docs_verified_count': verified_count,
        'docs_required_count': len(BusDocument.REQUIRED_DOC_TYPES),
        'is_fully_verified': verified_count >= len(BusDocument.REQUIRED_DOC_TYPES),
        'is_bookable': b.is_bookable(),
    }


def bus_document_dict(request, d):
    return {
        'id': d.id, 'doc_type': d.doc_type, 'status': d.status,
        'file_url': request.build_absolute_uri(d.file.url) if d.file else '',
        'file_name': (d.file.name or '').split('/')[-1] if d.file else '',
        'review_note': d.review_note or '',
        'can_replace': d.can_be_replaced_by_operator(),
        'uploaded_at': dt(d.uploaded_at), 'updated_at': dt(d.updated_at),
    }


def seats_taken(schedule):
    return Booking.objects.filter(
        schedule=schedule, booking_status__in=ACTIVE_BOOKING_STATES
    ).aggregate(n=Sum('seats_count'))['n'] or 0


def schedule_dict(s, include_seats=True):
    d = {
        'id': s.id,
        'bus_id': s.bus_id, 'bus_name': s.bus.bus_name,
        'route_id': s.route_id,
        'route': f'{s.route.source_city.city_name} → {s.route.destination_city.city_name}',
        'departure_time': dt(s.departure_time),
        'arrival_time': dt(s.arrival_time),
        'departure_input': dt_input(s.departure_time),
        'arrival_input': dt_input(s.arrival_time),
        'fare': float(s.fare),
        'status': s.status,
        'total_seats': s.bus.total_seats,
    }
    if include_seats:
        taken = seats_taken(s)
        d['seats_booked'] = taken
        d['seats_left'] = max(0, s.bus.total_seats - taken)
    return d


def booking_dict(b):
    return {
        'id': b.id, 'booking_code': b.booking_code,
        'passenger': b.user.full_name, 'passenger_email': b.user.email,
        'passenger_phone': b.user.phone,
        'schedule_id': b.schedule_id,
        'route': f'{b.schedule.route.source_city.city_name} → {b.schedule.route.destination_city.city_name}',
        'bus_name': b.schedule.bus.bus_name,
        'operator': b.schedule.bus.operator.company_name,
        'departure_time': dt(b.schedule.departure_time),
        'seats_count': b.seats_count,
        'total_amount': float(b.total_amount or 0),
        'commission_amount': float(b.commission_amount or 0),
        'operator_earning': float(b.operator_earning or 0),
        'booking_status': b.booking_status,
        'cancel_reason': b.cancel_reason,
        'booked_at': dt(b.booked_at),
    }


def login_log_dict(l):
    return {
        'id': l.id, 'created_at': dt(l.created_at, '%Y-%m-%d %H:%M:%S'),
        'email': l.email_attempted, 'user': l.user.full_name if l.user_id else None,
        'role': l.role_name, 'status': l.status,
        'ip': l.ip_address, 'message': l.message,
    }


def audit_dict(a):
    return {
        'id': a.id, 'created_at': dt(a.created_at, '%Y-%m-%d %H:%M:%S'),
        'admin': a.admin.full_name if a.admin_id else None,
        'action': a.action, 'target': a.target,
        'reason_code': a.reason_code, 'note': a.note,
    }


def trip_dict(s, seats_left=None, rating_avg=None, rating_count=None):
    """A schedule as a passenger-facing search result / trip detail."""
    bus = s.bus
    taken = seats_taken(s) if seats_left is None else None
    return {
        'schedule_id': s.id,
        'operator_id': bus.operator_id,
        'operator': bus.operator.company_name,
        'bus_id': bus.id,
        'bus_name': bus.bus_name,
        'bus_type': bus.bus_type,
        'amenities': bus.amenity_list(),
        'has_seat_map': bus.has_seat_map,
        'total_seats': bus.total_seats,
        'seats_left': seats_left if seats_left is not None else max(0, bus.total_seats - taken),
        'source_city': s.route.source_city.city_name,
        'destination_city': s.route.destination_city.city_name,
        'route': f'{s.route.source_city.city_name} → {s.route.destination_city.city_name}',
        'distance_km': float(s.route.distance_km) if s.route.distance_km is not None else None,
        'departure_time': dt(s.departure_time),
        'arrival_time': dt(s.arrival_time),
        'departure_date': timezone.localtime(s.departure_time).strftime('%Y-%m-%d'),
        'departure_clock': timezone.localtime(s.departure_time).strftime('%H:%M'),
        'arrival_clock': timezone.localtime(s.arrival_time).strftime('%H:%M'),
        'duration_minutes': int((s.arrival_time - s.departure_time).total_seconds() // 60),
        'fare': float(s.fare),
        'rating_avg': rating_avg,
        'rating_count': rating_count or 0,
        'status': s.status,
        'delay_note': s.delay_note,
    }


def passenger_booking_dict(b):
    """A booking as seen by the passenger who owns it (my-bookings, detail)."""
    seats = list(b.booking_seats.select_related('seat').all()) if b.pk else []
    now = timezone.now()
    return {
        'id': b.id,
        'booking_code': b.booking_code,
        'status': b.booking_status,
        'source_city': b.schedule.route.source_city.city_name,
        'destination_city': b.schedule.route.destination_city.city_name,
        'route': f'{b.schedule.route.source_city.city_name} → {b.schedule.route.destination_city.city_name}',
        'operator': b.schedule.bus.operator.company_name,
        'bus_name': b.schedule.bus.bus_name,
        'bus_type': b.schedule.bus.bus_type,
        'departure_time': dt(b.schedule.departure_time),
        'arrival_time': dt(b.schedule.arrival_time),
        'seats_count': b.seats_count,
        'seat_numbers': [bs.seat.seat_number for bs in seats],
        'total_amount': float(b.total_amount or 0),
        'booked_at': dt(b.booked_at),
        'payment_deadline': dt(b.payment_deadline) if b.payment_deadline else None,
        'cancel_reason': b.cancel_reason,
        'cancelled_by': b.cancelled_by,
        'cancellation_fee_amount': float(b.cancellation_fee_amount or 0),
        'can_pay': b.booking_status == 'PENDING_PAYMENT' and bool(b.payment_deadline) and b.payment_deadline > now,
        'can_cancel': b.booking_status in ('CONFIRMED', 'CHECKED_IN', 'PENDING_PAYMENT')
                      and b.schedule.departure_time > now,
        'can_rate': b.booking_status in ('CONFIRMED', 'CHECKED_IN', 'COMPLETED')
                    and b.schedule.arrival_time <= now and not hasattr(b, 'rating'),
        'already_rated': hasattr(b, 'rating'),
        'has_ticket': b.booking_status in ('CONFIRMED', 'CHECKED_IN', 'COMPLETED'),
    }