"""Order models — a persisted buyer request.

An order is an immutable snapshot: lines record what was ordered and the price at
order time, decoupled from the per-section listing models so later repricing or
deletion never rewrites history.
"""

import secrets

from django.db import IntegrityError, models, transaction

from .core import Shop

REF_ALPHABET = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"  # no 0 1 I L O


def generate_reference():
    return "".join(secrets.choice(REF_ALPHABET) for _ in range(5))


class Buyer(models.Model):
    """A storefront buyer, keyed by EVE character (from SSO)."""

    character_id = models.PositiveBigIntegerField(unique=True)
    character_name = models.CharField(max_length=255)
    discord_handle = models.CharField(max_length=100, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.character_name

    @classmethod
    def upsert_from_session(cls, buyer_session, discord_handle=None):
        """Get-or-create by character id; refresh name and (if given) handle."""
        buyer, _ = cls.objects.get_or_create(
            character_id=buyer_session["id"],
            defaults={"character_name": buyer_session["name"]},
        )
        changed = False
        if buyer.character_name != buyer_session["name"]:
            buyer.character_name = buyer_session["name"]
            changed = True
        if discord_handle is not None and buyer.discord_handle != discord_handle:
            buyer.discord_handle = discord_handle
            changed = True
        if changed:
            buyer.save()
        return buyer


class Order(models.Model):
    PENDING = "pending"
    ACCEPTED = "accepted"
    COMPLETED = "completed"
    DECLINED = "declined"
    CANCELLED = "cancelled"
    STATUS_CHOICES = [
        (PENDING, "Pending"),
        (ACCEPTED, "Accepted"),
        (COMPLETED, "Completed"),
        (DECLINED, "Declined"),
        (CANCELLED, "Cancelled"),
    ]
    OPEN_STATUSES = (PENDING, ACCEPTED)

    reference = models.CharField(max_length=5, unique=True, editable=False, default="")
    shop = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name="orders")
    buyer = models.ForeignKey(Buyer, on_delete=models.PROTECT, related_name="orders")
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=PENDING)
    additional_info = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"#{self.reference}"

    def save(self, *args, **kwargs):
        if self.reference:
            return super().save(*args, **kwargs)
        for _ in range(10):
            self.reference = generate_reference()
            try:
                with transaction.atomic():
                    return super().save(*args, **kwargs)
            except IntegrityError:
                self.reference = ""
        raise RuntimeError("could not allocate a unique order reference")

    @property
    def estimated_total(self):
        return sum(
            ln.unit_price * ln.quantity for ln in self.lines.all() if ln.unit_price
        )

    @property
    def has_ask(self):
        return any(ln.unit_price == 0 for ln in self.lines.all())

    @property
    def is_open(self):
        return self.status in self.OPEN_STATUSES

    @property
    def estimated_total_display(self):
        from ..formatting import format_isk

        total = self.estimated_total
        return format_isk(total) if total else "—"


class OrderLine(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="lines")
    section = models.CharField(max_length=20)
    eve_type = models.ForeignKey(
        "eveuniverse.EveType", on_delete=models.PROTECT, related_name="+"
    )
    quantity = models.PositiveIntegerField()
    unit_price = models.PositiveBigIntegerField(default=0)  # ISK; 0 = Ask
    listing_id = models.PositiveIntegerField(null=True, blank=True)
    label = models.CharField(
        max_length=100, blank=True
    )  # sub-item detail, e.g. BPC variant
    fit_json = models.JSONField(
        default=dict, blank=True
    )  # frozen fit snapshot for fitted-ship lines (survives the listing being removed)

    def __str__(self):
        return f"{self.quantity} x {self.eve_type} @ {self.unit_price}"

    @property
    def line_total(self):
        return self.unit_price * self.quantity

    @property
    def unit_price_display(self):
        from ..formatting import format_isk

        return format_isk(self.unit_price) if self.unit_price else "Ask"

    @property
    def line_total_display(self):
        from ..formatting import format_isk

        return format_isk(self.line_total) if self.unit_price else "Ask"
