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 TimeStampedModel, UUIDModel


class Permission(TimeStampedModel):
    """Permission catalogue. Codename is ``module.resource.action``.

    Source of truth is the in-code `PermissionRegistry`; these rows are kept
    in sync on `post_migrate`. We still store them in the DB so role-permission
    M2M relations and admin browsing work normally.
    """

    codename = models.CharField(_("codename"), max_length=128, unique=True)
    label = models.CharField(_("label"), max_length=255)
    module = models.CharField(_("module"), max_length=64, db_index=True)
    resource = models.CharField(_("resource"), max_length=64, db_index=True)
    action = models.CharField(_("action"), max_length=32, db_index=True)
    description = models.TextField(_("description"), blank=True, default="")

    class Meta:
        verbose_name = _("permission")
        verbose_name_plural = _("permissions")
        ordering: ClassVar[list[str]] = ["codename"]
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("module", "resource", "action")),
        ]

    def __str__(self) -> str:
        return self.codename


class Role(UUIDModel, TimeStampedModel):
    """Tenant-scoped role with single-parent inheritance.

    Resolved permissions = own permissions UNION parent.resolved (recursive).
    `is_system` roles are seeded and never user-editable.
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="roles",
        null=True,
        blank=True,
        verbose_name=_("tenant"),
        help_text=_("Null = global/system role available to all tenants."),
    )
    code = models.CharField(_("code"), max_length=64)
    name = models.CharField(_("name"), max_length=255)
    description = models.TextField(_("description"), blank=True, default="")
    parent_role = models.ForeignKey(
        "self",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="children",
        verbose_name=_("parent role"),
    )
    permissions = models.ManyToManyField(
        Permission,
        related_name="roles",
        blank=True,
        verbose_name=_("permissions"),
    )
    is_system = models.BooleanField(_("is system"), default=False)

    class Meta:
        verbose_name = _("role")
        verbose_name_plural = _("roles")
        ordering: ClassVar[list[str]] = ["tenant_id", "code"]
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("tenant", "code"),
                name="iam_role_unique_tenant_code",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.tenant_id or 'global'}:{self.code}"


class Policy(UUIDModel, TimeStampedModel):
    """ABAC policy — extra conditions evaluated on top of RBAC.

    `conditions` is a JSON expression understood by `iam.evaluator`. Each
    policy targets one permission and one role; the engine combines them with
    AND semantics: RBAC must grant **and** all attached policies must pass.
    """

    role = models.ForeignKey(
        Role,
        on_delete=models.CASCADE,
        related_name="policies",
        verbose_name=_("role"),
    )
    permission = models.ForeignKey(
        Permission,
        on_delete=models.CASCADE,
        related_name="policies",
        verbose_name=_("permission"),
    )
    name = models.CharField(_("name"), max_length=255)
    conditions = models.JSONField(_("conditions"), default=dict, blank=True)
    is_active = models.BooleanField(_("is active"), default=True)

    class Meta:
        verbose_name = _("policy")
        verbose_name_plural = _("policies")
        ordering: ClassVar[list[str]] = ["role_id", "permission_id"]

    def __str__(self) -> str:
        return f"{self.role_id}:{self.permission_id}:{self.name}"


class UserRole(TimeStampedModel):
    """Direct global role assignment to a user (not workspace-scoped).

    Workspace-scoped role assignment is handled by ``WorkspaceMembership.roles``.
    """

    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="iam_user_roles",
        verbose_name=_("user"),
    )
    role = models.ForeignKey(
        Role,
        on_delete=models.CASCADE,
        related_name="user_assignments",
        verbose_name=_("role"),
    )

    class Meta:
        verbose_name = _("user role assignment")
        verbose_name_plural = _("user role assignments")
        ordering: ClassVar[list[str]] = ["user_id"]
        constraints: ClassVar[list[models.BaseConstraint]] = [
            models.UniqueConstraint(
                fields=("user", "role"),
                name="iam_userrole_unique",
            )
        ]

    def __str__(self) -> str:
        return f"{self.user_id}\u2192{self.role_id}"
