"""Rules & Control Points models — Phase 4 + Phase 6 additions.

Business rules and review checkpoints for organisational processes.

Model inventory
---------------
ProcessRule           — a business rule governing when/how a process runs (R1…R7).
ProcessControlPoint   — a mandatory review gate at a specific process stage (CP1…CP4).
ControlPointExecution — records a gate review during a ProcessInstance execution.

Design decisions
----------------
* ProcessRule.source is a free-text field indicating the origin of the rule
  (e.g. "تریگر فرآیند", "ISO 31000", "Board directive").
* ProcessControlPoint.input_items is an explicit M2M to ProcessDataItem so
  that each gate can reference the exact inputs it inspects.
* ProcessControlPoint.responsible_role is a nullable FK to ProcessRole
  (Phase 3).  Null is allowed so control points can exist before roles are
  fully defined.
* ControlPointExecution was deferred from Phase 4 — it is added here in
  Phase 6 once ProcessInstance is available (imported lazily via string FK
  to avoid circular imports).
"""

from __future__ import annotations

from django.conf import settings
from django.db import models
from django.utils import timezone
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 ProcessDataItem
from simorgh.apps.bpm.models.raci import ProcessRole


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class ControlPointStage(models.TextChoices):
    BEFORE = "before", _("Before Start")
    DURING = "during", _("During Execution")
    AFTER  = "after",  _("After Completion")


# ---------------------------------------------------------------------------
# ProcessRule
# ---------------------------------------------------------------------------

class ProcessRule(TimeStampedModel):
    """A business rule that governs a process (code: R1, R2, …).

    Rules are immutable references unless ``is_configurable=True``, in which
    case the tenant may override the description or threshold.
    """

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="rules",
        verbose_name=_("process"),
    )
    code = models.CharField(_("code"), max_length=20)
    description = models.TextField(_("description"))
    source = models.CharField(
        _("source"), max_length=200, blank=True,
        help_text=_("Origin of the rule, e.g. 'trigger', 'ISO standard', 'policy'."),
    )
    is_configurable = models.BooleanField(
        _("is configurable"), default=True,
        help_text=_("Whether the tenant can adjust this rule's parameters."),
    )
    order = models.PositiveSmallIntegerField(_("display order"), default=0)

    class Meta:
        verbose_name = _("process rule")
        verbose_name_plural = _("process rules")
        ordering = ("process", "order", "code")
        constraints = [
            models.UniqueConstraint(
                fields=("process", "code"),
                name="bpm_processrule_process_code_uniq",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.process.hierarchy_id} — {self.code}"


# ---------------------------------------------------------------------------
# ProcessControlPoint
# ---------------------------------------------------------------------------

class ProcessControlPoint(TimeStampedModel):
    """A mandatory review gate at a specific stage of a process (CP1, CP2, …).

    ``input_items`` lists the ProcessDataItems that must be present/validated
    before the gate can be passed.
    ``responsible_role`` is the ProcessRole accountable for the gate decision.
    """

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="control_points",
        verbose_name=_("process"),
    )
    code = models.CharField(_("code"), max_length=20)
    stage = models.CharField(
        _("stage"), max_length=20,
        choices=ControlPointStage.choices,
        default=ControlPointStage.DURING,
    )
    description = models.TextField(_("description"))
    input_items = models.ManyToManyField(
        ProcessDataItem,
        blank=True,
        related_name="control_points",
        verbose_name=_("required input items"),
    )
    output_description = models.TextField(
        _("output description"), blank=True,
        help_text=_("What is produced/confirmed when this gate is passed."),
    )
    responsible_role = models.ForeignKey(
        ProcessRole,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="responsible_control_points",
        verbose_name=_("responsible role"),
    )
    order = models.PositiveSmallIntegerField(_("display order"), default=0)

    class Meta:
        verbose_name = _("process control point")
        verbose_name_plural = _("process control points")
        ordering = ("process", "order", "code")
        constraints = [
            models.UniqueConstraint(
                fields=("process", "code"),
                name="bpm_processcontrolpoint_process_code_uniq",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.process.hierarchy_id} — {self.code} [{self.get_stage_display()}]"


# ---------------------------------------------------------------------------
# ControlPointExecution  (Phase 6 — requires ProcessInstance)
# ---------------------------------------------------------------------------

class ControlPointExecutionOutcome(models.TextChoices):
    PASSED   = "passed",   _("Passed / تأیید شد")
    FAILED   = "failed",   _("Failed / رد شد")
    DEFERRED = "deferred", _("Deferred / به بعد موکول شد")


class ControlPointExecution(TimeStampedModel):
    """Records the outcome of a control-point gate review during a process instance.

    ``instance`` uses a string FK ("bpm.ProcessInstance") to avoid a circular
    import between rules.py and instance.py.
    """

    instance = models.ForeignKey(
        "bpm.ProcessInstance",
        on_delete=models.CASCADE,
        related_name="cp_executions",
        verbose_name=_("process instance"),
    )
    control_point = models.ForeignKey(
        ProcessControlPoint,
        on_delete=models.CASCADE,
        related_name="executions",
        verbose_name=_("control point"),
    )
    outcome = models.CharField(
        _("outcome"), max_length=20,
        choices=ControlPointExecutionOutcome.choices,
        default=ControlPointExecutionOutcome.PASSED,
    )
    executed_at = models.DateTimeField(_("executed at"), default=timezone.now)
    executed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="bpm_cp_executions",
        verbose_name=_("executed by"),
    )
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("control point execution")
        verbose_name_plural = _("control point executions")
        ordering = ("-executed_at",)
        constraints = [
            models.UniqueConstraint(
                fields=("instance", "control_point"),
                name="bpm_cpexecution_instance_cp_uniq",
            ),
        ]

    def __str__(self) -> str:
        return (
            f"{self.instance} › {self.control_point.code} "
            f"[{self.get_outcome_display()}]"
        )
