import os

from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.text import slugify

from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_PLAIN, render_content
from apps.core.video import VideoBlockMixin

from .release_assets import (
    FILE_FIELD_BY_ASSET_KEY,
    RELEASE_ASSET_KEY_BY_URL_FIELD,
    RELEASE_FILE_BY_URL_FIELD,
    RELEASE_FILE_HELP_TEXT,
    RELEASE_ORIGINAL_FILENAME_BY_ASSET,
    RELEASE_ORIGINAL_FILENAME_FIELDS,
    release_file_storage,
    release_linux_file_upload_to,
    release_macos_file_upload_to,
    release_source_file_upload_to,
    release_windows_file_upload_to,
)


INSTALLABLE_PLATFORM_SPECS = (
    ("windows_download_url", "Windows", "images/icon-windows.svg"),
    ("macos_download_url", "macOS", "images/icon-macos.svg"),
    ("linux_download_url", "Linux", "images/icon-linux.svg"),
    ("source_code_url", "Source code", None),
)

DISTRIBUTION_INSTALLABLE = "installable"
DISTRIBUTION_PACKAGE = "package"
DISTRIBUTION_CHOICES = [
    (DISTRIBUTION_INSTALLABLE, "Installable application"),
    (DISTRIBUTION_PACKAGE, "Package (external / non-installable)"),
]

RELEASE_CHANNEL_STABLE = "stable"
RELEASE_CHANNEL_BETA = "beta"
RELEASE_CHANNEL_RC = "rc"
RELEASE_CHANNEL_PREVIEW = "preview"
RELEASE_CHANNEL_NIGHTLY = "nightly"
RELEASE_CHANNEL_CURRENT = "current"
RELEASE_CHANNEL_PREVIOUS = "previous"

RELEASE_CHANNEL_CHOICES = [
    ("", "None"),
    (RELEASE_CHANNEL_STABLE, "Stable"),
    (RELEASE_CHANNEL_BETA, "Beta"),
    (RELEASE_CHANNEL_RC, "Release candidate"),
    (RELEASE_CHANNEL_PREVIEW, "Preview"),
    (RELEASE_CHANNEL_NIGHTLY, "Nightly"),
    (RELEASE_CHANNEL_CURRENT, "Current"),
    (RELEASE_CHANNEL_PREVIOUS, "Previous"),
]

RELEASE_CHANNEL_LABELS = {
    RELEASE_CHANNEL_STABLE: "Stable",
    RELEASE_CHANNEL_BETA: "Beta",
    RELEASE_CHANNEL_RC: "Release candidate",
    RELEASE_CHANNEL_PREVIEW: "Preview",
    RELEASE_CHANNEL_NIGHTLY: "Nightly",
    RELEASE_CHANNEL_CURRENT: "Current",
    RELEASE_CHANNEL_PREVIOUS: "Previous",
}


class MainProduct(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, blank=True)
    short_description = models.CharField(max_length=300)
    description = models.TextField()
    description_format = models.CharField(
        max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
    )
    image = models.ImageField(upload_to="products/main/", blank=True, null=True)
    distribution = models.CharField(
        max_length=20,
        choices=DISTRIBUTION_CHOICES,
        default=DISTRIBUTION_INSTALLABLE,
        help_text="Only affects how releases appear on the public site.",
    )
    package_resource_button_text = models.CharField(
        max_length=100,
        default="PyPI",
        help_text="Label for the package resource button on package releases.",
    )
    package_source_button_text = models.CharField(
        max_length=100,
        default="Source",
        help_text="Label for the source code button on package releases.",
    )
    downloads_section_link_url = models.URLField(
        blank=True,
        help_text="Optional link shown in the top-right corner of the downloads section.",
    )
    downloads_section_link_text = models.CharField(
        max_length=100,
        blank=True,
        help_text="Label for the optional downloads section link.",
    )
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)
    show_on_homepage = models.BooleanField(default=False, help_text="Display this product in the homepage Products section.")
    homepage_order = models.PositiveIntegerField(default=0, help_text="Order within the homepage Products section.")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["order", "name"]
        verbose_name = "Main Product"
        verbose_name_plural = "Main Products"

    @property
    def rendered_description(self):
        return render_content(self.description, self.description_format)

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("products:main_product_detail", kwargs={"main_slug": self.slug})

    def get_versions_archive_url(self):
        return reverse(
            "products:main_product_versions",
            kwargs={"main_slug": self.slug},
        )


class SubProduct(models.Model):
    DISTRIBUTION_INSTALLABLE = DISTRIBUTION_INSTALLABLE
    DISTRIBUTION_PACKAGE = DISTRIBUTION_PACKAGE
    DISTRIBUTION_CHOICES = DISTRIBUTION_CHOICES

    main_product = models.ForeignKey(
        MainProduct,
        on_delete=models.CASCADE,
        related_name="sub_products",
    )
    name = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)
    short_description = models.CharField(max_length=300)
    description = models.TextField()
    description_format = models.CharField(
        max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
    )
    image = models.ImageField(upload_to="products/sub/", blank=True, null=True)
    logo = models.ImageField(upload_to="products/sub_logos/", blank=True, null=True, help_text="Small logo shown as a corner badge on homepage product cards.")
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)
    show_on_homepage = models.BooleanField(default=False, help_text="Display this sub-product in the homepage Products section.")
    homepage_order = models.PositiveIntegerField(default=0, help_text="Order within the homepage Products section.")
    distribution = models.CharField(
        max_length=20,
        choices=DISTRIBUTION_CHOICES,
        default=DISTRIBUTION_INSTALLABLE,
        help_text="Only affects how releases appear on the public site.",
    )
    package_resource_button_text = models.CharField(
        max_length=100,
        default="PyPI",
        help_text="Label for the package resource button on package releases.",
    )
    package_source_button_text = models.CharField(
        max_length=100,
        default="Source",
        help_text="Label for the source code button on package releases.",
    )
    downloads_section_link_url = models.URLField(
        blank=True,
        help_text="Optional link shown in the top-right corner of the downloads section.",
    )
    downloads_section_link_text = models.CharField(
        max_length=100,
        blank=True,
        help_text="Label for the optional downloads section link.",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["order", "name"]
        unique_together = [["main_product", "slug"]]
        verbose_name = "Sub Product"
        verbose_name_plural = "Sub Products"

    @property
    def rendered_description(self):
        return render_content(self.description, self.description_format)

    def __str__(self):
        return f"{self.main_product.name} › {self.name}"

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse(
            "products:sub_product_detail",
            kwargs={
                "main_slug": self.main_product.slug,
                "sub_slug": self.slug,
            },
        )

    def get_versions_archive_url(self):
        return reverse(
            "products:sub_product_versions",
            kwargs={
                "main_slug": self.main_product.slug,
                "sub_slug": self.slug,
            },
        )


class Article(models.Model):
    main_product = models.ForeignKey(
        MainProduct,
        on_delete=models.CASCADE,
        related_name="articles",
        null=True,
        blank=True,
    )
    sub_product = models.ForeignKey(
        SubProduct,
        on_delete=models.CASCADE,
        related_name="articles",
        null=True,
        blank=True,
    )
    badge = models.CharField(
        max_length=100,
        blank=True,
        help_text="Optional label shown above the article title (e.g. 'Overview', 'Note').",
    )
    title = models.CharField(max_length=300)
    description = models.TextField()
    description_format = models.CharField(
        max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
    )
    citation_count_display = models.PositiveSmallIntegerField(
        null=True,
        blank=True,
        help_text="Optional number shown in the dashed citation circle badge. Leave blank to show an empty circle (if a label is set) or hide the badge entirely.",
    )
    citation_count_label = models.CharField(
        max_length=50,
        blank=True,
        default="",
        help_text="Optional label shown below the number in the citation circle badge (e.g. 'cited'). Leave blank to show no label.",
    )
    order = models.PositiveIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["order", "title"]
        verbose_name = "Article"
        verbose_name_plural = "Articles"
        constraints = [
            models.CheckConstraint(
                check=(
                    models.Q(sub_product__isnull=False, main_product__isnull=True)
                    | models.Q(sub_product__isnull=True, main_product__isnull=False)
                ),
                name="article_exactly_one_parent",
            ),
        ]

    @property
    def rendered_description(self):
        return render_content(self.description, self.description_format)

    def clean(self):
        super().clean()
        has_main = self.main_product_id is not None
        has_sub = self.sub_product_id is not None
        if has_main == has_sub:
            raise ValidationError(
                "An article must belong to exactly one main product or sub-product."
            )

    def __str__(self):
        return self.title


class SubProductVersion(models.Model):
    main_product = models.ForeignKey(
        MainProduct,
        on_delete=models.CASCADE,
        related_name="versions",
        null=True,
        blank=True,
    )
    sub_product = models.ForeignKey(
        SubProduct,
        on_delete=models.CASCADE,
        related_name="versions",
        null=True,
        blank=True,
    )
    version = models.CharField(max_length=80)
    release_channel = models.CharField(
        max_length=20,
        choices=RELEASE_CHANNEL_CHOICES,
        blank=True,
        default="",
        help_text="Preset badge for this release (Stable, Beta, Previous, etc.).",
    )
    channel_label = models.CharField(
        max_length=50,
        blank=True,
        help_text="Optional custom badge text. Overrides the preset channel label when set.",
    )
    is_featured = models.BooleanField(
        default=False,
        help_text="Primary release shown at the top of the downloads section.",
    )
    show_on_product_page = models.BooleanField(
        default=False,
        help_text="Also show this release on the product page (e.g. beta or previous version).",
    )
    windows_download_url = models.URLField(blank=True)
    macos_download_url = models.URLField(blank=True)
    linux_download_url = models.URLField(blank=True)
    source_code_url = models.URLField(blank=True)
    windows_download_file = models.FileField(
        upload_to=release_windows_file_upload_to,
        storage=release_file_storage,
        blank=True,
        help_text=RELEASE_FILE_HELP_TEXT,
    )
    macos_download_file = models.FileField(
        upload_to=release_macos_file_upload_to,
        storage=release_file_storage,
        blank=True,
        help_text=RELEASE_FILE_HELP_TEXT,
    )
    linux_download_file = models.FileField(
        upload_to=release_linux_file_upload_to,
        storage=release_file_storage,
        blank=True,
        help_text=RELEASE_FILE_HELP_TEXT,
    )
    source_code_file = models.FileField(
        upload_to=release_source_file_upload_to,
        storage=release_file_storage,
        blank=True,
        help_text=RELEASE_FILE_HELP_TEXT,
    )
    windows_download_filename = models.CharField(max_length=255, blank=True)
    macos_download_filename = models.CharField(max_length=255, blank=True)
    linux_download_filename = models.CharField(max_length=255, blank=True)
    source_code_filename = models.CharField(max_length=255, blank=True)
    package_resource_url = models.URLField(
        blank=True,
        help_text="Shown on the site when set. For Resources it is the main Open link; for Installable it appears under the platform grid.",
    )
    release_notes = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "version", "pk"]
        verbose_name = "Release version"
        verbose_name_plural = "Release versions"
        constraints = [
            models.CheckConstraint(
                check=(
                    models.Q(sub_product__isnull=False, main_product__isnull=True)
                    | models.Q(sub_product__isnull=True, main_product__isnull=False)
                ),
                name="release_version_exactly_one_parent",
            ),
            models.UniqueConstraint(
                fields=["sub_product", "version"],
                condition=models.Q(sub_product__isnull=False),
                name="release_version_unique_sub_product_version",
            ),
            models.UniqueConstraint(
                fields=["main_product", "version"],
                condition=models.Q(main_product__isnull=False),
                name="release_version_unique_main_product_version",
            ),
        ]

    def clean(self):
        super().clean()
        has_main = self.main_product_id is not None
        has_sub = self.sub_product_id is not None
        if has_main == has_sub:
            raise ValidationError(
                "A release version must belong to exactly one main product or sub-product."
            )

    def __str__(self):
        parent = self.sub_product or self.main_product
        return f"{parent.name} v{self.version}"

    @property
    def display_channel_label(self):
        custom = (self.channel_label or "").strip()
        if custom:
            return custom
        if self.release_channel:
            return RELEASE_CHANNEL_LABELS.get(
                self.release_channel,
                self.release_channel.replace("_", " ").title(),
            )
        return ""

    @property
    def display_channel_css_modifier(self):
        if (self.channel_label or "").strip():
            return "custom"
        return self.release_channel or "none"

    @property
    def is_previous_channel(self):
        if self.release_channel == RELEASE_CHANNEL_PREVIOUS:
            return True
        label = (self.channel_label or "").strip().lower()
        return label == "previous"

    def save(self, *args, **kwargs):
        for file_field, name_field in RELEASE_ORIGINAL_FILENAME_FIELDS.items():
            field_file = getattr(self, file_field)
            if field_file and field_file._file is not None:
                setattr(self, name_field, os.path.basename(field_file.name))
            elif not field_file:
                setattr(self, name_field, "")
        super().save(*args, **kwargs)

    def release_download_filename(self, asset):
        name_field = RELEASE_ORIGINAL_FILENAME_BY_ASSET.get(asset)
        if name_field:
            stored = (getattr(self, name_field, "") or "").strip()
            if stored:
                return stored
        file_field = FILE_FIELD_BY_ASSET_KEY.get(asset)
        if file_field:
            release_file = getattr(self, file_field)
            if release_file:
                return os.path.basename(release_file.name)
        return "download"

    def has_platform_asset(self, url_field_name):
        if (getattr(self, url_field_name) or "").strip():
            return True
        file_field = RELEASE_FILE_BY_URL_FIELD.get(url_field_name)
        if not file_field:
            return False
        return bool(getattr(self, file_field))

    def resolve_asset_url(self, url_field_name):
        url = (getattr(self, url_field_name) or "").strip()
        if url:
            return url
        file_field = RELEASE_FILE_BY_URL_FIELD.get(url_field_name)
        if not file_field or not getattr(self, file_field):
            return ""
        if not self.pk:
            return ""
        asset = RELEASE_ASSET_KEY_BY_URL_FIELD[url_field_name]
        return reverse(
            "products:release_asset_download",
            kwargs={"version_id": self.pk, "asset": asset},
        )

    def install_urls_for_specs(self, specs):
        out = []
        for field_name, label, icon in specs:
            url = self.resolve_asset_url(field_name)
            if url:
                out.append(
                    {
                        "field": field_name,
                        "label": label,
                        "icon": icon,
                        "url": url,
                        "external": bool((getattr(self, field_name) or "").strip()),
                    }
                )
        return out

    def has_any_install_asset(self):
        return any(self.has_platform_asset(f[0]) for f in INSTALLABLE_PLATFORM_SPECS)

    @property
    def resolved_source_code_url(self):
        return self.resolve_asset_url("source_code_url")

    def has_package_link(self):
        return bool((self.package_resource_url or "").strip())


class ArticleSection(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        related_name="sections",
    )
    title = models.CharField(max_length=200)
    value = models.TextField()
    value_format = models.CharField(
        max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
    )
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order"]
        verbose_name = "Article Section"
        verbose_name_plural = "Article Sections"

    @property
    def rendered_value(self):
        return render_content(self.value, self.value_format)

    def __str__(self):
        return f"{self.article.title} › {self.title}"


class ArticleCitation(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        related_name="citations",
    )
    text = models.TextField(help_text="Full citation text.")
    url = models.URLField(blank=True, help_text="Optional link to the cited source.")
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "pk"]
        verbose_name = "Article Citation"
        verbose_name_plural = "Article Citations"

    def __str__(self):
        return f"{self.article.title} — citation {self.order or self.pk}"


class ProductVideo(VideoBlockMixin, models.Model):
    main_product = models.ForeignKey(
        MainProduct,
        on_delete=models.CASCADE,
        related_name="videos",
        null=True,
        blank=True,
    )
    sub_product = models.ForeignKey(
        SubProduct,
        on_delete=models.CASCADE,
        related_name="videos",
        null=True,
        blank=True,
    )
    badge = models.CharField(max_length=100, blank=True)
    title = models.CharField(max_length=300, blank=True)
    description = models.TextField(blank=True)
    order = models.PositiveIntegerField(default=0)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["order", "pk"]
        verbose_name = "Product Video"
        verbose_name_plural = "Product Videos"
        constraints = [
            models.CheckConstraint(
                check=(
                    models.Q(sub_product__isnull=False, main_product__isnull=True)
                    | models.Q(sub_product__isnull=True, main_product__isnull=False)
                ),
                name="product_video_exactly_one_parent",
            ),
        ]

    def clean(self):
        super().clean()
        has_main = self.main_product_id is not None
        has_sub = self.sub_product_id is not None
        if has_main == has_sub:
            raise ValidationError(
                "A product video must belong to exactly one main product or sub-product."
            )
        self.clean_video_fields(require=True)

    def __str__(self):
        parent = self.sub_product or self.main_product
        label = self.title or self.badge or "Video"
        return f"{parent} › {label}"
