from django.db import IntegrityError, models, transaction
from django.conf import settings

from core.countries import COUNTRY_CHOICES


# Legacy model (keep for existing migration)
class Shipper(models.Model):
    name = models.CharField(max_length=255)
    email = models.EmailField(blank=True, null=True, max_length=254)
    phone = models.CharField(blank=True, null=True, max_length=50)
    company = models.CharField(blank=True, null=True, max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)


SHIPMENT_UNIQUE_ID_PREFIX = 'SH-'


def format_shipment_unique_id(sequence: int) -> str:
    return f'{SHIPMENT_UNIQUE_ID_PREFIX}{sequence:05d}'


def _max_shipment_unique_id_sequence(unique_ids) -> int:
    max_seq = 0
    prefix_len = len(SHIPMENT_UNIQUE_ID_PREFIX)
    for uid in unique_ids:
        if not uid or not uid.startswith(SHIPMENT_UNIQUE_ID_PREFIX):
            continue
        suffix = uid[prefix_len:]
        if suffix.isdigit():
            max_seq = max(max_seq, int(suffix))
    return max_seq


class Shipment(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'DRAFT', 'Draft'
        PUBLISHED = 'PUBLISHED', 'Published'
        ASSIGNED = 'ASSIGNED', 'Assigned'
        EN_ROUTE = 'EN_ROUTE', 'En Route'
        LOADED = 'LOADED', 'Loaded'
        IN_TRANSIT = 'IN_TRANSIT', 'In Transit'
        DELIVERED = 'DELIVERED', 'Delivered'
        COMPLETED = 'COMPLETED', 'Completed'
        CLOSED = 'CLOSED', 'Closed'

    shipper = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='shipments'
    )
    pickup_address = models.CharField(max_length=500)
    pickup_company = models.CharField(max_length=255, blank=True)
    pickup_lat = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    pickup_lon = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    delivery_address = models.CharField(max_length=500)
    dropoff_company = models.CharField(max_length=255, blank=True)
    delivery_lat = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    delivery_lon = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    cargo_type = models.CharField(max_length=100)
    weight = models.CharField(max_length=50)  # e.g. "500 kg"
    dimensions = models.CharField(max_length=100, blank=True)  # e.g. "2x2x2 m"
    vehicle_type_required = models.CharField(max_length=100)
    special_instructions = models.TextField(blank=True)
    luggage_image_url = models.URLField(
        max_length=500,
        blank=True,
        help_text='Absolute URL of the luggage/cargo photo for this shipment.',
    )
    distance_km = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    suggested_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    currency = models.CharField(
        max_length=3,
        blank=True,
        help_text='ISO 4217 currency for suggested_price; set from pickup country.',
    )
    price_breakdown = models.JSONField(
        null=True,
        blank=True,
        help_text='Per-country km/rate/price segments from zone boundary pricing.',
    )
    pickup_country_code = models.CharField(
        max_length=2,
        blank=True,
        help_text='ISO 3166-1 alpha-2 (e.g. AE, SA); used for discovery / corridor rules.',
    )
    delivery_country_code = models.CharField(
        max_length=2,
        blank=True,
        help_text='ISO 3166-1 alpha-2; used for return-load and corridor visibility.',
    )
    is_return = models.BooleanField(
        default=False,
        help_text='When true, shipment is offered as a return trip (subject to zone rules on pickup country).',
    )
    country_to_country = models.BooleanField(
        default=False,
        help_text='Cross-border load; mutually exclusive with local.',
    )
    local = models.BooleanField(
        default=False,
        help_text='Domestic/local load; mutually exclusive with country_to_country.',
    )
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.DRAFT)
    unique_id = models.CharField(max_length=12, unique=True, editable=False, null=True, blank=True)
    pickup_scheduled_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']
        constraints = [
            models.CheckConstraint(
                condition=models.Q(country_to_country=True, local=False)
                | models.Q(country_to_country=False, local=True),
                name='shipment_load_type_xor',
            ),
        ]

    @classmethod
    def allocate_unique_id(cls):
        """Return the next SH-##### id; serialized so concurrent creates cannot collide."""
        with transaction.atomic():
            cls.objects.select_for_update().order_by('-id').values_list('id', flat=True).first()
            recent_uids = (
                cls.objects.exclude(unique_id__isnull=True)
                .exclude(unique_id='')
                .order_by('-id')
                .values_list('unique_id', flat=True)[:100]
            )
            seq = _max_shipment_unique_id_sequence(recent_uids) + 1
            candidate = format_shipment_unique_id(seq)
            while cls.objects.filter(unique_id=candidate).exists():
                seq += 1
                candidate = format_shipment_unique_id(seq)
            return candidate

    def save(self, *args, **kwargs):
        if self.unique_id:
            return super().save(*args, **kwargs)
        for attempt in range(5):
            self.unique_id = type(self).allocate_unique_id()
            try:
                return super().save(*args, **kwargs)
            except IntegrityError:
                self.unique_id = None
                if attempt == 4:
                    raise
        return super().save(*args, **kwargs)

    def __str__(self):
        label = self.unique_id or f'#{self.id}'
        return f'Shipment {label} ({self.status})'

    def calculate_and_set_suggested_price(self, route_coords):
        """
        Set suggested_price + price_breakdown from a driven route polyline.

        route_coords: [(lng, lat), ...] from the client routing/directions provider
        (decoded polyline). There is no server-side directions API today; callers may
        fall back to a 2-point pickup→delivery line when a full route is unavailable.
        """
        from decimal import Decimal

        from core.pricing.service import calculate_suggested_price

        price, breakdown = calculate_suggested_price(route_coords)
        self.suggested_price = price
        self.price_breakdown = breakdown
        total_km = sum(
            (Decimal(str(seg.get('km') or 0)) for seg in breakdown),
            Decimal('0'),
        )
        self.distance_km = total_km.quantize(Decimal('0.01'))
        self.save(update_fields=['suggested_price', 'price_breakdown', 'distance_km', 'updated_at'])


class Bid(models.Model):
    class Status(models.TextChoices):
        PENDING = 'PENDING', 'Pending'
        AGREED = 'AGREED', 'Agreed'
        ACCEPTED = 'ACCEPTED', 'Accepted'
        REJECTED = 'REJECTED', 'Rejected'
        COUNTERED = 'COUNTERED', 'Countered'

    shipment = models.ForeignKey(
        Shipment,
        on_delete=models.CASCADE,
        related_name='bids'
    )
    transporter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='bids'
    )
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
    counter_amount = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    message = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'Bid #{self.id} on Shipment #{self.shipment_id} - {self.amount}'


class RateRequest(models.Model):
    class Status(models.TextChoices):
        PENDING = 'PENDING', 'Pending'
        ACCEPTED = 'ACCEPTED', 'Accepted'
        REJECTED = 'REJECTED', 'Rejected'
        CANCELLED = 'CANCELLED', 'Cancelled'
        EXPIRED = 'EXPIRED', 'Expired'

    shipment = models.ForeignKey(
        Shipment,
        on_delete=models.CASCADE,
        related_name='rate_requests',
    )
    driver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='received_rate_requests',
    )
    batch_number = models.PositiveIntegerField(default=1)
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
    distance_km = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    offered_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    currency = models.CharField(max_length=3, blank=True)
    rejection_reason = models.CharField(max_length=255, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    responded_at = models.DateTimeField(null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']
        constraints = [
            models.UniqueConstraint(
                fields=['shipment', 'driver'],
                name='uniq_shipment_driver_rate_request',
            ),
        ]

    def __str__(self):
        return f'RateRequest #{self.id} Shipment #{self.shipment_id} -> Driver #{self.driver_id} ({self.status})'


class Trip(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'DRAFT', 'Draft'
        PUBLISHED = 'PUBLISHED', 'Published'
        ASSIGNED = 'ASSIGNED', 'Assigned'
        EN_ROUTE = 'EN_ROUTE', 'En Route to Pickup'
        ARRIVED_PICKUP = 'ARRIVED_PICKUP', 'Arrived at Pickup'
        LOADED = 'LOADED', 'Loaded'
        IN_TRANSIT = 'IN_TRANSIT', 'In Transit'
        ARRIVED_DELIVERY = 'ARRIVED_DELIVERY', 'Arrived at Delivery'
        DELIVERED = 'DELIVERED', 'Delivered'
        COMPLETED = 'COMPLETED', 'Completed'
        CLOSED = 'CLOSED', 'Closed'

    shipment = models.OneToOneField(
        Shipment,
        on_delete=models.CASCADE,
        related_name='trip'
    )
    accepted_bid = models.ForeignKey(
        Bid,
        on_delete=models.PROTECT,
        related_name='trip'
    )
    transporter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='trips'
    )
    assigned_driver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='assigned_trips',
        null=True,
        blank=True,
    )
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.ASSIGNED)
    current_lat = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    current_lon = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    eta = models.DateTimeField(null=True, blank=True)
    agreed_price = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        null=True,
        blank=True,
        help_text='Final negotiated price when the bid was accepted.',
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    @property
    def unique_id(self):
        try:
            return self.shipment.unique_id
        except Shipment.DoesNotExist:
            return None

    def __str__(self):
        label = getattr(self.shipment, 'unique_id', None) or f'#{self.id}'
        return f'Trip {label} - {self.shipment} ({self.status})'


class TripLocation(models.Model):
    trip = models.ForeignKey(
        Trip,
        on_delete=models.CASCADE,
        related_name='locations'
    )
    lat = models.DecimalField(max_digits=10, decimal_places=7)
    lon = models.DecimalField(max_digits=10, decimal_places=7)
    recorded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-recorded_at']

    def __str__(self):
        return f'Trip #{self.trip_id} at {self.recorded_at}'


class TripStatusRecord(models.Model):
    trip = models.ForeignKey(
        Trip,
        on_delete=models.CASCADE,
        related_name='status_records',
    )
    status = models.CharField(max_length=20, choices=Trip.Status.choices)
    recorded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['recorded_at', 'id']

    def __str__(self):
        return f'Trip #{self.trip_id} status={self.status} at {self.recorded_at}'


class Conversation(models.Model):
    shipment = models.ForeignKey(
        Shipment,
        on_delete=models.CASCADE,
        related_name='conversations',
        null=True,
        blank=True,
    )
    shipper = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='shipper_conversations',
        null=True,
        blank=True,
    )
    transporter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='transporter_conversations',
        null=True,
        blank=True,
    )
    trip = models.OneToOneField(
        Trip,
        on_delete=models.SET_NULL,
        related_name='conversation',
        null=True,
        blank=True,
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']
        constraints = [
            models.UniqueConstraint(
                fields=['shipment', 'shipper', 'transporter'],
                name='uniq_conversation_shipment_shipper_transporter',
            )
        ]

    def __str__(self):
        return f'Conversation Shipment #{self.shipment_id} ({self.shipper_id}->{self.transporter_id})'


class Message(models.Model):
    class MessageType(models.TextChoices):
        TEXT = 'TEXT', 'Text'
        VOICE = 'VOICE', 'Voice'

    conversation = models.ForeignKey(
        Conversation,
        on_delete=models.CASCADE,
        related_name='messages'
    )
    sender = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='sent_messages'
    )
    text = models.TextField(blank=True)
    message_type = models.CharField(max_length=10, choices=MessageType.choices, default=MessageType.TEXT)
    voice_file = models.FileField(upload_to='messages/voice/%Y/%m/%d/', null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['created_at']

    def __str__(self):
        return f'Message from {self.sender_id} in Conv #{self.conversation_id}'


class ProofOfDelivery(models.Model):
    trip = models.OneToOneField(
        Trip,
        on_delete=models.CASCADE,
        related_name='pod'
    )
    receiver_name = models.CharField(max_length=255)
    receiver_signature = models.ImageField(upload_to='pod/signatures/%Y/%m/%d/', null=True, blank=True)
    delivery_lat = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    delivery_lon = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    delivered_at = models.DateTimeField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = 'Proof of delivery'
        verbose_name_plural = 'Proofs of delivery'

    def __str__(self):
        return f'POD for Trip #{self.trip_id}'


class PODPhoto(models.Model):
    pod = models.ForeignKey(
        ProofOfDelivery,
        on_delete=models.CASCADE,
        related_name='photos'
    )
    image = models.ImageField(upload_to='pod/photos/%Y/%m/%d/')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['created_at']

    def __str__(self):
        return f'Photo for POD Trip #{self.pod.trip_id}'


class TripReview(models.Model):
    """Shipper rates transporter (and optionally driver) after trip."""
    trip = models.OneToOneField(
        Trip,
        on_delete=models.CASCADE,
        related_name='review',
    )
    shipper = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='trip_reviews_written',
    )
    transporter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='trip_reviews_as_transporter',
    )
    driver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='trip_reviews_as_driver',
        help_text='If null, same as transporter.',
    )
    transporter_rating = models.PositiveSmallIntegerField()
    driver_rating = models.PositiveSmallIntegerField(null=True, blank=True)
    comment = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']


class TripShipperReview(models.Model):
    """Transporter or assigned driver rates the shipper after trip."""
    trip = models.OneToOneField(
        Trip,
        on_delete=models.CASCADE,
        related_name='shipper_review',
    )
    shipper = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='trip_reviews_as_shipper',
    )
    transporter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='shipper_reviews_as_transporter',
    )
    reviewer = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='shipper_reviews_written',
        help_text='Transporter or assigned driver who submitted the review.',
    )
    shipper_rating = models.PositiveSmallIntegerField()
    comment = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']


class SavedAddress(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='saved_addresses',
    )
    label = models.CharField(max_length=100)
    address_line = models.CharField(max_length=500)
    lat = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    lon = models.DecimalField(max_digits=10, decimal_places=7, null=True, blank=True)
    country_code = models.CharField(max_length=2, blank=True)
    is_default = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']


class FavoriteTransporter(models.Model):
    shipper = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='favorite_transporters_links',
    )
    transporter = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='favorited_by_shippers',
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']
        constraints = [
            models.UniqueConstraint(
                fields=['shipper', 'transporter'],
                name='uniq_favorite_shipper_transporter',
            ),
        ]


class AdminTripAction(models.Model):
    """Audit log when platform admin overrides a trip status with justification."""

    trip = models.ForeignKey(
        'Trip',
        on_delete=models.CASCADE,
        related_name='admin_actions',
    )
    performed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='admin_trip_actions',
    )
    previous_status = models.CharField(max_length=20)
    new_status = models.CharField(max_length=20)
    justification = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'Trip {self.trip_id}: {self.previous_status} -> {self.new_status}'

class Zone(models.Model):
    country_code = models.CharField(max_length=2, unique=True)
    radius_km = models.DecimalField(
        max_digits=8,
        decimal_places=2,
        null=True,
        blank=True,
        help_text='Service zone radius in kilometres.',
    )
    rate_per_km = models.DecimalField(
        max_digits=12,
        decimal_places=4,
        null=True,
        blank=True,
        help_text='Default price per km for route segments in this country.',
    )
    allow_return_trips = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['country_code']

    def __str__(self):
        return self.country_display

    @property
    def country_display(self):
        from core.countries import country_name_for_code
        return country_name_for_code(self.country_code) or self.country_code


class CountryBoundary(models.Model):
    country_code = models.CharField(max_length=2, unique=True, db_index=True)
    country_name = models.CharField(max_length=100)
    geojson = models.JSONField()  # GeoJSON Polygon or MultiPolygon geometry only

    class Meta:
        verbose_name_plural = 'Country boundaries'

    def __str__(self):
        return f'{self.country_code} - {self.country_name}'


class FreightRoute(models.Model):
    """Directional minimum freight rate between two countries (A→B ≠ B→A)."""

    origin_country_code = models.CharField(max_length=2, choices=COUNTRY_CHOICES)
    destination_country_code = models.CharField(max_length=2, choices=COUNTRY_CHOICES)
    min_freight = models.DecimalField(max_digits=10, decimal_places=2)
    currency = models.CharField(max_length=3, default='USD')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=['origin_country_code', 'destination_country_code'],
                name='unique_origin_destination',
            ),
            models.CheckConstraint(
                check=~models.Q(origin_country_code=models.F('destination_country_code')),
                name='origin_ne_destination',
            ),
        ]
        indexes = [
            models.Index(fields=['origin_country_code', 'destination_country_code']),
        ]
        ordering = ['origin_country_code', 'destination_country_code']

    def __str__(self):
        return (
            f'{self.origin_country_code} -> {self.destination_country_code}: '
            f'{self.min_freight} {self.currency}'
        )

    @property
    def origin_country_name(self):
        from core.countries import country_name_for_code
        return country_name_for_code(self.origin_country_code) or self.origin_country_code

    @property
    def destination_country_name(self):
        from core.countries import country_name_for_code
        return country_name_for_code(self.destination_country_code) or self.destination_country_code

    def clean(self):
        from django.core.exceptions import ValidationError
        from core.countries import is_valid_country_code, normalize_country_code

        origin = normalize_country_code(self.origin_country_code)
        destination = normalize_country_code(self.destination_country_code)
        if self.origin_country_code and not is_valid_country_code(self.origin_country_code):
            raise ValidationError({'origin_country_code': 'Invalid origin country code.'})
        if self.destination_country_code and not is_valid_country_code(self.destination_country_code):
            raise ValidationError({'destination_country_code': 'Invalid destination country code.'})
        if origin and destination and origin == destination:
            raise ValidationError('Origin and destination must be different countries.')

    def save(self, *args, **kwargs):
        from core.countries import normalize_country_code

        self.origin_country_code = normalize_country_code(self.origin_country_code) or (
            str(self.origin_country_code or '').strip().upper()
        )
        self.destination_country_code = normalize_country_code(self.destination_country_code) or (
            str(self.destination_country_code or '').strip().upper()
        )
        if self.currency:
            self.currency = self.currency.strip().upper()
        super().save(*args, **kwargs)


class Notification(models.Model):
    title = models.CharField(max_length=255)
    message = models.TextField()
    type = models.CharField(max_length=100)
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='notifications',
    )
    data = models.JSONField(null=True, blank=True)
    seen = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return f'Notification #{self.id} to {self.user_id} ({self.type})'
