"""Runtime models for the workflow engine.

* ``WorkflowInstance`` — one row per subject (lead, ticket, …) currently
  driven by a workflow. State name + free-form ``data`` payload.
* ``WorkflowTransitionLog`` — append-only audit trail of every fire.
* ``WorkflowApproval`` — approval records for transitions/states that
  require one.
* ``ProcessDefinition`` — tenant-scoped automation rule registered in the
  workflow engine. Fires when a named event occurs and its conditions match.
* ``ProcessExecutionLog`` — append-only record of every process execution.

Definitions live in code (`registry.py`), not the DB; only the runtime
state is persisted here.
"""

from __future__ import annotations

from typing import ClassVar

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.core.models import TenantScopedModel, UUIDModel


class WorkflowInstanceStatus(models.TextChoices):
    ACTIVE = "active", _("Active")
    COMPLETED = "completed", _("Completed")
    CANCELLED = "cancelled", _("Cancelled")


class WorkflowInstance(UUIDModel, TenantScopedModel):
    """A live FSM bound to one subject row."""

    definition_name = models.CharField(_("definition"), max_length=128, db_index=True)
    definition_version = models.PositiveIntegerField(_("definition version"), default=1)
    subject_type = models.CharField(_("subject type"), max_length=128, db_index=True)

    # Generic pointer to the business row this instance drives.
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        related_name="+",
    )
    object_id = models.CharField(_("object id"), max_length=64, blank=True, default="")
    subject = GenericForeignKey("content_type", "object_id")

    current_state = models.CharField(_("current state"), max_length=64, db_index=True)
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=WorkflowInstanceStatus.choices,
        default=WorkflowInstanceStatus.ACTIVE,
        db_index=True,
    )
    data = models.JSONField(_("data"), default=dict, blank=True)

    started_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
    )
    closed_at = models.DateTimeField(_("closed at"), null=True, blank=True)

    class Meta:
        verbose_name = _("workflow instance")
        verbose_name_plural = _("workflow instances")
        ordering = ("-created_at",)
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "definition_name", "status")),
            models.Index(fields=("content_type", "object_id")),
        ]

    def __str__(self) -> str:
        return f"{self.definition_name} v{self.definition_version} @ {self.current_state}"


class WorkflowTransitionLog(UUIDModel, TenantScopedModel):
    """Append-only record of every transition fired on an instance."""

    instance = models.ForeignKey(
        WorkflowInstance,
        on_delete=models.CASCADE,
        related_name="transition_logs",
    )
    transition_name = models.CharField(_("transition"), max_length=64)
    from_state = models.CharField(_("from state"), max_length=64)
    to_state = models.CharField(_("to state"), max_length=64)
    actor = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
    )
    trigger = models.CharField(_("trigger"), max_length=128, blank=True, default="")
    payload = models.JSONField(_("payload"), default=dict, blank=True)

    class Meta:
        verbose_name = _("workflow transition log")
        verbose_name_plural = _("workflow transition logs")
        ordering = ("-created_at",)
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("instance", "-created_at")),
        ]

    def __str__(self) -> str:
        return f"{self.instance_id}: {self.from_state} → {self.to_state}"


class WorkflowApprovalDecision(models.TextChoices):
    APPROVED = "approved", _("Approved")
    REJECTED = "rejected", _("Rejected")


class WorkflowApproval(UUIDModel, TenantScopedModel):
    """Approval recorded against an instance + transition combination."""

    instance = models.ForeignKey(
        WorkflowInstance,
        on_delete=models.CASCADE,
        related_name="approvals",
    )
    transition_name = models.CharField(_("transition"), max_length=64)
    decision = models.CharField(
        _("decision"),
        max_length=16,
        choices=WorkflowApprovalDecision.choices,
    )
    actor = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name="+",
    )
    role_code = models.CharField(_("role code"), max_length=64, blank=True, default="")
    note = models.TextField(_("note"), blank=True, default="")
    consumed_at = models.DateTimeField(_("consumed at"), null=True, blank=True)

    class Meta:
        verbose_name = _("workflow approval")
        verbose_name_plural = _("workflow approvals")
        ordering = ("-created_at",)
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("instance", "transition_name", "consumed_at")),
        ]

    def __str__(self) -> str:
        return f"{self.instance_id}:{self.transition_name}:{self.decision}"


# ---------------------------------------------------------------------------
# Process engine — tenant-scoped event-triggered automation rules
# ---------------------------------------------------------------------------


class ProcessDefinition(UUIDModel, TenantScopedModel):
    """A tenant-scoped automation rule handled by the workflow engine.

    When the named ``trigger_event`` fires on the event bus, the
    ``ProcessEngine`` loads all active matching ``ProcessDefinition`` rows for
    the tenant, evaluates ``conditions`` (workflow evaluator DSL), and fires
    each registered action handler in order.

    ``seed_key`` tracks which ``.seed/`` file originally created this row so
    the seeder stays idempotent. Empty for tenant-created custom rules.
    """

    name = models.CharField(_("name"), max_length=128)
    description = models.TextField(_("description"), blank=True)
    trigger_event = models.CharField(
        _("trigger event"),
        max_length=128,
        db_index=True,
        help_text=_("Event bus event name (e.g. 'helpdesk.ticket.created')."),
    )
    conditions = models.JSONField(
        _("conditions"),
        default=list,
        blank=True,
        help_text=_(
            "List of condition expression objects (workflow evaluator DSL). "
            "All conditions must pass (implicit AND). "
            "Example: [{\"field\": \"subject.status\", \"op\": \"eq\", \"value\": \"new\"}]"
        ),
    )
    actions = models.JSONField(
        _("actions"),
        default=list,
        blank=True,
        help_text=_(
            'Ordered list of {"name": "<handler>", "params": {...}} action specs. '
            "Each name must match a registered workflow action handler."
        ),
    )
    seed_key = models.CharField(
        _("seed key"),
        max_length=128,
        blank=True,
        default="",
        db_index=True,
        help_text=_(
            "Identifies the .seed file this row was loaded from. "
            "Empty = custom rule created by the tenant."
        ),
    )
    is_active = models.BooleanField(_("is active"), default=True, db_index=True)
    sort_order = models.PositiveIntegerField(
        _("sort order"),
        default=0,
        help_text=_("Lower values run first within the same trigger."),
    )
    run_count = models.PositiveBigIntegerField(_("run count"), default=0)
    last_run_at = models.DateTimeField(_("last run at"), null=True, blank=True)

    class Meta:
        verbose_name = _("process definition")
        verbose_name_plural = _("process definitions")
        ordering = ("sort_order", "name")
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "trigger_event", "is_active")),
            models.Index(fields=("tenant", "seed_key")),
        ]

    def __str__(self) -> str:
        return f"{self.name} [{self.trigger_event}]"


class ProcessExecutionLog(UUIDModel, TenantScopedModel):
    """Append-only record of every process rule execution."""

    process = models.ForeignKey(
        ProcessDefinition,
        on_delete=models.CASCADE,
        related_name="execution_logs",
        verbose_name=_("process"),
    )
    trigger_event = models.CharField(_("trigger event"), max_length=128)
    subject_ct = models.ForeignKey(
        ContentType,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("subject content type"),
    )
    object_id = models.CharField(_("object id"), max_length=64, blank=True, default="")
    subject = GenericForeignKey("subject_ct", "object_id")
    actions_fired = models.JSONField(_("actions fired"), default=list)
    success = models.BooleanField(_("success"), default=True)
    error_message = models.TextField(_("error message"), blank=True, default="")

    class Meta:
        verbose_name = _("process execution log")
        verbose_name_plural = _("process execution logs")
        ordering = ("-created_at",)
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("tenant", "process", "-created_at")),
            models.Index(fields=("subject_ct", "object_id")),
        ]

    def __str__(self) -> str:
        return f"{self.process.name} → obj={self.object_id} @ {self.created_at}"
