"""RACI Matrix models — Phase 3.

Responsibility Assignment Matrix (RACI) for organisational processes.

Model inventory
---------------
ProcessRole  — a role participating in a process (SM, CEO, CFO …).
RACIMatrix   — a versioned RACI snapshot for one process.
RACIEntry    — a single cell in the matrix (activity × role → R/A/C/I).

Design decisions
----------------
* ProcessRole is scoped to a ProcessDefinition so roles can differ
  between processes (a "Strategy Manager" in process 1.1.1 vs "Risk Owner"
  in process 2.3.1 are distinct roles).
* iam_role is a nullable soft FK (stored as CharField) — avoids circular
  import and tight coupling with the IAM app.
* RACIEntry.step is a nullable FK to ProcessOperationalStep.  When set it
  links the cell to a structured step; otherwise activity_label holds the
  free-text activity title.
* RACIMatrix allows multiple versioned snapshots per process.  is_current
  flags the active one.  A DB constraint ensures at most one is_current
  per process (enforced at application layer via the save() override).
"""

from __future__ import annotations

from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import TimeStampedModel

from simorgh.apps.bpm.models.process import ProcessDefinition
from simorgh.apps.bpm.models.data import ProcessOperationalStep


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class RACIResponsibility(models.TextChoices):
    RESPONSIBLE = "R", _("Responsible")
    ACCOUNTABLE = "A", _("Accountable")
    CONSULTED   = "C", _("Consulted")
    INFORMED    = "I", _("Informed")


# ---------------------------------------------------------------------------
# ProcessRole
# ---------------------------------------------------------------------------

class ProcessRole(TimeStampedModel):
    """A named role that participates in a specific process.

    ``code`` is a short identifier used in RACI cells (e.g. "SM", "CEO").
    ``iam_role_code`` stores the matching IAM role codename (optional).
    """

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="roles",
        verbose_name=_("process"),
    )
    code = models.CharField(_("code"), max_length=20)
    name = models.CharField(_("name"), max_length=120)
    name_fa = models.CharField(_("name (FA)"), max_length=120, blank=True)
    # Soft link to IAM role — stored as codename string, not a DB FK
    iam_role_code = models.CharField(
        _("IAM role code"), max_length=100, blank=True,
        help_text=_("Codename of the matching IAM Role (no DB constraint)."),
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="bpm_process_roles",
        verbose_name=_("assigned user"),
        help_text=_("Optionally pin a specific user to this role."),
    )
    order = models.PositiveSmallIntegerField(_("display order"), default=0)

    class Meta:
        verbose_name = _("process role")
        verbose_name_plural = _("process roles")
        ordering = ("process", "order", "code")
        constraints = [
            models.UniqueConstraint(
                fields=("process", "code"),
                name="bpm_processrole_process_code_uniq",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.process.hierarchy_id} — {self.code} ({self.name})"


# ---------------------------------------------------------------------------
# RACIMatrix
# ---------------------------------------------------------------------------

class RACIMatrix(TimeStampedModel):
    """A versioned RACI snapshot for a single ProcessDefinition.

    Only one RACIMatrix per process should be marked ``is_current=True``.
    """

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="raci_matrices",
        verbose_name=_("process"),
    )
    version = models.CharField(_("version"), max_length=20, default="1.0")
    is_current = models.BooleanField(_("is current"), default=False)
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("RACI matrix")
        verbose_name_plural = _("RACI matrices")
        ordering = ("-created_at",)

    def __str__(self) -> str:
        current = " [current]" if self.is_current else ""
        return f"{self.process.hierarchy_id} v{self.version}{current}"

    def save(self, *args, **kwargs) -> None:
        """Ensure only one is_current matrix per process."""
        if self.is_current:
            RACIMatrix.objects.filter(
                process=self.process, is_current=True,
            ).exclude(pk=self.pk).update(is_current=False)
        super().save(*args, **kwargs)


# ---------------------------------------------------------------------------
# RACIEntry
# ---------------------------------------------------------------------------

class RACIEntry(TimeStampedModel):
    """A single RACI cell: activity × role → responsibility (R/A/C/I).

    ``step`` is a nullable FK to a ProcessOperationalStep.  If set the
    ``activity_label`` is auto-populated from the step title on save.
    If ``step`` is null, ``activity_label`` must be provided manually.
    """

    matrix = models.ForeignKey(
        RACIMatrix,
        on_delete=models.CASCADE,
        related_name="entries",
        verbose_name=_("RACI matrix"),
    )
    step = models.ForeignKey(
        ProcessOperationalStep,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="raci_entries",
        verbose_name=_("operational step"),
    )
    activity_label = models.CharField(
        _("activity label"), max_length=255,
        help_text=_("Free-text activity title (auto-filled from step when step is set)."),
    )
    role = models.ForeignKey(
        ProcessRole,
        on_delete=models.CASCADE,
        related_name="raci_entries",
        verbose_name=_("process role"),
    )
    responsibility = models.CharField(
        _("responsibility"),
        max_length=1,
        choices=RACIResponsibility.choices,
    )
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("RACI entry")
        verbose_name_plural = _("RACI entries")
        ordering = ("matrix", "step__order", "activity_label", "role__order")
        constraints = [
            models.UniqueConstraint(
                fields=("matrix", "activity_label", "role"),
                name="bpm_racientry_matrix_activity_role_uniq",
            ),
        ]

    def __str__(self) -> str:
        return (
            f"{self.matrix.process.hierarchy_id} | "
            f"{self.activity_label} | {self.role.code} → {self.responsibility}"
        )

    def save(self, *args, **kwargs) -> None:
        """Auto-populate activity_label from linked step."""
        if self.step and not self.activity_label:
            self.activity_label = self.step.title
        super().save(*args, **kwargs)
