"""Persistence models for the module system.

The registry (``registry.py``) holds *declarations* — the code-defined
manifests. These models hold *runtime state* per tenant: what is installed,
which version, whether enabled, plus feature-flag overrides.
"""

from __future__ import annotations

from typing import ClassVar

from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import TenantScopedModel, TimeStampedModel, UUIDModel

# ---------------------------------------------------------------------------
# Phase A — Domain taxonomy
# ---------------------------------------------------------------------------


class Domain(UUIDModel, TimeStampedModel):
    """Top-level architectural grouping for modules.

    Replaces the flat ``module_type`` enum with a first-class entity.
    Modules belong to exactly one Domain (e.g. "platform", "commerce", "business").
    """

    code = models.SlugField(
        _("code"),
        max_length=40,
        unique=True,
        db_index=True,
        help_text=_("Stable machine-readable code, e.g. 'platform', 'commerce', 'business'."),
    )
    name = models.CharField(_("name"), max_length=120)
    description = models.TextField(_("description"), blank=True, default="")
    sort_order = models.PositiveSmallIntegerField(
        _("sort order"),
        default=0,
        db_index=True,
        help_text=_("Controls display order; lower values appear first."),
    )

    class Meta:
        verbose_name = _("domain")
        verbose_name_plural = _("domains")
        ordering = ("sort_order", "code")

    def __str__(self) -> str:
        return f"{self.name} ({self.code})"


# ---------------------------------------------------------------------------
# Phase A — Capability (replaces FeatureGroup)
# ---------------------------------------------------------------------------


class Capability(UUIDModel, TimeStampedModel):
    """Sub-domain grouping for features within a module.

    Replaces the deprecated FeatureGroup with explicit Domain and Module linkage.
    Examples: "CRM.Sales", "Chat.Messaging", "Reporting.Operational".
    FeatureGroup was removed in Phase D.
    """

    domain = models.ForeignKey(
        Domain,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="capabilities",
        verbose_name=_("domain"),
        help_text=_("Top-level domain this capability belongs to."),
    )
    module_name = models.CharField(
        _("module name"),
        max_length=80,
        blank=True,
        default="",
        db_index=True,
        help_text=_("The module that provides this capability, e.g. 'crm'."),
    )
    name = models.CharField(
        _("name"),
        max_length=120,
        unique=True,
        db_index=True,
        help_text=_("Unique capability name, e.g. 'CRM Sales' or 'Reports'."),
    )
    slug = models.SlugField(
        _("slug"),
        max_length=120,
        unique=True,
        db_index=True,
        help_text=_("URL-friendly identifier, e.g. 'crm-sales' or 'reports'."),
    )
    icon = models.CharField(
        _("icon"),
        max_length=60,
        blank=True,
        default="",
        help_text=_("Optional icon identifier for UI rendering."),
    )
    sort_order = models.PositiveSmallIntegerField(
        _("sort order"),
        default=0,
        db_index=True,
        help_text=_("Controls display order; lower values appear first."),
    )
    description = models.TextField(_("description"), blank=True, default="")

    class Meta:
        verbose_name = _("capability")
        verbose_name_plural = _("capabilities")
        ordering = ("sort_order", "name")

    def __str__(self) -> str:
        return self.name


class FeatureCatalog(UUIDModel, TimeStampedModel):
    """Global registry of feature codes — one row per feature declared in any module manifest.

    Not tenant-scoped. Used to gate navigation items and entitlement checks.
    Examples: 'chat.group_messaging', 'helpdesk.automation'.
    """

    code = models.CharField(
        _("code"),
        max_length=80,
        unique=True,
        db_index=True,
        help_text=_("Dotted code, e.g. 'chat.group_messaging'. Must be unique globally."),
    )
    label = models.CharField(_("label"), max_length=200, blank=True)
    module_name = models.CharField(
        _("module"),
        max_length=80,
        blank=True,
        help_text=_("The module that provides this feature, e.g. 'chat'."),
    )
    # Phase A — capability FK (replaces group)
    capability = models.ForeignKey(
        Capability,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="features",
        verbose_name=_("capability"),
        help_text=_("Capability grouping (Phase A — replaces deprecated group)."),
    )
    description = models.TextField(_("description"), blank=True, default="")

    # ── Licensing fields ────────────────────────────────────────────────────
    # Phase B: category + addon_price removed — domain+capability taxonomy
    # and Plan/Addon entitlement configuration replace them.

    is_always_on = models.BooleanField(
        _("always on"),
        default=False,
        help_text=_("If True this feature is always enabled — platform internals."),
    )
    is_platform_internal = models.BooleanField(
        _("platform internal"),
        default=False,
        help_text=_("If True this feature is never sold or licensed to tenants."),
    )

    class Meta:
        verbose_name = _("feature")
        verbose_name_plural = _("features")
        ordering = ("module_name", "code")

    def __str__(self) -> str:
        return self.code


class ModuleType(models.TextChoices):
    PLATFORM = "platform", _("Platform")  # infrastructure, always available, never sold separately
    BUSINESS = "business", _("Business")  # domain module, licensable via subscription


class ModuleStatus(models.TextChoices):
    INSTALLED = "installed", _("Installed")  # known, but not yet enabled
    ENABLED = "enabled", _("Enabled")  # active for this tenant
    DISABLED = "disabled", _("Disabled")  # temporarily off
    UNINSTALLED = "uninstalled", _("Uninstalled")  # archived row (tombstone)


class TenantModule(UUIDModel, TenantScopedModel):
    """Per-tenant state record for an installed module (BUSINESS or PLATFORM).

    Tracks installation lifecycle: version, migration state, enable/disable timestamps.
    Used for upgrade, rollback, audit, and billing hooks.
    """

    name = models.CharField(_("name"), max_length=128, db_index=True)
    version = models.PositiveIntegerField(_("installed version"), default=1)
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=ModuleStatus.choices,
        default=ModuleStatus.INSTALLED,
        db_index=True,
    )
    config = models.JSONField(_("config"), default=dict, blank=True)
    previous_version = models.PositiveIntegerField(
        _("previous version"),
        null=True,
        blank=True,
        help_text=_("Set by upgrade to allow rollback."),
    )
    installed_at = models.DateTimeField(_("installed at"), null=True, blank=True)
    enabled_at = models.DateTimeField(_("enabled at"), null=True, blank=True)
    disabled_at = models.DateTimeField(_("disabled at"), null=True, blank=True)

    class Meta:
        verbose_name = _("tenant module")
        verbose_name_plural = _("tenant modules")
        ordering = ("name",)
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("tenant", "name"),
                name="modules_module_unique_per_tenant",
            ),
        ]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "status")),
        ]

    def __str__(self) -> str:
        return f"{self.name} v{self.version} ({self.status})"


class TenantFeatureOverride(UUIDModel, TenantScopedModel):
    """Per-tenant (or per-user) override for a module-declared feature flag.

    A missing row means the module manifest's default value applies.
    Uses ``feature_key`` as the primary lookup (Phase D: module FK removed).
    """

    feature_key = models.CharField(
        _("feature key"),
        max_length=120,
        blank=True,
        default="",
        db_index=True,
        help_text=_("Dotted key, e.g. 'crm.pipeline'. Will replace module FK in Phase 2."),
    )
    name = models.CharField(_("flag"), max_length=64)
    enabled = models.BooleanField(_("enabled"))
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="+",
        null=True,
        blank=True,
        help_text=_("Per-user override; null means tenant-wide."),
    )

    class Meta:
        verbose_name = _("feature override")
        verbose_name_plural = _("feature overrides")
        ordering = ("feature_key", "name")
        constraints: ClassVar[list[models.BaseConstraint]] = [
            # Phase 2: unique on (tenant, feature_key, user) — module FK is optional now.
            models.UniqueConstraint(
                fields=("tenant", "feature_key", "user"),
                name="modules_featureoverride_unique_fkey_scope",
                condition=models.Q(feature_key__gt=""),
            ),
        ]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "feature_key")),
        ]

    def __str__(self) -> str:
        scope = f"user:{self.user_id}" if self.user_id else "tenant"
        label = self.feature_key or self.name
        return f"{label}={self.enabled} ({scope})"


class ModuleCatalog(UUIDModel, TimeStampedModel):
    """Global registry of all available modules on the platform.

    Unlike ``Module`` (per-tenant installation state), this table mirrors
    every registered ``ModuleManifest`` — it is the single source of truth
    for what modules exist.
    Phase B: module_type removed — domain replaces tier+type grouping.
    """

    name = models.CharField(
        _("name"),
        max_length=128,
        unique=True,
        db_index=True,
        help_text=_("Stable dotted module name, e.g. 'crm'."),
    )
    version = models.PositiveIntegerField(_("version"), default=1)
    label = models.CharField(_("label"), max_length=200, blank=True)
    description = models.TextField(_("description"), blank=True, default="")
    # Phase A — Domain taxonomy (replaces tier + module_type grouping)
    domain = models.ForeignKey(
        Domain,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="module_catalog_entries",
        verbose_name=_("domain"),
        help_text=_("Top-level domain (Phase A — replaces module_type for grouping)."),
    )
    depends_on = models.JSONField(
        _("depends on"),
        default=list,
        blank=True,
        help_text=_("List of module names this module depends on."),
    )
    permissions = models.JSONField(
        _("permissions"),
        default=list,
        blank=True,
        help_text=_("All permission codenames declared by this module."),
    )
    events = models.JSONField(
        _("events"),
        default=list,
        blank=True,
        help_text=_("All domain event names emitted by this module."),
    )
    feature_flags = models.JSONField(
        _("feature flags"),
        default=list,
        blank=True,
        help_text=_("All developer feature-flag names declared by this module."),
    )

    class Meta:
        verbose_name = _("module catalog")
        verbose_name_plural = _("module catalog entries")
        ordering = ("name",)

    def __str__(self) -> str:
        return f"{self.name} (v{self.version})"


class FeatureEntitlementSource(models.TextChoices):
    INCLUDED = "included", _("Included")
    PURCHASED = "purchased", _("Purchased")
    TRIAL = "trial", _("Trial")
    GRANTED = "granted", _("Granted")


class FeatureEntitlement(UUIDModel, TenantScopedModel):
    """Authorisation for a tenant (or user) to use a module feature.

    The presence of a row says "you may use this feature"; ``enabled`` lets
    the entitlement holder toggle it on/off without losing entitlement.
    ``expires_at`` lets trials and time-boxed purchases expire automatically.

    Phase D: ``feature_key`` is the canonical lookup (module FK + feature_code removed).
    Phase B: ``capability_key`` added for capability-scoped entitlement grouping.
    """

    feature_key = models.CharField(
        _("feature key"),
        max_length=120,
        blank=True,
        default="",
        db_index=True,
        help_text=_("Dotted key, e.g. 'crm.sales.pipeline'. Primary lookup for licensing decisions."),
    )
    capability_key = models.CharField(
        _("capability key"),
        max_length=120,
        blank=True,
        default="",
        db_index=True,
        help_text=_("Capability-scoped key, e.g. 'crm.sales'. For grouping entitlements by capability."),
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="+",
        null=True,
        blank=True,
        help_text=_("Per-user entitlement; null means tenant-wide."),
    )
    source = models.CharField(
        _("source"),
        max_length=16,
        choices=FeatureEntitlementSource.choices,
        default=FeatureEntitlementSource.INCLUDED,
    )
    enabled = models.BooleanField(_("enabled"), default=True)
    starts_at = models.DateTimeField(_("starts at"), null=True, blank=True)
    expires_at = models.DateTimeField(_("expires at"), null=True, blank=True)
    revoked_at = models.DateTimeField(_("revoked at"), null=True, blank=True)
    metadata = models.JSONField(_("metadata"), default=dict, blank=True)

    class Meta:
        verbose_name = _("feature entitlement")
        verbose_name_plural = _("feature entitlements")
        ordering = ("feature_key",)
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("tenant", "feature_key", "user"),
                name="modules_entitlement_unique_scope_v2",
                condition=models.Q(feature_key__gt=""),
            ),
        ]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(
                fields=("tenant", "feature_key"),
                name="platform_mo_tenant_fkey_idx",
            ),
            models.Index(fields=("tenant", "user", "revoked_at")),
        ]

    def __str__(self) -> str:
        scope = f"user:{self.user_id}" if self.user_id else "tenant"
        return f"{self.feature_key} ({self.source}) [{scope}]"
