"""Process Instance models — Phase 6 (Execution Tracking).

Tracks real-world execution of ProcessDefinition instances.

Model inventory
---------------
ProcessInstance          — a single run of a process (has status lifecycle).
ProcessInstanceStep      — per-step execution state within an instance.
ProcessInstanceRuleCheck — compliance check against a ProcessRule.

Design decisions
----------------
* ``workspace`` FK is nullable so instances can exist without a workspace
  (e.g. triggered from external API or scheduled job).
* ``output_documents`` on ProcessInstanceStep is a plain TextField storing
  a JSON array of DMS document UUIDs — avoids a cross-app M2M FK while
  Phase 8 (BPMN) is not yet implemented.  This will be upgraded in Phase 8.
* ``instance_number`` is auto-generated in the model's ``save()`` as
  "<hierarchy_id>-<year>-<seq>" (e.g. "1.1.1-2026-001").  The sequence is
  the count of existing instances for that process in that year + 1.
* ``ControlPointExecution`` lives in ``rules.py`` and is imported here to
  be referenced in admin/permissions.  It requires ProcessInstance so it
  was deferred from Phase 4 as planned.
"""

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, UUIDModel

from simorgh.apps.bpm.models.process import ProcessDefinition
from simorgh.apps.bpm.models.data import ProcessOperationalStep
from simorgh.apps.bpm.models.rules import ProcessRule, ProcessControlPoint


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class InstanceStatus(models.TextChoices):
    PLANNED    = "planned",     _("Planned / برنامه‌ریزی‌شده")
    IN_PROGRESS = "in_progress", _("In Progress / در حال اجرا")
    COMPLETED  = "completed",   _("Completed / تکمیل‌شده")
    CANCELLED  = "cancelled",   _("Cancelled / لغو‌شده")


class StepStatus(models.TextChoices):
    PENDING    = "pending",     _("Pending / در انتظار")
    IN_PROGRESS = "in_progress", _("In Progress / در حال اجرا")
    DONE       = "done",        _("Done / انجام‌شده")
    SKIPPED    = "skipped",     _("Skipped / رد شده")


# ---------------------------------------------------------------------------
# ProcessInstance
# ---------------------------------------------------------------------------

class ProcessInstance(UUIDModel, TimeStampedModel):
    """A single execution run of a ProcessDefinition.

    ``instance_number`` is auto-generated on first save using the pattern
    ``<hierarchy_id (dots removed)>-<year>-<zero-padded seq>``.
    Example: process 1.1.1, year 2026, 3rd run → "111-2026-003".
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="process_instances",
        verbose_name=_("tenant"),
    )
    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="instances",
        verbose_name=_("process"),
    )
    instance_number = models.CharField(
        _("instance number"), max_length=50, blank=True,
        help_text=_("Auto-generated: <process_code>-<year>-<seq>."),
    )
    title = models.CharField(_("title"), max_length=255)
    triggered_by = models.CharField(
        _("triggered by"), max_length=255, blank=True,
        help_text=_("Human-readable trigger description, e.g. 'شروع دوره Q2 1405'."),
    )
    status = models.CharField(
        _("status"), max_length=20,
        choices=InstanceStatus.choices,
        default=InstanceStatus.PLANNED,
    )
    start_date = models.DateTimeField(_("start date"), null=True, blank=True)
    end_date = models.DateTimeField(
        _("planned end date"), null=True, blank=True,
    )
    actual_end_date = models.DateTimeField(
        _("actual end date"), null=True, blank=True,
    )
    workspace = models.ForeignKey(
        "platform_workspaces.Workspace",
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="process_instances",
        verbose_name=_("workspace"),
    )
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="created_process_instances",
        verbose_name=_("created by"),
    )
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("process instance")
        verbose_name_plural = _("process instances")
        ordering = ("-created_at",)

    def __str__(self) -> str:
        return f"{self.instance_number or self.public_id} — {self.title}"

    def save(self, *args, **kwargs) -> None:  # noqa: ANN002
        if not self.instance_number and self.process_id:
            self.instance_number = self._generate_instance_number()
        super().save(*args, **kwargs)

    def _generate_instance_number(self) -> str:
        year = timezone.now().year
        code = self.process.hierarchy_id.replace(".", "")
        existing = ProcessInstance.objects.filter(
            process=self.process,
            instance_number__startswith=f"{code}-{year}-",
        ).count()
        seq = existing + 1
        return f"{code}-{year}-{seq:03d}"

    def advance_status(self) -> None:
        """Move instance to the next logical status."""
        transitions = {
            InstanceStatus.PLANNED:    InstanceStatus.IN_PROGRESS,
            InstanceStatus.IN_PROGRESS: InstanceStatus.COMPLETED,
        }
        if self.status in transitions:
            self.status = transitions[self.status]
            if self.status == InstanceStatus.IN_PROGRESS and not self.start_date:
                self.start_date = timezone.now()
            elif self.status == InstanceStatus.COMPLETED and not self.actual_end_date:
                self.actual_end_date = timezone.now()
            self.save(update_fields=["status", "start_date", "actual_end_date"])


# ---------------------------------------------------------------------------
# ProcessInstanceStep
# ---------------------------------------------------------------------------

class ProcessInstanceStep(TimeStampedModel):
    """Tracks execution status of a single operational step within an instance."""

    instance = models.ForeignKey(
        ProcessInstance,
        on_delete=models.CASCADE,
        related_name="instance_steps",
        verbose_name=_("process instance"),
    )
    step = models.ForeignKey(
        ProcessOperationalStep,
        on_delete=models.CASCADE,
        related_name="instance_steps",
        verbose_name=_("operational step"),
    )
    status = models.CharField(
        _("status"), max_length=20,
        choices=StepStatus.choices,
        default=StepStatus.PENDING,
    )
    started_at = models.DateTimeField(_("started at"), null=True, blank=True)
    completed_at = models.DateTimeField(_("completed at"), null=True, blank=True)
    assigned_to = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="assigned_instance_steps",
        verbose_name=_("assigned to"),
    )
    notes = models.TextField(_("notes"), blank=True)
    # JSON array of DMS document UUIDs; upgraded to M2M FK in Phase 8
    output_document_ids = models.JSONField(
        _("output document IDs"), default=list, blank=True,
        help_text=_("List of DMS document UUIDs produced by this step."),
    )

    class Meta:
        verbose_name = _("instance step")
        verbose_name_plural = _("instance steps")
        ordering = ("step__order", "step__step_number")
        constraints = [
            models.UniqueConstraint(
                fields=("instance", "step"),
                name="bpm_instancestep_instance_step_uniq",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.instance} › Step {self.step.step_number} [{self.status}]"

    def start(self) -> None:
        """Mark this step as in-progress."""
        self.status = StepStatus.IN_PROGRESS
        self.started_at = self.started_at or timezone.now()
        self.save(update_fields=["status", "started_at"])

    def complete(self) -> None:
        """Mark this step as done."""
        self.status = StepStatus.DONE
        self.completed_at = self.completed_at or timezone.now()
        self.save(update_fields=["status", "completed_at"])


# ---------------------------------------------------------------------------
# ProcessInstanceRuleCheck
# ---------------------------------------------------------------------------

class ProcessInstanceRuleCheck(TimeStampedModel):
    """Records compliance check of a ProcessRule during an instance execution."""

    instance = models.ForeignKey(
        ProcessInstance,
        on_delete=models.CASCADE,
        related_name="rule_checks",
        verbose_name=_("process instance"),
    )
    rule = models.ForeignKey(
        ProcessRule,
        on_delete=models.CASCADE,
        related_name="instance_checks",
        verbose_name=_("business rule"),
    )
    is_compliant = models.BooleanField(_("compliant"), default=True)
    checked_at = models.DateTimeField(_("checked at"), default=timezone.now)
    checked_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True, blank=True,
        related_name="bpm_rule_checks",
        verbose_name=_("checked by"),
    )
    notes = models.TextField(_("notes"), blank=True)

    class Meta:
        verbose_name = _("rule check")
        verbose_name_plural = _("rule checks")
        ordering = ("-checked_at",)
        constraints = [
            models.UniqueConstraint(
                fields=("instance", "rule"),
                name="bpm_instancerulecheck_instance_rule_uniq",
            ),
        ]

    def __str__(self) -> str:
        status = "✓" if self.is_compliant else "✗"
        return f"{self.instance} › {self.rule.code} {status}"
