from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _


class Photo(models.Model):
    """
    Generic DB-backed image. Raw bytes are stored in PostgreSQL (bytea).
    Served via GET /api/photos/{id}/data/ — no filesystem or S3 required.
    """
    data = models.BinaryField(_('data'))
    content_type = models.CharField(_('content type'), max_length=50)  # e.g. 'image/jpeg'
    size = models.PositiveIntegerField(_('size (bytes)'))
    width = models.PositiveSmallIntegerField(_('width (px)'), null=True, blank=True)
    height = models.PositiveSmallIntegerField(_('height (px)'), null=True, blank=True)
    uploaded_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='photos',
        verbose_name=_('uploaded by'),
    )
    uploaded_at = models.DateTimeField(_('uploaded at'), auto_now_add=True)
    description = models.CharField(_('description'), max_length=255, blank=True)

    class Meta:
        verbose_name = _('photo')
        verbose_name_plural = _('photos')
        ordering = ['-uploaded_at']

    def __str__(self):
        owner = self.uploaded_by.email if self.uploaded_by_id else _('anonymous')
        return f"Photo #{self.pk} ({self.content_type}, {owner})"


class GroupPhoto(models.Model):
    """
    Links a Photo to a ShotGroup. A single group can have multiple photos
    (e.g. different distances or targets at the same session).
    """
    photo = models.OneToOneField(
        Photo,
        on_delete=models.CASCADE,
        related_name='group_photo',
        verbose_name=_('photo'),
    )
    shot_group = models.ForeignKey(
        'tools.ShotGroup',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='group_photos',
        verbose_name=_('shot group'),
    )
    caption = models.CharField(_('caption'), max_length=255, blank=True)
    order = models.PositiveSmallIntegerField(_('order'), default=0)
    is_public = models.BooleanField(_('public'), default=False)

    class Meta:
        verbose_name = _('group photo')
        verbose_name_plural = _('group photos')
        ordering = ['order', 'id']

    def __str__(self):
        target = self.shot_group or _('unlinked')
        return f"GroupPhoto #{self.pk} → {target}"


class GroupPhotoAnalysis(models.Model):
    """
    Ballistic overlay data for a GroupPhoto: group size, point-of-impact
    offsets, and mean radius — all in millimetres and MOA.
    """
    group_photo = models.OneToOneField(
        GroupPhoto,
        on_delete=models.CASCADE,
        related_name='analysis',
        verbose_name=_('group photo'),
    )
    group_size_mm = models.DecimalField(
        _('group size (mm)'), max_digits=7, decimal_places=2, null=True, blank=True,
    )
    group_size_moa = models.DecimalField(
        _('group size (MOA)'), max_digits=7, decimal_places=3, null=True, blank=True,
    )
    elevation_offset_mm = models.DecimalField(
        _('elevation offset (mm)'), max_digits=7, decimal_places=2, null=True, blank=True,
    )
    elevation_offset_moa = models.DecimalField(
        _('elevation offset (MOA)'), max_digits=7, decimal_places=3, null=True, blank=True,
    )
    windage_offset_mm = models.DecimalField(
        _('windage offset (mm)'), max_digits=7, decimal_places=2, null=True, blank=True,
    )
    windage_offset_moa = models.DecimalField(
        _('windage offset (MOA)'), max_digits=7, decimal_places=3, null=True, blank=True,
    )
    mean_radius_mm = models.DecimalField(
        _('mean radius (mm)'), max_digits=7, decimal_places=2, null=True, blank=True,
    )
    mean_radius_moa = models.DecimalField(
        _('mean radius (MOA)'), max_digits=7, decimal_places=3, null=True, blank=True,
    )
    notes = models.TextField(_('notes'), blank=True)

    class Meta:
        verbose_name = _('group photo analysis')
        verbose_name_plural = _('group photo analyses')

    def __str__(self):
        return f"Analysis for {self.group_photo}"

    def clean(self):
        errors = {}
        for field in ('group_size_mm', 'group_size_moa', 'mean_radius_mm', 'mean_radius_moa'):
            value = getattr(self, field)
            if value is not None and value < 0:
                errors[field] = _('This measurement cannot be negative.')
        if errors:
            raise ValidationError(errors)


class PointOfImpact(models.Model):
    """
    An individual bullet-hole marker on a GroupPhoto.

    Pixel coordinates (x_px, y_px) allow UI overlays.
    Real-world coordinates (x_mm, y_mm) use the point-of-aim as origin,
    with + = right and + = up (standard ballistic convention).
    Optionally linked to a Shot from the chronograph for combined analysis.
    """
    group_photo = models.ForeignKey(
        GroupPhoto,
        on_delete=models.CASCADE,
        related_name='points_of_impact',
        verbose_name=_('group photo'),
    )
    # Optional link to the matching Shot record from a ChronographAnalysis
    shot = models.OneToOneField(
        'tools.Shot',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='point_of_impact',
        verbose_name=_('shot'),
    )
    order = models.PositiveSmallIntegerField(
        _('order'), default=0,
        help_text=_('1-based sequence; used when shot FK is absent.'),
    )
    # Pixel position on the photo (for overlay rendering)
    x_px = models.PositiveSmallIntegerField(_('x (px)'))
    y_px = models.PositiveSmallIntegerField(_('y (px)'))
    # Real-world offsets from point-of-aim (millimetres)
    x_mm = models.DecimalField(
        _('x offset (mm)'), max_digits=7, decimal_places=2, null=True, blank=True,
    )
    y_mm = models.DecimalField(
        _('y offset (mm)'), max_digits=7, decimal_places=2, null=True, blank=True,
    )
    # Radius of the bullet hole (for rendering)
    radius_mm = models.DecimalField(
        _('bullet hole radius (mm)'), max_digits=6, decimal_places=2, null=True, blank=True,
    )
    notes = models.CharField(_('notes'), max_length=255, blank=True)

    class Meta:
        verbose_name = _('point of impact')
        verbose_name_plural = _('points of impact')
        ordering = ['order', 'id']

    def __str__(self):
        return f"POI #{self.order or self.pk} on {self.group_photo}"
