"""Approval Engine domain models.

Model inventory
---------------
ApprovalType        — enum: single_step / multi_step / sequential / parallel / conditional
ApprovalStatus      — enum: pending / in_progress / approved / rejected / returned / cancelled / expired
ApprovalPriority    — enum: low / medium / high / urgent
ApprovalDecision    — enum: approve / reject / return / request_changes / delegate / abstain
StepStatus          — enum: pending / in_progress / approved / rejected / returned / skipped / expired
ApprovalRequest     — core entity: tenant-scoped approval request
ApprovalStep        — individual step within a multi-step approval request
ApprovalMatrix      — reusable approval matrix for different entity types
MatrixApproverConfig— approver configuration within a matrix entry
"""

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,
    TimeStampedModel,
    UUIDModel,
)


class ApprovalType(models.TextChoices):
    SINGLE      = "single",      _("Single Step")
    MULTI       = "multi",       _("Multi Step")
    SEQUENTIAL  = "sequential",  _("Sequential")
    PARALLEL    = "parallel",    _("Parallel")
    CONDITIONAL = "conditional", _("Conditional")


class ApprovalStatus(models.TextChoices):
    PENDING     = "pending",     _("Pending")
    IN_PROGRESS = "in_progress", _("In Progress")
    APPROVED    = "approved",    _("Approved")
    REJECTED    = "rejected",    _("Rejected")
    RETURNED    = "returned",    _("Returned")
    CANCELLED   = "cancelled",   _("Cancelled")
    EXPIRED     = "expired",     _("Expired")


class ApprovalPriority(models.TextChoices):
    LOW    = "low",    _("Low")
    MEDIUM = "medium", _("Medium")
    HIGH   = "high",   _("High")
    URGENT = "urgent", _("Urgent")


class ApprovalDecision(models.TextChoices):
    APPROVE         = "approve",         _("Approve")
    REJECT          = "reject",          _("Reject")
    RETURN          = "return",          _("Return")
    REQUEST_CHANGES = "request_changes", _("Request Changes")
    DELEGATE        = "delegate",        _("Delegate")
    ABSTAIN         = "abstain",         _("Abstain")


class StepStatus(models.TextChoices):
    PENDING     = "pending",     _("Pending")
    IN_PROGRESS = "in_progress", _("In Progress")
    APPROVED    = "approved",    _("Approved")
    REJECTED    = "rejected",    _("Rejected")
    RETURNED    = "returned",    _("Returned")
    SKIPPED     = "skipped",     _("Skipped")
    EXPIRED     = "expired",     _("Expired")


class ApprovalRequest(UUIDModel, TenantScopedModel):
    """An approval request that can be linked to any entity."""

    title       = models.CharField(_("title"), max_length=500)
    description = models.TextField(_("description"), blank=True, default="")

    approval_type = models.CharField(
        _("approval type"),
        max_length=16,
        choices=ApprovalType.choices,
        default=ApprovalType.SINGLE,
        db_index=True,
    )
    status = models.CharField(
        _("status"),
        max_length=16,
        choices=ApprovalStatus.choices,
        default=ApprovalStatus.PENDING,
        db_index=True,
    )
    priority = models.CharField(
        _("priority"),
        max_length=10,
        choices=ApprovalPriority.choices,
        default=ApprovalPriority.MEDIUM,
        db_index=True,
    )

    requested_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        related_name="approval_requests_made",
        verbose_name=_("requested by"),
    )

    due_date      = models.DateTimeField(_("due date"), null=True, blank=True, db_index=True)
    completed_at  = models.DateTimeField(_("completed at"), null=True, blank=True)
    is_confidential = models.BooleanField(_("confidential"), default=False)

    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name=_("entity type"),
    )
    object_id = models.PositiveIntegerField(_("entity id"), null=True, blank=True)
    content_object = GenericForeignKey("content_type", "object_id")

    matrix = models.ForeignKey(
        "approval_engine.ApprovalMatrix",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="requests",
        verbose_name=_("approval matrix"),
    )

    class Meta(TenantScopedModel.Meta):
        verbose_name        = _("approval request")
        verbose_name_plural = _("approval requests")
        ordering            = ("-created_at",)
        indexes: ClassVar = [
            *TenantScopedModel.Meta.indexes,
            models.Index(fields=("tenant", "requested_by", "status"), name="appr_req_tenant_reqby_status"),
            models.Index(fields=("tenant", "approval_type", "status"), name="appr_req_tenant_type_status"),
            models.Index(fields=("tenant", "due_date"), name="appr_req_tenant_due_date"),
            models.Index(fields=("content_type", "object_id"), name="appr_req_content_object"),
        ]

    def __str__(self) -> str:
        return self.title


class ApprovalStep(UUIDModel, TimeStampedModel):
    """An individual step within a multi-step approval request."""

    request = models.ForeignKey(
        ApprovalRequest,
        on_delete=models.CASCADE,
        related_name="steps",
        verbose_name=_("approval request"),
    )
    step_order = models.PositiveSmallIntegerField(_("step order"))

    approver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="approval_steps_assigned",
        verbose_name=_("approver"),
    )
    approver_role = models.ForeignKey(
        "iam.Role",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="approval_steps",
        verbose_name=_("approver role"),
    )
    approver_org_unit = models.ForeignKey(
        "organizations.OrganizationNode",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="approval_steps",
        verbose_name=_("approver org unit"),
    )

    status = models.CharField(
        _("status"),
        max_length=16,
        choices=StepStatus.choices,
        default=StepStatus.PENDING,
        db_index=True,
    )
    decision = models.CharField(
        _("decision"),
        max_length=16,
        choices=ApprovalDecision.choices,
        null=True,
        blank=True,
    )
    decided_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="approval_decisions_made",
        verbose_name=_("decided by"),
    )
    decided_at = models.DateTimeField(_("decided at"), null=True, blank=True)
    note = models.TextField(_("note"), blank=True, default="")
    due_date  = models.DateTimeField(_("due date"), null=True, blank=True, db_index=True)
    is_parallel = models.BooleanField(_("parallel"), default=False)

    class Meta:
        verbose_name        = _("approval step")
        verbose_name_plural = _("approval steps")
        ordering            = ("request", "step_order")
        constraints = (
            models.UniqueConstraint(
                fields=("request", "step_order"),
                name="appr_step_unique_request_order",
            ),
        )
        indexes: ClassVar[list[models.Index]] = [
            models.Index(fields=("request", "status"), name="appr_step_request_status"),
            models.Index(fields=("approver", "status"), name="appr_step_approver_status"),
            models.Index(fields=("due_date",), name="appr_step_due_date"),
        ]

    def __str__(self) -> str:
        return f"{self.request_id}:{self.step_order}:{self.status}"


class ApprovalMatrix(UUIDModel, TimeStampedModel):
    """A reusable approval matrix defining approver configurations for entity types."""

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="approval_matrices",
        verbose_name=_("tenant"),
    )
    name = models.CharField(_("name"), max_length=200)
    description = models.TextField(_("description"), blank=True, default="")

    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        related_name="approval_matrices",
        verbose_name=_("entity type"),
    )
    conditions = models.JSONField(
        _("conditions"),
        default=dict,
        blank=True,
        help_text=_("JSON conditions to match when this matrix should apply."),
    )
    is_active = models.BooleanField(_("active"), default=True, db_index=True)

    class Meta:
        verbose_name        = _("approval matrix")
        verbose_name_plural = _("approval matrices")
        ordering            = ("tenant", "name")
        constraints = (
            models.UniqueConstraint(
                fields=("tenant", "name"),
                name="appr_matrix_unique_tenant_name",
            ),
        )

    def __str__(self) -> str:
        return self.name


class MatrixApproverConfig(TimeStampedModel):
    """Approver configuration entry within an approval matrix."""

    matrix = models.ForeignKey(
        ApprovalMatrix,
        on_delete=models.CASCADE,
        related_name="approver_configs",
        verbose_name=_("approval matrix"),
    )
    step_order = models.PositiveSmallIntegerField(_("step order"), default=1)
    approver = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="matrix_approver_configs",
        verbose_name=_("approver"),
    )
    approver_role = models.ForeignKey(
        "iam.Role",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="matrix_approver_configs",
        verbose_name=_("approver role"),
    )
    approver_org_unit = models.ForeignKey(
        "organizations.OrganizationNode",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="matrix_approver_configs",
        verbose_name=_("approver org unit"),
    )
    is_parallel = models.BooleanField(_("parallel"), default=False)
    is_required = models.BooleanField(_("required"), default=True)
    quorum = models.PositiveSmallIntegerField(_("quorum"), default=1, help_text=_("Required number of approvers."))
    majority_voting = models.BooleanField(_("majority voting"), default=False)

    class Meta:
        verbose_name        = _("matrix approver config")
        verbose_name_plural = _("matrix approver configs")
        ordering            = ("matrix", "step_order")
        constraints = (
            models.UniqueConstraint(
                fields=("matrix", "step_order"),
                name="appr_matrix_config_unique_step",
            ),
        )

    def __str__(self) -> str:
        return f"{self.matrix}:{self.step_order}"
