"""Modèles du module Matériel & Produits d'entretien.

Les quantités sont toujours exprimées dans l'unité de référence du produit et
stockées en DECIMAL.  Les mouvements constituent l'historique immuable ; les
soldes par lot/emplacement sont des projections verrouillées pendant les
opérations transactionnelles.
"""
from datetime import datetime, timezone
from decimal import Decimal

from ...extensions import db


def utcnow():
    return datetime.now(timezone.utc)


class ProductCategory(db.Model):
    __tablename__ = "cleaning_product_categories"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), nullable=False, unique=True)
    description = db.Column(db.Text)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    products = db.relationship("ProductGeneric", back_populates="category")


class ProductGeneric(db.Model):
    __tablename__ = "cleaning_products_generic"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False, unique=True)
    description = db.Column(db.Text)
    category_id = db.Column(db.Integer, db.ForeignKey("cleaning_product_categories.id"), index=True)
    product_type = db.Column(db.String(40), nullable=False, default="consommable")
    reference_unit = db.Column(db.String(30), nullable=False, default="unité")
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    stock_minimum = db.Column(db.Numeric(14, 6), nullable=False, default=0)
    stock_security = db.Column(db.Numeric(14, 6), nullable=False, default=0)
    forecast_daily_quantity = db.Column(db.Numeric(14, 6), nullable=False, default=0)
    display_order = db.Column(db.Integer, nullable=False, default=0)
    usage_zones = db.Column(db.String(500))
    request_enabled = db.Column(db.Boolean, nullable=False, default=True)
    allowed_distribution_modes = db.Column(db.String(255), nullable=False, default="standard")
    created_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    updated_at = db.Column(db.DateTime, nullable=False, default=utcnow, onupdate=utcnow)
    category = db.relationship("ProductCategory", back_populates="products")
    commercial_products = db.relationship("CommercialProduct", back_populates="generic_product", cascade="all, delete-orphan")
    packagings = db.relationship("ProductPackaging", back_populates="generic_product", cascade="all, delete-orphan")
    lots = db.relationship("StockLot", back_populates="generic_product")

    @property
    def stock_total(self):
        return sum((b.quantity for lot in self.lots for b in lot.balances), Decimal("0"))


class CommercialProduct(db.Model):
    __tablename__ = "cleaning_commercial_products"
    id = db.Column(db.Integer, primary_key=True)
    generic_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id"), nullable=False, index=True)
    commercial_name = db.Column(db.String(200), nullable=False)
    brand = db.Column(db.String(120))
    manufacturer_id = db.Column(db.Integer, db.ForeignKey("companies.id"), index=True)
    supplier_id = db.Column(db.Integer, db.ForeignKey("companies.id"), index=True)
    manufacturer_reference = db.Column(db.String(120))
    supplier_reference = db.Column(db.String(120))
    barcode = db.Column(db.String(80), index=True)
    valid_from = db.Column(db.Date)
    valid_to = db.Column(db.Date)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    photo_path = db.Column(db.String(500))
    dosage_information = db.Column(db.Text)
    is_concentrated = db.Column(db.Boolean, nullable=False, default=False)
    after_opening_days = db.Column(db.Integer)
    safety_notes = db.Column(db.Text)
    pictograms = db.Column(db.String(500))
    warning_statement = db.Column(db.String(120))
    hazard_statements = db.Column(db.Text)
    precautionary_statements = db.Column(db.Text)
    required_ppe = db.Column(db.String(500))
    storage_conditions = db.Column(db.Text)
    incompatibilities = db.Column(db.Text)
    created_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    generic_product = db.relationship("ProductGeneric", back_populates="commercial_products")
    manufacturer = db.relationship("Company", foreign_keys=[manufacturer_id])
    supplier = db.relationship("Company", foreign_keys=[supplier_id])
    lots = db.relationship("StockLot", back_populates="commercial_product")
    packagings = db.relationship("ProductPackaging", back_populates="commercial_product")
    documents = db.relationship("ProductDocument", back_populates="commercial_product", cascade="all, delete-orphan")


class ProductPackaging(db.Model):
    __tablename__ = "cleaning_product_packagings"
    id = db.Column(db.Integer, primary_key=True)
    generic_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id"), nullable=False, index=True)
    commercial_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_commercial_products.id"), index=True)
    name = db.Column(db.String(160), nullable=False)
    purchase_unit = db.Column(db.String(40), nullable=False, default="conditionnement")
    units_per_package = db.Column(db.Numeric(14, 6), nullable=False, default=1)
    reference_quantity_per_package = db.Column(db.Numeric(14, 6), nullable=False)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    is_current = db.Column(db.Boolean, nullable=False, default=True)
    generic_product = db.relationship("ProductGeneric", back_populates="packagings")
    commercial_product = db.relationship("CommercialProduct", back_populates="packagings")


class StockLocation(db.Model):
    __tablename__ = "cleaning_stock_locations"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(160), nullable=False, unique=True)
    building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), index=True)
    zone_id = db.Column(db.Integer, db.ForeignKey("zones.id"), index=True)
    room_id = db.Column(db.Integer, db.ForeignKey("rooms.id"), index=True)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    notes = db.Column(db.Text)
    balances = db.relationship("StockLotBalance", back_populates="location")


class StockLot(db.Model):
    __tablename__ = "cleaning_stock_lots"
    id = db.Column(db.Integer, primary_key=True)
    generic_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id"), nullable=False, index=True)
    commercial_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_commercial_products.id"), nullable=False, index=True)
    lot_number = db.Column(db.String(120), nullable=False, index=True)
    received_at = db.Column(db.Date, nullable=False)
    manufactured_at = db.Column(db.Date)
    expiry_date = db.Column(db.Date, index=True)
    quantity_received = db.Column(db.Numeric(14, 6), nullable=False)
    opened_at = db.Column(db.Date)
    after_opening_days = db.Column(db.Integer)
    status = db.Column(db.String(30), nullable=False, default="disponible", index=True)
    notes = db.Column(db.Text)
    generic_product = db.relationship("ProductGeneric", back_populates="lots")
    commercial_product = db.relationship("CommercialProduct", back_populates="lots")
    balances = db.relationship("StockLotBalance", back_populates="lot", cascade="all, delete-orphan")
    movements = db.relationship("StockMovement", back_populates="lot")


class StockLotBalance(db.Model):
    __tablename__ = "cleaning_stock_lot_balances"
    __table_args__ = (db.UniqueConstraint("lot_id", "location_id", name="uq_cleaning_lot_location"),)
    id = db.Column(db.Integer, primary_key=True)
    lot_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_lots.id", ondelete="RESTRICT"), nullable=False, index=True)
    location_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_locations.id", ondelete="RESTRICT"), nullable=False, index=True)
    quantity = db.Column(db.Numeric(14, 6), nullable=False, default=0)
    lot = db.relationship("StockLot", back_populates="balances")
    location = db.relationship("StockLocation", back_populates="balances")


class StockMovement(db.Model):
    __tablename__ = "cleaning_stock_movements"
    id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
    generic_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id"), nullable=False, index=True)
    commercial_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_commercial_products.id"), index=True)
    lot_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_lots.id"), index=True)
    movement_type = db.Column(db.String(30), nullable=False, index=True)
    quantity_reference = db.Column(db.Numeric(14, 6), nullable=False)
    source_location_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_locations.id"), index=True)
    destination_location_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_locations.id"), index=True)
    staff_id = db.Column(db.Integer, db.ForeignKey("staff.id"), index=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), index=True)
    comment = db.Column(db.Text)
    dilution_ratio = db.Column(db.Numeric(8, 5))
    created_at = db.Column(db.DateTime, nullable=False, default=utcnow, index=True)
    lot = db.relationship("StockLot", back_populates="movements")
    generic_product = db.relationship("ProductGeneric")
    commercial_product = db.relationship("CommercialProduct")
    source_location = db.relationship("StockLocation", foreign_keys=[source_location_id])
    destination_location = db.relationship("StockLocation", foreign_keys=[destination_location_id])
    staff = db.relationship("Staff")
    user = db.relationship("User")


class MaterialAssignment(db.Model):
    __tablename__ = "cleaning_material_assignments"
    id = db.Column(db.Integer, primary_key=True)
    equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id", ondelete="RESTRICT"), nullable=False, index=True)
    staff_id = db.Column(db.Integer, db.ForeignKey("staff.id"), index=True)
    zone_id = db.Column(db.Integer, db.ForeignKey("zones.id"), index=True)
    room_id = db.Column(db.Integer, db.ForeignKey("rooms.id"), index=True)
    status = db.Column(db.String(30), nullable=False, default="affecte")
    assigned_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    returned_at = db.Column(db.DateTime)
    notes = db.Column(db.Text)
    equipment = db.relationship("Equipment")
    staff = db.relationship("Staff")
    zone = db.relationship("Zone")
    room = db.relationship("Room")


class ReusableContainer(db.Model):
    __tablename__ = "cleaning_reusable_containers"
    id = db.Column(db.Integer, primary_key=True)
    identifier = db.Column(db.String(80), nullable=False, unique=True)
    container_type = db.Column(db.String(80), nullable=False)
    volume_liters = db.Column(db.Numeric(12, 6), nullable=False)
    dedicated_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id"))
    current_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id"))
    staff_id = db.Column(db.Integer, db.ForeignKey("staff.id"))
    zone_id = db.Column(db.Integer, db.ForeignKey("zones.id"))
    state = db.Column(db.String(30), nullable=False, default="disponible")
    last_filled_at = db.Column(db.DateTime)
    dedicated_product = db.relationship("ProductGeneric", foreign_keys=[dedicated_product_id])
    current_product = db.relationship("ProductGeneric", foreign_keys=[current_product_id])
    staff = db.relationship("Staff")
    zone = db.relationship("Zone")
    fills = db.relationship("ContainerFill", back_populates="container", cascade="all, delete-orphan")


class ContainerFill(db.Model):
    __tablename__ = "cleaning_container_fills"
    id = db.Column(db.Integer, primary_key=True)
    container_id = db.Column(db.Integer, db.ForeignKey("cleaning_reusable_containers.id", ondelete="RESTRICT"), nullable=False, index=True)
    movement_id = db.Column(db.BigInteger, db.ForeignKey("cleaning_stock_movements.id", ondelete="RESTRICT"), nullable=False)
    volume_liters = db.Column(db.Numeric(12, 6), nullable=False)
    dilution_ratio = db.Column(db.Numeric(8, 5))
    prepared_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    container = db.relationship("ReusableContainer", back_populates="fills")
    movement = db.relationship("StockMovement")


class ProductDocument(db.Model):
    __tablename__ = "cleaning_product_documents"
    id = db.Column(db.Integer, primary_key=True)
    commercial_product_id = db.Column(db.Integer, db.ForeignKey("cleaning_commercial_products.id", ondelete="RESTRICT"), nullable=False, index=True)
    document_type = db.Column(db.String(30), nullable=False, default="fds")
    title = db.Column(db.String(255), nullable=False)
    filename = db.Column(db.String(255), nullable=False)
    filepath = db.Column(db.String(500), nullable=False)
    version = db.Column(db.String(50))
    published_at = db.Column(db.Date)
    uploaded_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    uploaded_by_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"))
    is_current = db.Column(db.Boolean, nullable=False, default=True)
    commercial_product = db.relationship("CommercialProduct", back_populates="documents")
    uploaded_by = db.relationship("User")


class RequestProfile(db.Model):
    __tablename__ = "cleaning_request_profiles"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(160), nullable=False, unique=True)
    description = db.Column(db.Text)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    items = db.relationship("RequestProfileItem", back_populates="profile", cascade="all, delete-orphan", order_by="RequestProfileItem.display_order")


class RequestProfileItem(db.Model):
    __tablename__ = "cleaning_request_profile_items"
    id = db.Column(db.Integer, primary_key=True)
    profile_id = db.Column(db.Integer, db.ForeignKey("cleaning_request_profiles.id", ondelete="CASCADE"), nullable=False, index=True)
    product_id = db.Column(db.Integer, db.ForeignKey("cleaning_products_generic.id", ondelete="RESTRICT"), nullable=False, index=True)
    display_order = db.Column(db.Integer, nullable=False, default=0)
    profile = db.relationship("RequestProfile", back_populates="items")
    product = db.relationship("ProductGeneric")


class StockInventory(db.Model):
    __tablename__ = "cleaning_stock_inventories"
    id = db.Column(db.Integer, primary_key=True)
    location_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_locations.id"), nullable=False, index=True)
    counted_at = db.Column(db.DateTime, nullable=False, default=utcnow)
    status = db.Column(db.String(20), nullable=False, default="brouillon")
    user_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"))
    notes = db.Column(db.Text)
    location = db.relationship("StockLocation")
    user = db.relationship("User")
    lines = db.relationship("StockInventoryLine", back_populates="inventory", cascade="all, delete-orphan")


class StockInventoryLine(db.Model):
    __tablename__ = "cleaning_stock_inventory_lines"
    id = db.Column(db.Integer, primary_key=True)
    inventory_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_inventories.id", ondelete="CASCADE"), nullable=False, index=True)
    lot_id = db.Column(db.Integer, db.ForeignKey("cleaning_stock_lots.id", ondelete="RESTRICT"), nullable=False)
    theoretical_quantity = db.Column(db.Numeric(14, 6), nullable=False)
    counted_quantity = db.Column(db.Numeric(14, 6), nullable=False)
    inventory = db.relationship("StockInventory", back_populates="lines")
    lot = db.relationship("StockLot")


class CleaningForecastConfig(db.Model):
    __tablename__ = "cleaning_forecast_configs"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120), nullable=False, default="Configuration générale")
    normal_coefficient = db.Column(db.Numeric(8, 5), nullable=False, default=1)
    permanence_coefficient = db.Column(db.Numeric(8, 5), nullable=False, default=Decimal("0.4"))
    closed_coefficient = db.Column(db.Numeric(8, 5), nullable=False, default=0)
    exceptional_coefficient = db.Column(db.Numeric(8, 5), nullable=False, default=1)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
