import uuid
from datetime import datetime

from sqlalchemy import (
    Boolean,
    DateTime,
    Enum,
    Float,
    ForeignKey,
    Index,
    Integer,
    LargeBinary,
    String,
    Text,
    UniqueConstraint,
    func,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(DeclarativeBase):
    pass


class BaseMixin:
    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)


class PharmacyModel(Base, BaseMixin):
    __tablename__ = "pharmacies"

    code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    address: Mapped[str | None] = mapped_column(String(500))
    city: Mapped[str | None] = mapped_column(String(100))
    province: Mapped[str | None] = mapped_column(String(100))
    postal_code: Mapped[str | None] = mapped_column(String(20))
    country: Mapped[str] = mapped_column(String(2), default="ES")
    latitude: Mapped[float | None] = mapped_column(Float)
    longitude: Mapped[float | None] = mapped_column(Float)
    phone: Mapped[str | None] = mapped_column(String(50))
    email: Mapped[str | None] = mapped_column(String(255))
    opening_hours: Mapped[dict | None] = mapped_column(JSONB)
    status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
    erp_type: Mapped[str | None] = mapped_column(String(50))
    erp_version: Mapped[str | None] = mapped_column(String(50))
    connector_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("connectors.id"), nullable=True)
    sync_strategy: Mapped[str] = mapped_column(String(20), default="incremental")
    sync_interval_minutes: Mapped[int] = mapped_column(Integer, default=30)
    last_sync_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    last_sync_status: Mapped[str | None] = mapped_column(String(20))
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    connector = relationship("ConnectorModel", back_populates="pharmacies", foreign_keys=[connector_id])
    inventory_records = relationship("InventoryRecordModel", back_populates="pharmacy", cascade="all, delete-orphan")
    reservations = relationship("ReservationModel", back_populates="pharmacy", cascade="all, delete-orphan")
    catalog_entries = relationship("MedicationCatalogEntryModel", back_populates="pharmacy", cascade="all, delete-orphan")
    sync_jobs = relationship("SyncJobModel", back_populates="pharmacy", cascade="all, delete-orphan")

    __table_args__ = (
        Index("ix_pharmacies_location", "latitude", "longitude"),
        Index("ix_pharmacies_status_erp", "status", "erp_type"),
    )


class ERPSystemModel(Base, BaseMixin):
    __tablename__ = "erp_systems"

    name: Mapped[str] = mapped_column(String(255), nullable=False)
    erp_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
    version: Mapped[str | None] = mapped_column(String(50))
    base_url: Mapped[str | None] = mapped_column(String(500))
    auth_config: Mapped[dict | None] = mapped_column(JSONB)
    status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    connectors = relationship("ConnectorModel", back_populates="erp_system", cascade="all, delete-orphan")


class ConnectorModel(Base, BaseMixin):
    __tablename__ = "connectors"

    erp_system_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("erp_systems.id"), nullable=False, index=True)
    pharmacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("pharmacies.id"), nullable=False, index=True)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    connector_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
    version: Mapped[str] = mapped_column(String(20), default="1.0.0")
    config: Mapped[dict | None] = mapped_column(JSONB)
    status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
    last_heartbeat_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    last_error: Mapped[str | None] = mapped_column(Text)
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    erp_system = relationship("ERPSystemModel", back_populates="connectors")
    pharmacies = relationship("PharmacyModel", back_populates="connector", foreign_keys="PharmacyModel.connector_id")
    configs = relationship("ConnectorConfigModel", back_populates="connector", cascade="all, delete-orphan")
    connector_versions = relationship("ConnectorVersionModel", back_populates="connector", cascade="all, delete-orphan")

    __table_args__ = (
        UniqueConstraint("erp_system_id", "pharmacy_id", name="uq_connector_erp_pharmacy"),
    )


class ConnectorVersionModel(Base, BaseMixin):
    __tablename__ = "connector_versions"

    connector_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("connectors.id"), nullable=False, index=True)
    connector_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
    version: Mapped[str] = mapped_column(String(20), nullable=False)
    changelog: Mapped[str | None] = mapped_column(Text)
    download_url: Mapped[str | None] = mapped_column(String(500))
    checksum: Mapped[str | None] = mapped_column(String(128))
    is_latest: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
    is_required: Mapped[bool] = mapped_column(Boolean, default=False)
    released_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))

    connector = relationship("ConnectorModel", back_populates="connector_versions")

    __table_args__ = (
        UniqueConstraint("connector_type", "version", name="uq_connector_version"),
    )


class ConnectorConfigModel(Base, BaseMixin):
    __tablename__ = "connector_configs"

    connector_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("connectors.id"), nullable=False, index=True)
    key: Mapped[str] = mapped_column(String(100), nullable=False)
    value: Mapped[str] = mapped_column(Text, nullable=False)
    encrypted: Mapped[bool] = mapped_column(Boolean, default=False)
    description: Mapped[str | None] = mapped_column(String(500))

    connector = relationship("ConnectorModel", back_populates="configs")

    __table_args__ = (
        UniqueConstraint("connector_id", "key", name="uq_connector_config_key"),
    )


class MedicationModel(Base, BaseMixin):
    __tablename__ = "medications"

    nregistro: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
    name: Mapped[str] = mapped_column(String(500), nullable=False, index=True)
    active_ingredient: Mapped[str | None] = mapped_column(String(500))
    dosage: Mapped[str | None] = mapped_column(String(100))
    form: Mapped[str | None] = mapped_column(String(100))
    atc_code: Mapped[str | None] = mapped_column(String(20), index=True)
    prescription_required: Mapped[bool] = mapped_column(Boolean, default=False)
    manufacturer: Mapped[str | None] = mapped_column(String(255))
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    catalog_entries = relationship("MedicationCatalogEntryModel", back_populates="medication", cascade="all, delete-orphan")
    inventory_records = relationship("InventoryRecordModel", back_populates="medication", cascade="all, delete-orphan")
    reservations = relationship("ReservationModel", back_populates="medication", cascade="all, delete-orphan")


class MedicationCatalogEntryModel(Base, BaseMixin):
    __tablename__ = "medication_catalog"

    pharmacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("pharmacies.id"), nullable=False, index=True)
    medication_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("medications.id"), nullable=False, index=True)
    price: Mapped[float | None] = mapped_column(Float)
    stock: Mapped[int] = mapped_column(Integer, default=0)
    is_available: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
    last_verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    pharmacy = relationship("PharmacyModel", back_populates="catalog_entries")
    medication = relationship("MedicationModel", back_populates="catalog_entries")

    __table_args__ = (
        UniqueConstraint("pharmacy_id", "medication_id", name="uq_catalog_pharmacy_medication"),
    )


class InventoryRecordModel(Base, BaseMixin):
    __tablename__ = "inventory_records"

    pharmacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("pharmacies.id"), nullable=False, index=True)
    medication_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("medications.id"), nullable=False, index=True)
    stock: Mapped[int] = mapped_column(Integer, default=0)
    reserved_stock: Mapped[int] = mapped_column(Integer, default=0)
    available_stock: Mapped[int] = mapped_column(Integer, default=0)
    min_stock: Mapped[int | None] = mapped_column(Integer)
    max_stock: Mapped[int | None] = mapped_column(Integer)
    last_updated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
    source: Mapped[str | None] = mapped_column(String(100))
    batch_id: Mapped[str | None] = mapped_column(String(100))
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    pharmacy = relationship("PharmacyModel", back_populates="inventory_records")
    medication = relationship("MedicationModel", back_populates="inventory_records")
    history = relationship("InventoryHistoryModel", back_populates="inventory_record", cascade="all, delete-orphan")

    __table_args__ = (
        UniqueConstraint("pharmacy_id", "medication_id", name="uq_inventory_pharmacy_medication"),
    )


class InventoryHistoryModel(Base):
    __tablename__ = "inventory_history"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    inventory_record_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("inventory_records.id"), nullable=False, index=True)
    pharmacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("pharmacies.id"), nullable=False, index=True)
    medication_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("medications.id"), nullable=False, index=True)
    previous_stock: Mapped[int] = mapped_column(Integer, nullable=False)
    new_stock: Mapped[int] = mapped_column(Integer, nullable=False)
    delta: Mapped[int] = mapped_column(Integer, nullable=False)
    reason: Mapped[str | None] = mapped_column(String(255))
    source: Mapped[str | None] = mapped_column(String(100))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)

    inventory_record = relationship("InventoryRecordModel", back_populates="history")


class ReservationModel(Base, BaseMixin):
    __tablename__ = "reservations"

    pharmacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("pharmacies.id"), nullable=False, index=True)
    medication_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("medications.id"), nullable=False, index=True)
    user_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("technical_users.id"), nullable=True, index=True)
    external_reference: Mapped[str | None] = mapped_column(String(255))
    quantity: Mapped[int] = mapped_column(Integer, nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
    expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)
    confirmed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    cancelled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    idempotency_key: Mapped[str | None] = mapped_column(String(255), unique=True, index=True)
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    pharmacy = relationship("PharmacyModel", back_populates="reservations")
    medication = relationship("MedicationModel", back_populates="reservations")
    user = relationship("TechnicalUserModel", back_populates="reservations")

    __table_args__ = (
        Index("ix_reservations_pharmacy_status", "pharmacy_id", "status"),
        Index("ix_reservations_expires", "expires_at"),
    )


class SyncJobModel(Base, BaseMixin):
    __tablename__ = "sync_jobs"

    pharmacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("pharmacies.id"), nullable=False, index=True)
    connector_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("connectors.id"), nullable=False, index=True)
    strategy: Mapped[str] = mapped_column(String(20), nullable=False)
    status: Mapped[str] = mapped_column(String(20), default="pending", index=True)
    started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    items_total: Mapped[int | None] = mapped_column(Integer)
    items_processed: Mapped[int | None] = mapped_column(Integer)
    items_failed: Mapped[int | None] = mapped_column(Integer)
    error_message: Mapped[str | None] = mapped_column(Text)
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    pharmacy = relationship("PharmacyModel", back_populates="sync_jobs")
    logs = relationship("SyncLogModel", back_populates="sync_job", cascade="all, delete-orphan")


class SyncLogModel(Base):
    __tablename__ = "sync_logs"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    sync_job_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("sync_jobs.id"), nullable=False, index=True)
    level: Mapped[str] = mapped_column(String(20), default="INFO", nullable=False)
    message: Mapped[str] = mapped_column(Text, nullable=False)
    details: Mapped[dict | None] = mapped_column(JSONB)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)

    sync_job = relationship("SyncJobModel", back_populates="logs")


class AuditLogModel(Base):
    __tablename__ = "audit_logs"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    actor_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), index=True)
    actor_type: Mapped[str | None] = mapped_column(String(50))
    action: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
    resource_type: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
    resource_id: Mapped[str | None] = mapped_column(String(255), index=True)
    old_value: Mapped[dict | None] = mapped_column(JSONB)
    new_value: Mapped[dict | None] = mapped_column(JSONB)
    ip_address: Mapped[str | None] = mapped_column(String(45))
    user_agent: Mapped[str | None] = mapped_column(String(500))
    request_id: Mapped[str | None] = mapped_column(String(100), index=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)

    __table_args__ = (
        Index("ix_audit_logs_action_resource", "action", "resource_type"),
        Index("ix_audit_logs_actor_created", "actor_id", "created_at"),
    )


class TechnicalUserModel(Base, BaseMixin):
    __tablename__ = "technical_users"

    username: Mapped[str] = mapped_column(String(100), unique=True, nullable=False, index=True)
    email: Mapped[str | None] = mapped_column(String(255))
    full_name: Mapped[str | None] = mapped_column(String(255))
    password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
    role: Mapped[str] = mapped_column(String(20), default="viewer", index=True)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
    scopes: Mapped[dict | None] = mapped_column(JSONB, default=list)
    last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))

    reservations = relationship("ReservationModel", back_populates="user")
    api_clients = relationship("APIClientModel", back_populates="owner")


class APIClientModel(Base, BaseMixin):
    __tablename__ = "api_clients"

    name: Mapped[str] = mapped_column(String(255), nullable=False)
    description: Mapped[str | None] = mapped_column(Text)
    owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("technical_users.id"), nullable=True, index=True)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
    scopes: Mapped[dict | None] = mapped_column(JSONB, default=list)
    rate_limit: Mapped[int | None] = mapped_column(Integer)
    allowed_ips: Mapped[dict | None] = mapped_column(JSONB, default=list)
    metadata_: Mapped[dict | None] = mapped_column("metadata", JSONB)

    owner = relationship("TechnicalUserModel", back_populates="api_clients")
    api_keys = relationship("APIKeyModel", back_populates="api_client", cascade="all, delete-orphan")


class APIKeyModel(Base):
    __tablename__ = "api_keys"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    api_client_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("api_clients.id"), nullable=False, index=True)
    key_prefix: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
    key_hash: Mapped[bytes] = mapped_column(LargeBinary(64), nullable=False)
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
    expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)

    api_client = relationship("APIClientModel", back_populates="api_keys")

    __table_args__ = (
        Index("ix_api_keys_prefix_active", "key_prefix", "is_active"),
    )
