"""Models."""

from decimal import ROUND_HALF_UP, Decimal

from django.contrib.auth.models import User
from django.core.validators import RegexValidator
from django.db import models

from allianceauth.eveonline.models import EveCorporationInfo

SLUG_VALIDATOR = RegexValidator(
    regex=r"^[a-z0-9]+(-[a-z0-9]+)*$",
    message="Slug may contain lowercase letters, numbers and single dashes only.",
)

HULL = "hulls"
SECTION_CHOICES = [(HULL, "Hulls")]

# Stock modes
AUTO_STOCK = "auto"
FIXED_STOCK = "fixed"
STOCK_MODE_CHOICES = [(AUTO_STOCK, "Auto"), (FIXED_STOCK, "Fixed")]

# Price modes ("Ask" is not a mode: Fixed price 0 renders as Ask)
FIXED_PRICE = "fixed"
JITA_BUY_PCT = "jita_buy_pct"
JITA_SELL_PCT = "jita_sell_pct"
PRICE_MODE_CHOICES = [
    (FIXED_PRICE, "Fixed"),
    (JITA_BUY_PCT, "% Jita Buy"),
    (JITA_SELL_PCT, "% Jita Sell"),
]
PCT_MODES = (JITA_BUY_PCT, JITA_SELL_PCT)


def format_price_rule(price_mode, price_pct, price=None) -> str:
    """Human label for a price rule. Fixed shows 'Ask' at 0 (or when price is
    unknown, e.g. a stockroom's default), else 'Fixed'; pct modes show 'Jita Buy 90%'."""
    if price_mode == FIXED_PRICE:
        return "Ask" if (price is None or price == 0) else "Fixed"
    label = "Jita Buy" if price_mode == JITA_BUY_PCT else "Jita Sell"
    # normalize() strips trailing zeros but yields sci-notation (9E+1); "f" fixes it.
    pct = format((price_pct or Decimal(0)).normalize(), "f")
    return f"{label} {pct}%"


class General(models.Model):
    """Meta model that only carries app-level permissions."""

    class Meta:
        managed = False
        default_permissions = ()
        permissions = (
            ("basic_access", "Can access the shop app"),
            ("manage_corporation_shops", "Can manage corporation shops"),
        )


class Shop(models.Model):
    """A member's or corporation's shop."""

    class ShopType(models.TextChoices):
        PERSONAL = "personal", "Personal"
        CORPORATION = "corporation", "Corporation"

    slug = models.SlugField(
        max_length=50,
        unique=True,
        validators=[SLUG_VALIDATOR],
        help_text="Globally unique. Lowercase letters, numbers and dashes.",
    )
    name = models.CharField(max_length=100)
    shop_type = models.CharField(
        max_length=12, choices=ShopType.choices, default=ShopType.PERSONAL
    )
    is_published = models.BooleanField(default=False)
    corporation = models.ForeignKey(
        EveCorporationInfo,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="shops",
    )
    created_by = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name="shops"
    )
    enabled_sections = models.JSONField(default=list, blank=True)
    description = models.TextField(blank=True, default="")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

    def section_enabled(self, key) -> bool:
        return key in (self.enabled_sections or [])

    @property
    def description_html(self) -> str:
        from .markdown import render_markdown
        return render_markdown(self.description)


class HullListing(models.Model):
    """A ship type listed for sale by a shop, with stored stock."""

    shop = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name="hull_listings")
    eve_type = models.ForeignKey("eveuniverse.EveType", on_delete=models.PROTECT, related_name="+")
    price = models.PositiveBigIntegerField(default=0)   # ISK; 0 = "Ask"
    stock = models.PositiveIntegerField(default=0)      # stored snapshot
    stock_mode = models.CharField(
        max_length=10, choices=STOCK_MODE_CHOICES, default=AUTO_STOCK
    )
    price_mode = models.CharField(
        max_length=20, choices=PRICE_MODE_CHOICES, default=FIXED_PRICE
    )
    price_pct = models.DecimalField(
        max_digits=5, decimal_places=2, null=True, blank=True
    )  # e.g. 90.00; used only in pct price modes
    persist = models.BooleanField(
        default=False
    )  # keep listing when auto-stock hits 0; unchecked = auto-remove at 0
    featured = models.BooleanField(
        default=False
    )  # starred: shown first + larger on the storefront
    featured_at = models.DateTimeField(null=True, blank=True)  # set on star; orders the row
    description = models.TextField(blank=True, default="")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["shop", "eve_type"], name="uniq_shop_hull")
        ]

    def __str__(self):
        return f"{self.shop.slug}: {self.eve_type.name}"

    @property
    def price_display(self) -> str:
        from .formatting import format_isk
        return "Ask" if self.price == 0 else format_isk(self.price)

    @property
    def price_mode_display(self) -> str:
        return format_price_rule(self.price_mode, self.price_pct, self.price)

    def apply_jita_price(self, buy, sell) -> bool:
        """Set price from Jita percentiles per this listing's pct rule.

        Returns True if `price` changed (caller saves/bulk_updates). No save here.
        Keep-last-known: leaves price untouched when the needed side is None.
        """
        if self.price_mode not in PCT_MODES:
            return False
        base = buy if self.price_mode == JITA_BUY_PCT else sell
        if base is None:
            return False
        new_price = int(
            (self.price_pct / Decimal(100) * Decimal(base)).to_integral_value(
                rounding=ROUND_HALF_UP
            )
        )
        if new_price == self.price:
            return False
        self.price = new_price
        return True

    def recompute_stock(self, save=True) -> int:
        if self.stock_mode == FIXED_STOCK:
            return self.stock
        from .stock import source_qty
        sources = self.shop.sources.filter(section=HULL)
        self.stock = sum(source_qty(s, self.eve_type) for s in sources)
        if save:
            self.save(update_fields=["stock"])
        return self.stock

    @property
    def description_html(self) -> str:
        from .markdown import render_markdown
        return render_markdown(self.description)


class ShopSource(models.Model):
    """A memberaudit Character → Station → (optional) Container stockroom for a shop section.

    Section-scoped via `section`: a hull listing's stock is counted across the shop's
    sources where `section == "hulls"`. The same table serves every section.
    """

    shop = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name="sources")
    section = models.CharField(max_length=20, choices=SECTION_CHOICES)
    character = models.ForeignKey("memberaudit.Character", on_delete=models.CASCADE, related_name="+")
    location = models.ForeignKey("memberaudit.Location", on_delete=models.CASCADE, related_name="+")
    container_item_id = models.PositiveBigIntegerField(null=True, blank=True)
    container_name = models.CharField(max_length=100, blank=True, default="")
    price_mode = models.CharField(
        max_length=20, choices=PRICE_MODE_CHOICES, default=FIXED_PRICE
    )
    price_pct = models.DecimalField(
        max_digits=5, decimal_places=2, null=True, blank=True
    )  # default price rule inherited by first-seen auto-listed hulls

    def __str__(self):
        return f"{self.shop_id}:{self.section}:char{self.character_id}@loc{self.location_id}"

    @property
    def price_rule_display(self) -> str:
        """The default price rule applied to first-seen hulls (Fixed shows 'Ask')."""
        return format_price_rule(self.price_mode, self.price_pct)
