import os
import re
import shutil
from django.db import models
from django.urls import reverse
from django.conf import settings
from django.dispatch import receiver
from django.core.exceptions import ValidationError

from tinymce import models as tinymce

from ram.models import BaseModel, Image, PropertyInstance
from ram.utils import DeduplicatedStorage, slugify
from ram.managers import RollingStockManager
from metadata.models import (
    Scale,
    Manufacturer,
    Shop,
    Decoder,
    Company,
    Tag,
    RollingStockType,
)


class RollingClass(models.Model):
    identifier = models.CharField(max_length=128, unique=False)
    type = models.ForeignKey(RollingStockType, on_delete=models.CASCADE)
    company = models.ForeignKey(Company, on_delete=models.CASCADE)
    description = tinymce.HTMLField(blank=True)
    manufacturer = models.ManyToManyField(
        Manufacturer,
        blank=True,
        limit_choices_to={"category": "real"},
    )

    class Meta:
        ordering = ["company", "identifier"]
        verbose_name = "Class"
        verbose_name_plural = "Classes"
        indexes = [
            models.Index(fields=["company"], name="roster_rc_company_idx"),
            models.Index(fields=["type"], name="roster_rc_type_idx"),
            models.Index(
                fields=["company", "identifier"],
                name="roster_rc_co_ident_idx",  # Shortened to fit 30 char limit
            ),
        ]

    def __str__(self):
        return "{0} {1}".format(self.company, self.identifier)

    @property
    def country(self):
        return self.company.country


class RollingClassProperty(PropertyInstance):
    rolling_class = models.ForeignKey(
        RollingClass,
        on_delete=models.CASCADE,
        null=False,
        blank=False,
        related_name="property",
        verbose_name="Class",
    )


class RollingStock(BaseModel):
    rolling_class = models.ForeignKey(
        RollingClass,
        on_delete=models.CASCADE,
        null=False,
        blank=False,
        related_name="rolling_stock",
        verbose_name="Class",
    )
    road_number = models.CharField(max_length=128, unique=False)
    road_number_int = models.PositiveIntegerField(default=0, unique=False)
    manufacturer = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        limit_choices_to={"category": "model"},
    )
    scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
    item_number = models.CharField(
        max_length=32,
        blank=True,
        help_text="Catalog item number or code",
    )
    item_number_slug = models.CharField(
        max_length=32, blank=True, editable=False
    )
    set = models.BooleanField(
        default=False,
        help_text="Part of a set",
    )
    decoder_interface = models.PositiveSmallIntegerField(
        choices=settings.DECODER_INTERFACES, null=True, blank=True
    )
    decoder = models.ForeignKey(
        Decoder, on_delete=models.CASCADE, null=True, blank=True
    )
    address = models.SmallIntegerField(default=None, null=True, blank=True)
    era = models.CharField(
        max_length=32,
        blank=True,
        help_text="Era or epoch of the model",
    )
    production_year = models.SmallIntegerField(null=True, blank=True)
    shop = models.ForeignKey(
        Shop, on_delete=models.SET_NULL, null=True, blank=True
    )
    purchase_date = models.DateField(null=True, blank=True)
    price = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        null=True,
        blank=True,
    )
    featured = models.BooleanField(
        default=False,
        help_text="Featured rolling stock will appear on the homepage",
    )
    tags = models.ManyToManyField(
        Tag, related_name="rolling_stock", blank=True
    )

    objects = RollingStockManager()

    class Meta:
        ordering = ["rolling_class", "road_number_int"]
        verbose_name_plural = "Rolling stock"
        indexes = [
            # Index for published/featured filtering
            models.Index(fields=["published"], name="roster_published_idx"),
            models.Index(fields=["featured"], name="roster_featured_idx"),
            # Index for item number searches
            models.Index(
                fields=["item_number_slug"], name="roster_item_slug_idx"
            ),
            # Index for road number searches and ordering
            models.Index(
                fields=["road_number_int"], name="roster_road_num_idx"
            ),
            # Composite index for common filtering patterns
            models.Index(
                fields=["published", "featured"], name="roster_pub_feat_idx"
            ),
            # Composite index for manufacturer+item_number lookups
            models.Index(
                fields=["manufacturer", "item_number_slug"],
                name="roster_mfr_item_idx",
            ),
            # Index for scale filtering
            models.Index(fields=["scale"], name="roster_scale_idx"),
        ]

    def __str__(self):
        return "{0} {1}".format(self.rolling_class, self.road_number)

    def get_absolute_url(self):
        return reverse("rolling_stock", kwargs={"uuid": self.uuid})

    def preview(self):
        return self.image.first().image_thumbnail(350)

    # similar to get_decoder_interface_display in template render,
    # but returns "-" if no decoder interface is set
    def get_decoder_interface(self):
        return str(
            dict(settings.DECODER_INTERFACES).get(self.decoder_interface)
            or "No interface"
        )

    def dcc(self):
        if self.decoder:
            dcc = (
                '<i class="bi bi-volume-up-fill"></i>'
                if self.decoder.sound
                else '<i class="bi bi-cpu-fill"></i>'
            )
            dcc = f'<abbr title="{self.decoder} ({self.get_decoder_interface()})">{dcc}</abbr>'  # noqa: E501
        elif self.decoder_interface:
            dcc = f'<abbr title="{self.get_decoder_interface()}"><i class="bi bi-cpu"></i></abbr>'  # noqa: E501
        else:
            dcc = f'<abbr title="{self.get_decoder_interface()}"><i class="bi bi-ban"></i></abbr>'  # noqa: E501
        return dcc

    @property
    def country(self):
        return self.rolling_class.company.country

    @property
    def company(self):
        return self.rolling_class.company

    def delete(self, *args, **kwargs):
        shutil.rmtree(
            os.path.join(
                settings.MEDIA_ROOT, "images", "rollingstock", str(self.uuid)
            ),
            ignore_errors=True,
        )
        super(RollingStock, self).delete(*args, **kwargs)

    def clean(self, *args, **kwargs):
        if self.featured:
            MAX = settings.FEATURED_ITEMS_MAX
            featured_count = (
                RollingStock.objects.filter(featured=True)
                .exclude(uuid=self.uuid)
                .count()
            )
            if featured_count > MAX - 1:
                raise ValidationError(
                    "There are already {} featured items".format(MAX)
                )


@receiver(models.signals.pre_save, sender=RollingStock)
def pre_save_internal_fields(sender, instance, *args, **kwargs):
    # Extract road number integer from road number
    try:
        instance.road_number_int = int(
            re.findall(r"\d+", instance.road_number)[0]
        )
    except IndexError:
        pass
    # Generate a machine-friendly item number from original item number
    instance.item_number_slug = slugify(instance.item_number)


def rolling_stock_image_upload(instance, filename):
    return os.path.join(
        "images", "rollingstock", str(instance.rolling_stock.uuid), filename
    )


class RollingStockImage(Image):
    rolling_stock = models.ForeignKey(
        RollingStock, on_delete=models.CASCADE, related_name="image"
    )
    image = models.ImageField(
        upload_to=rolling_stock_image_upload,
        storage=DeduplicatedStorage,
    )


class RollingStockProperty(PropertyInstance):
    rolling_stock = models.ForeignKey(
        RollingStock,
        on_delete=models.CASCADE,
        related_name="property",
        null=False,
        blank=False,
    )


class RollingStockJournal(models.Model):
    rolling_stock = models.ForeignKey(
        RollingStock,
        on_delete=models.CASCADE,
        related_name="journal",
        null=False,
        blank=False,
    )
    date = models.DateField()
    log = tinymce.HTMLField()
    private = models.BooleanField(
        default=False,
        help_text="Journal log will be visible only to logged users",
    )
    creation_time = models.DateTimeField(auto_now_add=True)
    updated_time = models.DateTimeField(auto_now=True)

    def __str__(self):
        return "{0} - {1}".format(self.rolling_stock, self.date)

    class Meta:
        ordering = ["date", "rolling_stock"]

    objects = RollingStockManager()


# @receiver(models.signals.post_delete, sender=Cab)
# def post_save_image(sender, instance, *args, **kwargs):
#     try:
#         instance.image.delete(save=False)
#     except Exception:
#         pass
