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


# ── Choices ───────────────────────────────────────────────────────────────────

class ShootingPosition(models.TextChoices):
    PRONE       = 'PRONE',       _('Prone')
    STANDING    = 'STANDING',    _('Standing')
    SITTING     = 'SITTING',     _('Sitting')
    KNEELING    = 'KNEELING',    _('Kneeling')
    BARRICADE   = 'BARRICADE',   _('Barricade')
    UNSUPPORTED = 'UNSUPPORTED', _('Unsupported')
    OTHER       = 'OTHER',       _('Other')


class CorrectionUnit(models.TextChoices):
    MOA   = 'MOA',   _('MOA')
    MRAD  = 'MRAD',  _('MRAD')
    CLICK = 'CLICK', _('Clicks')


# ── Abstract base ─────────────────────────────────────────────────────────────

class AbstractSession(models.Model):
    """
    Shared fields inherited by all concrete session types.
    Each subclass gets its own DB table (no cross-table joins).
    """
    # Use '+' to suppress reverse accessors — query via concrete model managers
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='+',
        verbose_name=_('user'),
    )
    name = models.CharField(_('name'), max_length=255, blank=True)
    date = models.DateField(_('date'))
    location = models.CharField(_('location'), max_length=255, blank=True)

    # Intentional cross-app FKs (string refs avoid circular imports)
    analysis = models.ForeignKey(
        'tools.ChronographAnalysis',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name=_('chronograph analysis'),
    )
    rig = models.ForeignKey(
        'gears.Rig',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name=_('rig'),
    )
    ammo = models.ForeignKey(
        'gears.Ammo',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name=_('factory ammo'),
    )
    reloaded_batch = models.ForeignKey(
        'gears.ReloadedAmmoBatch',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name=_('reloaded batch'),
    )

    # Weather
    temperature_c      = models.DecimalField(_('temperature (°C)'), max_digits=5,  decimal_places=1, null=True, blank=True)
    wind_speed_ms      = models.DecimalField(_('wind speed (m/s)'), max_digits=5,  decimal_places=1, null=True, blank=True)
    wind_direction_deg = models.PositiveSmallIntegerField(_('wind direction (°)'), null=True, blank=True)
    humidity_pct       = models.PositiveSmallIntegerField(_('humidity (%)'),       null=True, blank=True)
    pressure_hpa       = models.DecimalField(_('pressure (hPa)'),  max_digits=6,  decimal_places=1, null=True, blank=True)
    weather_notes      = models.TextField(_('weather notes'), blank=True)

    notes      = models.TextField(_('notes'), blank=True)
    is_public  = models.BooleanField(_('public'), default=False)
    created_at = models.DateTimeField(_('created at'), auto_now_add=True)
    updated_at = models.DateTimeField(_('updated at'), auto_now=True)

    class Meta:
        abstract = True
        ordering = ['-date', '-created_at']

    def clean(self):
        if self.ammo_id and self.reloaded_batch_id:
            raise ValidationError(
                _('A session may use factory ammo or a reloaded batch, not both.')
            )
        if self.rig_id:
            if self.rig.user_id != self.user_id:
                raise ValidationError({'rig': _('This rig does not belong to you.')})
        if self.wind_direction_deg is not None and not (0 <= self.wind_direction_deg <= 359):
            raise ValidationError(
                {'wind_direction_deg': _('Wind direction must be between 0 and 359 degrees.')}
            )
        if self.humidity_pct is not None and not (0 <= self.humidity_pct <= 100):
            raise ValidationError(
                {'humidity_pct': _('Humidity must be between 0 and 100.')}
            )


# ── PRS session ───────────────────────────────────────────────────────────────

class PRSSession(AbstractSession):
    """
    A Precision Rifle Series session.
    Two-phase workflow: preparation (stages defined upfront) →
    execution (weather entered, corrections computed, results recorded).
    """
    competition_name = models.CharField(_('competition name'), max_length=255, blank=True)
    category         = models.CharField(_('category'), max_length=100, blank=True)

    class Meta(AbstractSession.Meta):
        verbose_name        = _('PRS session')
        verbose_name_plural = _('PRS sessions')

    def __str__(self):
        label = self.competition_name or self.name or _('PRS session')
        return f"{label} — {self.date}"


class PRSStage(models.Model):
    """
    One stage within a PRSSession.
    Fields are grouped by lifecycle phase: prep, execution, results.
    """
    session = models.ForeignKey(
        PRSSession,
        on_delete=models.CASCADE,
        related_name='stages',
        verbose_name=_('session'),
    )

    # ── Prep phase ────────────────────────────────────────────────────────────
    order           = models.PositiveSmallIntegerField(_('order'))
    position        = models.CharField(
        _('shooting position'), max_length=20,
        choices=ShootingPosition.choices, default=ShootingPosition.PRONE,
    )
    distance_m      = models.PositiveSmallIntegerField(_('distance (m)'))
    target_width_cm = models.DecimalField(
        _('target width (cm)'), max_digits=6, decimal_places=1, null=True, blank=True,
    )
    target_height_cm = models.DecimalField(
        _('target height (cm)'), max_digits=6, decimal_places=1, null=True, blank=True,
    )
    max_time_s   = models.PositiveSmallIntegerField(_('max time (s)'), null=True, blank=True)
    shots_count  = models.PositiveSmallIntegerField(_('shots count'), default=1)
    notes_prep   = models.TextField(_('prep notes'), blank=True)

    # ── Execution phase ───────────────────────────────────────────────────────
    # computed_* are set by the ballistic engine (read-only for clients)
    computed_elevation = models.DecimalField(
        _('computed elevation'), max_digits=6, decimal_places=2, null=True, blank=True,
    )
    computed_windage = models.DecimalField(
        _('computed windage'), max_digits=6, decimal_places=2, null=True, blank=True,
    )
    correction_unit = models.CharField(
        _('correction unit'), max_length=10,
        choices=CorrectionUnit.choices, blank=True,
    )
    # actual_* are editable by the shooter
    actual_elevation = models.DecimalField(
        _('actual elevation'), max_digits=6, decimal_places=2, null=True, blank=True,
    )
    actual_windage = models.DecimalField(
        _('actual windage'), max_digits=6, decimal_places=2, null=True, blank=True,
    )

    # ── Results phase ─────────────────────────────────────────────────────────
    hits         = models.PositiveSmallIntegerField(_('hits'),              null=True, blank=True)
    score        = models.PositiveSmallIntegerField(_('score'),             null=True, blank=True)
    time_taken_s = models.DecimalField(
        _('time taken (s)'), max_digits=6, decimal_places=2, null=True, blank=True,
    )
    # Optional link to chronograph/shot data
    shot_group = models.ForeignKey(
        'tools.ShotGroup',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='prs_stages',
        verbose_name=_('shot group'),
    )
    notes_post = models.TextField(_('post notes'), blank=True)

    class Meta:
        verbose_name        = _('PRS stage')
        verbose_name_plural = _('PRS stages')
        ordering            = ['order']
        constraints = [
            models.UniqueConstraint(fields=['session', 'order'], name='unique_prs_stage_order')
        ]

    def __str__(self):
        return f"Stage {self.order} — {self.distance_m}m {self.get_position_display()}"

    def clean(self):
        if self.hits is not None and self.hits > self.shots_count:
            raise ValidationError(
                {'hits': _('Hits cannot exceed the number of shots for this stage.')}
            )
        if self.score is not None and self.hits is not None and self.score > self.hits:
            raise ValidationError(
                {'score': _('Score cannot exceed the number of hits.')}
            )


# ── Free Practice session ─────────────────────────────────────────────────────

class FreePracticeSession(AbstractSession):
    """A free-form practice session at a fixed distance."""
    distance_m          = models.PositiveSmallIntegerField(_('distance (m)'), null=True, blank=True)
    target_description  = models.CharField(_('target description'), max_length=255, blank=True)
    rounds_fired        = models.PositiveSmallIntegerField(_('rounds fired'), null=True, blank=True)

    class Meta(AbstractSession.Meta):
        verbose_name        = _('free practice session')
        verbose_name_plural = _('free practice sessions')

    def __str__(self):
        label = self.name or _('Free practice')
        dist  = f' — {self.distance_m}m' if self.distance_m else ''
        return f"{label}{dist} ({self.date})"


# ── Speed Shooting session ────────────────────────────────────────────────────

class SpeedShootingSession(AbstractSession):
    """A speed shooting session (IPSC, IDPA, Steel Challenge, …). Minimal placeholder."""
    format       = models.CharField(_('format'), max_length=100, blank=True)
    rounds_fired = models.PositiveSmallIntegerField(_('rounds fired'), null=True, blank=True)

    class Meta(AbstractSession.Meta):
        verbose_name        = _('speed shooting session')
        verbose_name_plural = _('speed shooting sessions')

    def __str__(self):
        label = self.name or self.format or _('Speed shooting')
        return f"{label} ({self.date})"
