"""Numbered receiving and transfer documents; drafts do not constitute stock."""
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MaxLengthValidator
from django.db import models

from .models import InventoryRecord, InventoryLocation, SerializedStockUnit, StockMovement


class InventoryDocumentSequence(models.Model):
    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    kind = models.CharField(max_length=8, choices=[("RECEIPT", "Receipt"), ("TRANSFER", "Transfer")])
    next_value = models.PositiveBigIntegerField(default=1)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["company", "kind"], name="inventory_sequence_company_kind_unique"),
            models.CheckConstraint(condition=models.Q(next_value__gte=1, next_value__lte=1000000000000000000), name="inventory_sequence_positive"),
            models.CheckConstraint(condition=models.Q(kind__in=["RECEIPT", "TRANSFER"]), name="inventory_sequence_kind_valid"),
        ]


class GoodsReceipt(InventoryRecord):
    class Status(models.TextChoices):
        DRAFT = "DRAFT", "Draft"
        POSTED = "POSTED", "Posted"
        CANCELLED = "CANCELLED", "Cancelled"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    number = models.CharField(max_length=22)
    destination = models.ForeignKey(InventoryLocation, on_delete=models.PROTECT)
    external_reference = models.CharField(max_length=128, blank=True)
    note = models.TextField(blank=True, validators=[MaxLengthValidator(2000)])
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="created_goods_receipts")
    status = models.CharField(max_length=12, choices=Status.choices, default=Status.DRAFT)
    received_at = models.DateTimeField(null=True, blank=True)
    received_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="posted_goods_receipts")
    cancelled_at = models.DateTimeField(null=True, blank=True)
    cancelled_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="cancelled_goods_receipts")
    cancellation_reason = models.CharField(max_length=500, blank=True)

    class Meta:
        ordering = ["company_id", "number"]
        constraints = [
            models.UniqueConstraint(fields=["company", "number"], name="inventory_receipt_number_unique"),
            models.CheckConstraint(condition=models.Q(number__regex=r"^GRN-[0-9]{8,18}$"), name="inventory_receipt_number_format"),
            models.CheckConstraint(condition=(
                models.Q(status="DRAFT", received_at__isnull=True, received_by__isnull=True, cancelled_at__isnull=True, cancelled_by__isnull=True, cancellation_reason="")
                | models.Q(status="POSTED", received_at__isnull=False, received_by__isnull=False, cancelled_at__isnull=True, cancelled_by__isnull=True, cancellation_reason="")
                | (models.Q(status="CANCELLED", received_at__isnull=True, received_by__isnull=True, cancelled_at__isnull=False, cancelled_by__isnull=False) & ~models.Q(cancellation_reason=""))
            ), name="inventory_receipt_status_evidence"),
        ]

    def __str__(self):
        return self.number


class GoodsReceiptLine(InventoryRecord):
    receipt = models.ForeignKey(GoodsReceipt, on_delete=models.PROTECT, related_name="lines")
    spare_part = models.ForeignKey("parts.SparePart", on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField()
    is_active = models.BooleanField(default=True)
    movement = models.OneToOneField(StockMovement, null=True, blank=True, on_delete=models.PROTECT, related_name="receipt_line")

    class Meta:
        ordering = ["spare_part_id", "pk"]
        constraints = [
            models.UniqueConstraint(fields=["receipt", "spare_part"], name="inventory_receipt_part_unique"),
            models.CheckConstraint(condition=models.Q(quantity__gte=1, quantity__lte=1000000000), name="inventory_receipt_line_quantity"),
        ]


class GoodsReceiptIdentifier(InventoryRecord):
    line = models.ForeignKey(GoodsReceiptLine, on_delete=models.PROTECT, related_name="identifiers")
    identifier = models.CharField(max_length=128)
    is_active = models.BooleanField(default=True)
    unit = models.ForeignKey(SerializedStockUnit, null=True, blank=True, on_delete=models.PROTECT)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["line", "identifier"], name="inventory_receipt_identifier_unique"),
            models.CheckConstraint(condition=~models.Q(identifier=""), name="inventory_receipt_identifier_nonempty"),
        ]


class StockTransfer(InventoryRecord):
    class Status(models.TextChoices):
        DRAFT = "DRAFT", "Draft"
        DISPATCHED = "DISPATCHED", "Dispatched"
        RECEIVED = "RECEIVED", "Received"
        CANCELLED = "CANCELLED", "Cancelled"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    number = models.CharField(max_length=22)
    source = models.ForeignKey(InventoryLocation, on_delete=models.PROTECT, related_name="transfer_sources")
    destination = models.ForeignKey(InventoryLocation, on_delete=models.PROTECT, related_name="transfer_destinations")
    transit_location = models.OneToOneField(InventoryLocation, null=True, blank=True, on_delete=models.PROTECT, related_name="managed_transfer")
    note = models.TextField(blank=True, validators=[MaxLengthValidator(2000)])
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="created_stock_transfers")
    status = models.CharField(max_length=12, choices=Status.choices, default=Status.DRAFT)
    dispatched_at = models.DateTimeField(null=True, blank=True)
    dispatched_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="dispatched_stock_transfers")
    received_at = models.DateTimeField(null=True, blank=True)
    received_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="received_stock_transfers")
    cancelled_at = models.DateTimeField(null=True, blank=True)
    cancelled_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.PROTECT, related_name="cancelled_stock_transfers")
    cancellation_reason = models.CharField(max_length=500, blank=True)

    class Meta:
        ordering = ["company_id", "number"]
        constraints = [
            models.UniqueConstraint(fields=["company", "number"], name="inventory_transfer_number_unique"),
            models.CheckConstraint(condition=models.Q(number__regex=r"^TRF-[0-9]{8,18}$"), name="inventory_transfer_number_format"),
            models.CheckConstraint(condition=~models.Q(source=models.F("destination")), name="inventory_transfer_distinct_locations"),
            models.CheckConstraint(condition=(
                models.Q(status="DRAFT", transit_location__isnull=True, dispatched_at__isnull=True, dispatched_by__isnull=True,
                    received_at__isnull=True, received_by__isnull=True, cancelled_at__isnull=True, cancelled_by__isnull=True, cancellation_reason="")
                | models.Q(status="DISPATCHED", transit_location__isnull=False, dispatched_at__isnull=False, dispatched_by__isnull=False,
                    received_at__isnull=True, received_by__isnull=True, cancelled_at__isnull=True, cancelled_by__isnull=True, cancellation_reason="")
                | models.Q(status="RECEIVED", transit_location__isnull=False, dispatched_at__isnull=False, dispatched_by__isnull=False,
                    received_at__isnull=False, received_by__isnull=False, cancelled_at__isnull=True, cancelled_by__isnull=True, cancellation_reason="")
                | (models.Q(status="CANCELLED", transit_location__isnull=True, dispatched_at__isnull=True, dispatched_by__isnull=True,
                    received_at__isnull=True, received_by__isnull=True, cancelled_at__isnull=False, cancelled_by__isnull=False) & ~models.Q(cancellation_reason=""))
            ), name="inventory_transfer_status_evidence"),
            models.CheckConstraint(condition=models.Q(received_at__isnull=True) | models.Q(received_at__gte=models.F("dispatched_at")), name="inventory_transfer_time_order"),
        ]

    def __str__(self):
        return self.number


class StockTransferLine(InventoryRecord):
    transfer = models.ForeignKey(StockTransfer, on_delete=models.PROTECT, related_name="lines")
    spare_part = models.ForeignKey("parts.SparePart", on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField()
    is_active = models.BooleanField(default=True)
    dispatch_movement = models.OneToOneField(StockMovement, null=True, blank=True, on_delete=models.PROTECT, related_name="transfer_dispatch_line")
    receive_movement = models.OneToOneField(StockMovement, null=True, blank=True, on_delete=models.PROTECT, related_name="transfer_receive_line")

    class Meta:
        ordering = ["spare_part_id", "pk"]
        constraints = [
            models.UniqueConstraint(fields=["transfer", "spare_part"], name="inventory_transfer_part_unique"),
            models.CheckConstraint(condition=models.Q(quantity__gte=1, quantity__lte=1000000000), name="inventory_transfer_line_quantity"),
        ]


class StockTransferUnit(InventoryRecord):
    line = models.ForeignKey(StockTransferLine, on_delete=models.PROTECT, related_name="units")
    unit = models.ForeignKey(SerializedStockUnit, on_delete=models.PROTECT)
    is_active = models.BooleanField(default=True)

    class Meta:
        constraints = [models.UniqueConstraint(fields=["line", "unit"], name="inventory_transfer_unit_unique")]
