"""
Extensión de la base de datos para incluir información de productos.
Este archivo extiende la funcionalidad de database.py sin modificar los archivos originales.

Nueva tabla: product_details
Relación: One-to-One con image
"""

from typing import Optional
from sqlalchemy import ForeignKey, String, create_engine, Float
from sqlalchemy.orm import (
    Mapped,
    mapped_column,
    relationship,
    sessionmaker,
)
from sqlalchemy.orm.session import Session
from database import Base, Image


class ProductDetails(Base):
    """
    Tabla que almacena información detallada de productos asociados a imágenes.
    Relación One-to-One con Image.
    """
    __tablename__ = "product_details"
    
    id: Mapped[int] = mapped_column(primary_key=True)
    image_id: Mapped[int] = mapped_column(
        ForeignKey("image.id", ondelete="CASCADE"),
        unique=True,
        nullable=False
    )
    
    # Dimensiones (en centímetros)
    alto: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    largo: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    ancho: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    
    # Peso (en kilogramos)
    peso: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    
    # Precio (en la moneda del sistema)
    precio: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    
    # SKU (Stock Keeping Unit) - código único del producto
    sku: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, unique=True)
    
    # Relación One-to-One con Image
    # Nota: La relación bidireccional no está definida en Image (archivo original)
    # pero la relación funciona desde este lado
    image: Mapped["Image"] = relationship(
        "Image",
        uselist=False,
        foreign_keys=[image_id]
    )
    
    def __repr__(self) -> str:
        return f"ProductDetails(id={self.id!r}, image_id={self.image_id!r}, sku={self.sku!r})"


# Actualizar la relación en Image (esto se hace mediante la relación bidireccional)
# Nota: En SQLAlchemy, la relación se define en ambos lados, pero como no podemos
# modificar database.py, la relación se manejará desde este lado.


class DatabaseExtended:
    """
    Clase extendida que proporciona métodos para trabajar con ProductDetails.
    Utiliza la misma conexión de base de datos que Database.
    """
    
    def __init__(self, db_path: str = './db.sqlite3'):
        """
        Inicializa la conexión a la base de datos.
        
        Args:
            db_path: Ruta al archivo de base de datos SQLite
        """
        self.engine = create_engine(f'sqlite+pysqlite:///{db_path}', echo=True)
        self.session = sessionmaker[Session](self.engine)
        
        # Crear todas las tablas (incluyendo las nuevas)
        Base.metadata.create_all(self.engine)
    
    def add_product_details(
        self,
        image_id: int,
        alto: Optional[float] = None,
        largo: Optional[float] = None,
        ancho: Optional[float] = None,
        peso: Optional[float] = None,
        precio: Optional[float] = None,
        sku: Optional[str] = None
    ) -> ProductDetails:
        """
        Agrega información de producto para una imagen.
        
        Args:
            image_id: ID de la imagen a la que se asocia el producto
            alto: Altura del producto en centímetros
            largo: Largo del producto en centímetros
            ancho: Ancho del producto en centímetros
            peso: Peso del producto en kilogramos
            precio: Precio del producto
            sku: Código SKU único del producto
            
        Returns:
            Objeto ProductDetails creado
            
        Raises:
            Exception: Si hay error al agregar el producto
        """
        try:
            with self.session() as session:
                # Verificar que la imagen existe
                image = session.query(Image).filter(Image.id == image_id).first()
                if image is None:
                    raise ValueError(f"Image with id {image_id} does not exist")
                
                # Verificar que no existe ya un producto para esta imagen
                existing = session.query(ProductDetails).filter(
                    ProductDetails.image_id == image_id
                ).first()
                if existing is not None:
                    raise ValueError(f"Product details already exist for image_id {image_id}")
                
                # Verificar unicidad de SKU si se proporciona
                if sku:
                    existing_sku = session.query(ProductDetails).filter(
                        ProductDetails.sku == sku
                    ).first()
                    if existing_sku is not None:
                        raise ValueError(f"SKU {sku} already exists")
                
                product_details = ProductDetails(
                    image_id=image_id,
                    alto=alto,
                    largo=largo,
                    ancho=ancho,
                    peso=peso,
                    precio=precio,
                    sku=sku
                )
                session.add(product_details)
                session.commit()
                return product_details
        except Exception as e:
            print(f"Error adding product details: {e}")
            raise e
    
    def get_product_details(self, image_id: int) -> Optional[ProductDetails]:
        """
        Obtiene los detalles del producto asociados a una imagen.
        
        Args:
            image_id: ID de la imagen
            
        Returns:
            Objeto ProductDetails o None si no existe
        """
        try:
            with self.session() as session:
                return session.query(ProductDetails).filter(
                    ProductDetails.image_id == image_id
                ).first()
        except Exception as e:
            print(f"Error getting product details: {e}")
            raise e
    
    def get_product_by_sku(self, sku: str) -> Optional[ProductDetails]:
        """
        Obtiene los detalles del producto por su SKU.
        
        Args:
            sku: Código SKU del producto
            
        Returns:
            Objeto ProductDetails o None si no existe
        """
        try:
            with self.session() as session:
                return session.query(ProductDetails).filter(
                    ProductDetails.sku == sku
                ).first()
        except Exception as e:
            print(f"Error getting product by SKU: {e}")
            raise e
    
    def update_product_details(
        self,
        image_id: int,
        alto: Optional[float] = None,
        largo: Optional[float] = None,
        ancho: Optional[float] = None,
        peso: Optional[float] = None,
        precio: Optional[float] = None,
        sku: Optional[str] = None
    ) -> Optional[ProductDetails]:
        """
        Actualiza los detalles del producto para una imagen.
        
        Args:
            image_id: ID de la imagen
            alto: Nueva altura (None para no actualizar)
            largo: Nuevo largo (None para no actualizar)
            ancho: Nuevo ancho (None para no actualizar)
            peso: Nuevo peso (None para no actualizar)
            precio: Nuevo precio (None para no actualizar)
            sku: Nuevo SKU (None para no actualizar)
            
        Returns:
            Objeto ProductDetails actualizado o None si no existe
        """
        try:
            with self.session() as session:
                product = session.query(ProductDetails).filter(
                    ProductDetails.image_id == image_id
                ).first()
                
                if product is None:
                    return None
                
                # Actualizar solo los campos proporcionados
                if alto is not None:
                    product.alto = alto
                if largo is not None:
                    product.largo = largo
                if ancho is not None:
                    product.ancho = ancho
                if peso is not None:
                    product.peso = peso
                if precio is not None:
                    product.precio = precio
                if sku is not None:
                    # Verificar unicidad de SKU si se actualiza
                    existing_sku = session.query(ProductDetails).filter(
                        ProductDetails.sku == sku,
                        ProductDetails.id != product.id
                    ).first()
                    if existing_sku is not None:
                        raise ValueError(f"SKU {sku} already exists")
                    product.sku = sku
                
                session.commit()
                return product
        except Exception as e:
            print(f"Error updating product details: {e}")
            raise e
    
    def delete_product_details(self, image_id: int) -> bool:
        """
        Elimina los detalles del producto asociados a una imagen.
        
        Args:
            image_id: ID de la imagen
            
        Returns:
            True si se eliminó, False si no existía
        """
        try:
            with self.session() as session:
                product = session.query(ProductDetails).filter(
                    ProductDetails.image_id == image_id
                ).first()
                
                if product is None:
                    return False
                
                session.delete(product)
                session.commit()
                return True
        except Exception as e:
            print(f"Error deleting product details: {e}")
            raise e
    
    def get_all_products_with_details(self):
        """
        Obtiene todas las imágenes que tienen detalles de producto asociados.
        
        Returns:
            Lista de objetos ProductDetails con sus imágenes relacionadas
        """
        try:
            with self.session() as session:
                return session.query(ProductDetails).join(Image).all()
        except Exception as e:
            print(f"Error getting all products with details: {e}")
            raise e
    
    def get_images_without_product_details(self):
        """
        Obtiene todas las imágenes que no tienen detalles de producto asociados.
        
        Returns:
            Lista de objetos Image sin ProductDetails
        """
        try:
            with self.session() as session:
                return session.query(Image).outerjoin(ProductDetails).filter(
                    ProductDetails.id == None
                ).all()
        except Exception as e:
            print(f"Error getting images without product details: {e}")
            raise e


# Nota: Para que la relación bidireccional funcione correctamente con Image,
# sería necesario modificar database.py para agregar la relación en Image.
# Como no podemos modificar ese archivo, la relación se maneja desde este lado.
# Si necesitas acceder a product_details desde Image, puedes hacerlo mediante:
# product_details = db_extended.get_product_details(image.id)

