"""Company-owned inventory evidence, separate from global spare-part masters."""
import uuid

from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MaxLengthValidator, RegexValidator
from django.db import models
from django.utils import timezone

from apps.organization.model_base import TimeStampedModel

CODE_PATTERN = r"^[A-Z0-9][A-Z0-9_-]*$"


class InventoryQuerySet(models.QuerySet):
    def delete(self):
        raise ValidationError("Inventory records are retained. Use inventory services.")


class InventoryRecord(TimeStampedModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    objects = InventoryQuerySet.as_manager()

    class Meta:
        abstract = True
        ordering = ["created_at", "pk"]

    def save(self, *args, **kwargs):
        raise ValidationError("Use the inventory services.")

    def delete(self, *args, **kwargs):
        raise ValidationError("Inventory evidence cannot be deleted.")

    def clean_fields(self, exclude=None):
        for field in self._meta.concrete_fields:
            value = getattr(self, field.attname)
            if isinstance(field, (models.CharField, models.TextField)) and isinstance(value, str):
                setattr(self, field.attname, value.strip())
        super().clean_fields(exclude=exclude)

    def _persist(self):
        self.full_clean()
        super().save(using="default", force_insert=self._state.adding)

    def __str__(self):
        return f"{self._meta.verbose_name} {self.pk}"


class InventoryLocation(InventoryRecord):
    class Type(models.TextChoices):
        WAREHOUSE = "WAREHOUSE", "Warehouse"
        STORE = "STORE", "Service-center store"
        QUARANTINE = "QUARANTINE", "Quarantine"
        DEFECTIVE = "DEFECTIVE", "Defective"
        TRANSIT = "TRANSIT", "In transit (managed by transfer)"
        CUSTODY = "CUSTODY", "Job custody (managed by issue)"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    service_center = models.ForeignKey("organization.ServiceCenter", null=True, blank=True, on_delete=models.PROTECT)
    code = models.CharField(max_length=64, validators=[RegexValidator(CODE_PATTERN)])
    name = models.CharField(max_length=200)
    location_type = models.CharField(max_length=16, choices=Type.choices)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["company_id", "code"]
        permissions = [
            ("manage_inventory", "Manage inventory locations"),
            ("view_stock", "View scoped inventory and history"),
            ("receive_stock", "Receive inventory"),
            ("transfer_stock", "Move inventory within a company"),
        ]
        constraints = [
            models.UniqueConstraint(fields=["company", "code"], name="inventory_location_code_uniq"),
            models.UniqueConstraint(fields=["id", "company"], name="inventory_location_owner_uniq"),
            models.CheckConstraint(condition=models.Q(code__regex=CODE_PATTERN), name="inventory_location_code_valid"),
            models.CheckConstraint(condition=~models.Q(name=""), name="inventory_location_name_nonempty"),
            models.CheckConstraint(condition=models.Q(location_type__in=["WAREHOUSE", "STORE", "QUARANTINE", "DEFECTIVE", "TRANSIT", "CUSTODY"]), name="inventory_location_type_valid"),
            models.CheckConstraint(condition=~models.Q(location_type="STORE") | models.Q(service_center__isnull=False), name="inventory_store_requires_center"),
        ]

    def __str__(self):
        return f"{self.code}: {self.name}"


class StockPosition(InventoryRecord):
    """Lock anchor only. No cached or authoritative quantity is stored here."""
    location = models.ForeignKey(InventoryLocation, on_delete=models.PROTECT)
    spare_part = models.ForeignKey("parts.SparePart", on_delete=models.PROTECT)

    class Meta:
        constraints = [models.UniqueConstraint(fields=["location", "spare_part"], name="inventory_position_unique")]


class SerializedStockUnit(InventoryRecord):
    def __str__(self):
        return f"{self.spare_part.part_code}: {self.identifier}"

    class State(models.TextChoices):
        IN_CUSTODY = "IN_CUSTODY", "In job custody"
        CONSUMED = "CONSUMED", "Consumed in repair"
        REMOVED = "REMOVED", "Removed by inventory adjustment"
        REGISTERED = "REGISTERED", "Registered, not yet received"
        IN_STOCK = "IN_STOCK", "In stock"
        IN_TRANSIT = "IN_TRANSIT", "In transit"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    spare_part = models.ForeignKey("parts.SparePart", on_delete=models.PROTECT)
    identifier = models.CharField(max_length=128)
    current_location = models.ForeignKey(InventoryLocation, null=True, blank=True, on_delete=models.PROTECT)
    current_movement = models.ForeignKey("StockMovement", null=True, blank=True, on_delete=models.PROTECT, related_name="current_units")
    registered_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    state = models.CharField(max_length=16, choices=State.choices, default=State.REGISTERED)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["spare_part", "identifier"], name="inventory_unit_identity_permanent"),
            models.UniqueConstraint(fields=["id", "company", "spare_part"], name="inventory_unit_owner_part_unique"),
            models.CheckConstraint(condition=~models.Q(identifier=""), name="inventory_unit_identifier_nonempty"),
            models.CheckConstraint(condition=(models.Q(state="REGISTERED", current_location__isnull=True, current_movement__isnull=True)
                | models.Q(state__in=["IN_STOCK", "IN_TRANSIT", "IN_CUSTODY"], current_location__isnull=False, current_movement__isnull=False)
                | models.Q(state__in=["CONSUMED", "REMOVED"], current_location__isnull=True, current_movement__isnull=False)), name="inventory_unit_state_location"),
        ]


class StockMovement(InventoryRecord):
    class Kind(models.TextChoices):
        RECEIPT = "RECEIPT", "Receipt"
        MOVE = "MOVE", "Location movement"
        CONSUME = "CONSUME", "Repair consumption"
        ADJUST_IN = "ADJUST_IN", "Positive stock adjustment"
        ADJUST_OUT = "ADJUST_OUT", "Negative stock adjustment"

    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    spare_part = models.ForeignKey("parts.SparePart", on_delete=models.PROTECT)
    kind = models.CharField(max_length=16, choices=Kind.choices)
    quantity = models.PositiveIntegerField()
    source = models.ForeignKey(InventoryLocation, null=True, blank=True, on_delete=models.PROTECT, related_name="outgoing_movements")
    destination = models.ForeignKey(InventoryLocation, null=True, blank=True, on_delete=models.PROTECT, related_name="incoming_movements")
    actor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
    posted_at = models.DateTimeField(default=timezone.now, editable=False)
    reference = models.CharField(max_length=128)
    note = models.TextField(blank=True, validators=[MaxLengthValidator(2000)])
    idempotency_key = models.UUIDField()

    class Meta:
        ordering = ["posted_at", "id"]
        constraints = [
            models.UniqueConstraint(fields=["company", "idempotency_key"], name="inventory_movement_command_unique"),
            models.UniqueConstraint(fields=["id", "company", "spare_part"], name="inventory_movement_owner_part_unique"),
            models.CheckConstraint(condition=models.Q(quantity__gte=1, quantity__lte=1000000000), name="inventory_movement_quantity_positive"),
            models.CheckConstraint(condition=(models.Q(kind__in=["RECEIPT", "ADJUST_IN"], source__isnull=True, destination__isnull=False)
                | models.Q(kind="MOVE", source__isnull=False, destination__isnull=False)
                | models.Q(kind__in=["CONSUME", "ADJUST_OUT"], source__isnull=False, destination__isnull=True)), name="inventory_movement_kind_endpoints"),
            models.CheckConstraint(condition=~models.Q(source=models.F("destination")), name="inventory_movement_distinct_locations"),
            models.CheckConstraint(condition=~models.Q(reference=""), name="inventory_movement_reference_required"),
        ]

    def _persist(self):
        if not self._state.adding:
            raise ValidationError("Posted movement history is immutable.")
        super()._persist()


class StockLedgerEntry(InventoryRecord):
    movement = models.ForeignKey(StockMovement, on_delete=models.PROTECT, related_name="entries")
    company = models.ForeignKey("organization.Company", on_delete=models.PROTECT)
    spare_part = models.ForeignKey("parts.SparePart", on_delete=models.PROTECT)
    location = models.ForeignKey(InventoryLocation, on_delete=models.PROTECT, related_name="ledger_entries")
    quantity_delta = models.BigIntegerField()

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["movement", "location"], name="inventory_entry_movement_location_unique"),
            models.CheckConstraint(condition=~models.Q(quantity_delta=0), name="inventory_entry_nonzero"),
        ]

    def _persist(self):
        if not self._state.adding:
            raise ValidationError("Posted ledger history is immutable.")
        super()._persist()


class StockMovementUnit(InventoryRecord):
    movement = models.ForeignKey(StockMovement, on_delete=models.PROTECT, related_name="unit_links")
    unit = models.ForeignKey(SerializedStockUnit, on_delete=models.PROTECT, related_name="movement_links")

    class Meta:
        constraints = [models.UniqueConstraint(fields=["movement", "unit"], name="inventory_movement_unit_unique")]

    def _persist(self):
        if not self._state.adding:
            raise ValidationError("Unit movement history is immutable.")
        super()._persist()


from .document_models import (  # noqa: E402,F401
    InventoryDocumentSequence, GoodsReceipt, GoodsReceiptLine, GoodsReceiptIdentifier,
    StockTransfer, StockTransferLine, StockTransferUnit,
)

from .request_models import PartsRequest, PartsRequestLine, PartsRequestEvent, StockReservation, ReservationUnit  # noqa: E402,F401
from .usage_models import PartsIssue, PartsDisposition, DefectiveRecovery  # noqa: E402,F401
from .control_models import StockCount, StockCountUnit, StockAdjustment  # noqa: E402,F401
