from datetime import datetime
from decimal import Decimal
from typing import Dict

from sqlalchemy import JSON, BigInteger, ForeignKey, Numeric, String
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

__all__ = [
    "Base",
    "Item",
    "Provider",
    "Order",
    "LocationType",
    "Location",
    "ItemBatch",
    "Discount",
    "Customer",
    "Cart",
    "ItemOnShelf",
]


class Base(AsyncAttrs, DeclarativeBase):
    pass


class Provider(Base):
    __tablename__ = "providers"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    address: Mapped[str]
    phone: Mapped[str]
    vat: Mapped[str]

    def __repr__(self) -> str:
        return f"<Provider ({self.name}) at {hex(id(self))}>"


class Item(Base):
    __tablename__ = "items"
    sku: Mapped[int] = mapped_column(primary_key=True)
    upc: Mapped[int] = mapped_column(BigInteger, nullable=False)
    provider: Mapped[int] = mapped_column(ForeignKey("providers.id"))
    name: Mapped[str] = mapped_column(nullable=False)
    package: Mapped[str] = mapped_column(unique=False)
    current: Mapped[bool] = mapped_column(
        comment="True if the item can be still requested to a provider, "
        "False if it has been discontinued"
    )
    # unit_weight: Mapped[int] = mapped_column(
    #    comment="Unit weight in grams not including additional packaging"
    # ) To be added as exercise
    volume_unpacked: Mapped[int] = mapped_column(
        comment="Volume of the item unpacked in cubic decimeters"
    )
    volume_packed: Mapped[int] = mapped_column(
        comment="Volume of each unit item when packaged in cubic decimeters"
    )

    def __repr__(self) -> str:
        return f"<Item {self.name} at {hex(id(self))}>"


class Order(Base):
    __tablename__ = "orders"
    id: Mapped[int] = mapped_column(primary_key=True)
    sku: Mapped[int] = mapped_column(ForeignKey("items.sku"))
    placed: Mapped[datetime]
    qty: Mapped[int] = mapped_column(nullable=False)
    provider: Mapped[int] = mapped_column(ForeignKey("providers.id"))


class LocationType(Base):
    __tablename__ = "locationtypes"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    retail: Mapped[bool]


class Location(Base):
    __tablename__ = "locations"
    id: Mapped[int] = mapped_column(primary_key=True)
    loctype: Mapped[int] = mapped_column(ForeignKey("locationtypes.id"))
    name: Mapped[str]
    capacity: Mapped[int]


class ItemBatch(Base):
    __tablename__ = "batches"
    id: Mapped[int] = mapped_column(primary_key=True)
    lot: Mapped[str] = mapped_column(String(128), nullable=False)
    order: Mapped[str] = mapped_column(ForeignKey("orders.id"), nullable=True)
    received: Mapped[datetime] = mapped_column(nullable=True)
    unit_cost: Mapped[Decimal] = mapped_column(Numeric(9, 2), nullable=False)
    price: Mapped[Decimal] = mapped_column(Numeric(9, 2), nullable=False)
    best_until: Mapped[datetime]
    quantity: Mapped[int]
    sku: Mapped[id] = mapped_column(ForeignKey("items.sku"))
    item: Mapped[Item] = relationship()


class Discount(Base):
    __tablename__ = "discounts"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(unique=True, nullable=False)
    definition: Mapped[Dict[str, Dict[str, int]]] = mapped_column(JSON, nullable=True)

    def __repr__(self) -> str:
        return f"Type: {self.name} at {id(self)}"


class TaskOwner(Base):
    __tablename__ = "taskowners"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(unique=True, nullable=False)


class Task(Base):
    __tablename__ = "tasks"
    id: Mapped[int] = mapped_column(primary_key=True)
    taskowner: Mapped[int] = mapped_column(ForeignKey("taskowners.id"), nullable=False)
    location: Mapped[int] = mapped_column(ForeignKey("locations.id"), nullable=False)
    sku: Mapped[int] = mapped_column(ForeignKey("items.sku"), nullable=False)
    description: Mapped[str]


class Customer(Base):
    __tablename__ = "customers"
    id: Mapped[int] = mapped_column(primary_key=True)
    document: Mapped[str] = mapped_column(nullable=False)
    info: Mapped[Dict[str, str]] = mapped_column(JSON, nullable=True)


class Cart(Base):
    __tablename__ = "carts"
    id: Mapped[int] = mapped_column(primary_key=True)
    checkout: Mapped[datetime] = mapped_column(nullable=True)
    location: Mapped[int] = mapped_column(ForeignKey("locations.id"), nullable=False)
    customer: Mapped[int] = mapped_column(ForeignKey("customers.id"), nullable=True)


class ItemOnCart(Base):
    __tablename__ = "itemsoncart"
    id: Mapped[int] = mapped_column(primary_key=True)
    cart: Mapped[int] = mapped_column(ForeignKey("carts.id"), nullable=False)
    upc: Mapped[int] = mapped_column(nullable=False)  # Not FK to avoid sync issues
    quantity: Mapped[int] = mapped_column(nullable=True)
    unitprice: Mapped[Decimal] = mapped_column(Numeric(9, 2), nullable=True)
    discount: Mapped[Decimal] = mapped_column(Numeric(9, 2), nullable=True)


class ItemOnShelf(Base):
    __tablename__ = "itemsonshelf"
    id: Mapped[int] = mapped_column(primary_key=True)
    batch: Mapped[int] = mapped_column(ForeignKey("batches.id"), nullable=True)
    discount: Mapped[int] = mapped_column(ForeignKey("discounts.id"), nullable=True)
    quantity: Mapped[int] = mapped_column(nullable=False)
    location: Mapped[int] = mapped_column(ForeignKey("locations.id"), nullable=False)
    batches: Mapped[ItemBatch] = relationship()
