"""Process Definition models — Phase 1 (Organizational Layer).

Tenant-owned process definitions that map onto the APQC PCF taxonomy.

Model inventory
---------------
ProcessDefinition   — a formalized organizational process linked to a PCF element.
ProcessOwnership    — role-based ownership/accountability per process.
ProcessDocument     — soft link to DMS documents (procedure, form, template …).

Design decisions
----------------
* ProcessDefinition uses ``TimeStampedModel`` + ``UUIDModel`` plus an explicit
  ``tenant`` FK instead of ``TenantScopedModel`` (which also requires an
  ``organization_node``).  Process definitions are company-wide catalogs, not
  scoped to a single org node.
* ProcessDocument stores ``dms_document_id`` (UUID CharField) — NOT a DB FK —
  to avoid tight coupling with the DMS app.  The UUID matches
  ``dms.documents.models.Document.public_id``.
* ProcessOwnership allows multiple users per process with different roles.
* parent FK on ProcessDefinition enables custom hierarchical decomposition
  independent of the PCF hierarchy.
"""

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
from simorgh.apps.bpm.models.pcf import PCFElement, PCFFramework


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class ProcessStatus(models.TextChoices):
    DRAFT      = "draft",      _("Draft")
    ACTIVE     = "active",     _("Active")
    DEPRECATED = "deprecated", _("Deprecated")
    ARCHIVED   = "archived",   _("Archived")


class OwnershipRole(models.TextChoices):
    OWNER    = "owner",    _("Process Owner")
    MANAGER  = "manager",  _("Process Manager")
    REVIEWER = "reviewer", _("Reviewer")
    APPROVER = "approver", _("Approver")


class DocLinkType(models.TextChoices):
    PROCEDURE = "procedure", _("Procedure")
    FORM      = "form",      _("Form")
    TEMPLATE  = "template",  _("Template")
    POLICY    = "policy",    _("Policy")
    GUIDELINE = "guideline", _("Guideline")
    OTHER     = "other",     _("Other")


# ---------------------------------------------------------------------------
# ProcessDefinition
# ---------------------------------------------------------------------------

class ProcessDefinition(UUIDModel, TimeStampedModel):
    """A formalized organizational process, optionally mapped to a PCF element.

    The ``hierarchy_id`` follows the same dot-notation as PCF (e.g. "1.1.1")
    but may diverge to reflect the organization's own decomposition.

    Status lifecycle: draft → active → deprecated / archived.
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="process_definitions",
        verbose_name=_("tenant"),
    )
    # ------------------------------------------------------------------
    # Taxonomy / mapping
    # ------------------------------------------------------------------
    framework = models.ForeignKey(
        PCFFramework,
        on_delete=models.PROTECT,
        related_name="process_definitions",
        verbose_name=_("PCF framework"),
        help_text=_("The PCF edition this definition is aligned with."),
    )
    pcf_element = models.ForeignKey(
        PCFElement,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="process_definitions",
        verbose_name=_("PCF element"),
        help_text=_("Standard PCF node this process maps to (optional)."),
    )
    # ------------------------------------------------------------------
    # Hierarchy
    # ------------------------------------------------------------------
    parent = models.ForeignKey(
        "self",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="children",
        verbose_name=_("parent process"),
    )
    hierarchy_id = models.CharField(
        _("hierarchy ID"),
        max_length=64,
        help_text=_('Dot-notation identifier, e.g. "1.1.1".'),
    )
    level = models.PositiveSmallIntegerField(
        _("level"),
        default=1,
        help_text=_("1=Category, 2=Process Group, 3=Process, 4=Activity."),
    )
    # ------------------------------------------------------------------
    # Identity
    # ------------------------------------------------------------------
    name = models.CharField(_("name (EN)"), max_length=512)
    name_fa = models.CharField(_("name (FA)"), max_length=512, blank=True)
    description = models.TextField(_("description"), blank=True)
    trigger = models.TextField(
        _("trigger"),
        blank=True,
        help_text=_("Event or condition that initiates this process."),
    )
    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=ProcessStatus.choices,
        default=ProcessStatus.DRAFT,
        db_index=True,
    )
    version = models.CharField(
        _("version"),
        max_length=32,
        default="1.0",
        help_text=_('Semantic version string, e.g. "1.0", "2.3".'),
    )
    effective_date = models.DateField(_("effective date"), null=True, blank=True)
    review_date = models.DateField(_("review date"), null=True, blank=True)

    class Meta:
        verbose_name = _("process definition")
        verbose_name_plural = _("process definitions")
        ordering = ["hierarchy_id"]
        constraints: ClassVar = [
            models.UniqueConstraint(
                fields=["tenant", "hierarchy_id"],
                name="bpm_processdefinition_unique_tenant_hierarchy",
            ),
        ]
        indexes = [
            models.Index(fields=["tenant", "status"]),
            models.Index(fields=["tenant", "framework"]),
        ]

    def __str__(self) -> str:
        return f"[{self.hierarchy_id}] {self.name}"

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------
    def get_ancestors(self) -> list[ProcessDefinition]:
        """Return ordered list of ancestors from root to direct parent."""
        ancestors: list[ProcessDefinition] = []
        node = self.parent
        while node is not None:
            ancestors.insert(0, node)
            node = node.parent
        return ancestors

    def get_descendants(self) -> models.QuerySet[ProcessDefinition]:
        """Return all descendants via a prefix-match on hierarchy_id."""
        prefix = self.hierarchy_id + "."
        return ProcessDefinition.objects.filter(
            tenant=self.tenant,
            hierarchy_id__startswith=prefix,
        ).order_by("hierarchy_id")


# ---------------------------------------------------------------------------
# ProcessOwnership
# ---------------------------------------------------------------------------

class ProcessOwnership(TimeStampedModel):
    """Role-based ownership link between a user and a ProcessDefinition."""

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="ownerships",
        verbose_name=_("process"),
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="process_ownerships",
        verbose_name=_("user"),
    )
    role = models.CharField(
        _("role"),
        max_length=16,
        choices=OwnershipRole.choices,
        default=OwnershipRole.OWNER,
    )
    assigned_at = models.DateTimeField(_("assigned at"), auto_now_add=True)
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("process ownership")
        verbose_name_plural = _("process ownerships")
        constraints: ClassVar = [
            models.UniqueConstraint(
                fields=["process", "user", "role"],
                name="bpm_processownership_unique_process_user_role",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.user} — {self.get_role_display()} of {self.process}"


# ---------------------------------------------------------------------------
# ProcessDocument
# ---------------------------------------------------------------------------

class ProcessDocument(TimeStampedModel):
    """Soft link from a process definition to a DMS document.

    ``dms_document_id`` stores the ``Document.public_id`` UUID from the DMS
    app without a hard DB foreign key, preserving module independence.
    """

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="documents",
        verbose_name=_("process"),
    )
    dms_document_id = models.UUIDField(
        _("DMS document ID"),
        help_text=_("UUID matching dms.Document.public_id."),
    )
    doc_type = models.CharField(
        _("document type"),
        max_length=16,
        choices=DocLinkType.choices,
        default=DocLinkType.PROCEDURE,
    )
    title = models.CharField(
        _("title"),
        max_length=512,
        blank=True,
        help_text=_("Cached document title for quick display without a DMS lookup."),
    )
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("process document")
        verbose_name_plural = _("process documents")
        ordering = ["doc_type", "title"]
        constraints: ClassVar = [
            models.UniqueConstraint(
                fields=["process", "dms_document_id"],
                name="bpm_processdocument_unique_process_doc",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.get_doc_type_display()} — {self.title or self.dms_document_id}"
