from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException, status
from typing import List, Optional, Dict, Any
import re
from datetime import datetime

from app.models.catalog_models import Category, Product, ProductVariant, ProductImage, Collection
from app.schemas.catalog_schemas import (
    CategoryCreate, CategoryUpdate,
    ProductCreate, ProductUpdate,
    ProductVariantCreate, ProductVariantUpdate,
    ProductImageCreate, ProductImageUpdate,
    CollectionCreate, CollectionUpdate
)


# Вспомогательная функция для генерации slug
def generate_slug(name: str) -> str:
    # Преобразуем в нижний регистр
    slug = name.lower()
    # Заменяем пробелы на дефисы
    slug = re.sub(r'\s+', '-', slug)
    # Удаляем все символы, кроме букв, цифр и дефисов
    slug = re.sub(r'[^a-z0-9-]', '', slug)
    # Удаляем повторяющиеся дефисы
    slug = re.sub(r'-+', '-', slug)
    # Удаляем дефисы в начале и конце
    slug = slug.strip('-')
    
    return slug


# Функции для работы с коллекциями
def get_collection(db: Session, collection_id: int) -> Optional[Collection]:
    return db.query(Collection).filter(Collection.id == collection_id).first()


def get_collection_by_slug(db: Session, slug: str) -> Optional[Collection]:
    return db.query(Collection).filter(Collection.slug == slug).first()


def get_collections(
    db: Session, 
    skip: int = 0, 
    limit: int = 100, 
    is_active: Optional[bool] = True
) -> List[Collection]:
    query = db.query(Collection)
    
    if is_active is not None:
        query = query.filter(Collection.is_active == is_active)
    
    return query.offset(skip).limit(limit).all()


def create_collection(db: Session, collection: CollectionCreate) -> Collection:
    # Если slug не предоставлен, генерируем его из имени
    if not collection.slug:
        collection.slug = generate_slug(collection.name)
    
    # Проверяем, что коллекция с таким slug не существует
    if get_collection_by_slug(db, collection.slug):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Коллекция с таким slug уже существует"
        )
    
    # Создаем новую коллекцию
    db_collection = Collection(
        name=collection.name,
        slug=collection.slug,
        description=collection.description,
        is_active=collection.is_active
    )
    
    try:
        db.add(db_collection)
        db.commit()
        db.refresh(db_collection)
        return db_collection
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при создании коллекции"
        )


def update_collection(db: Session, collection_id: int, collection: CollectionUpdate) -> Collection:
    db_collection = get_collection(db, collection_id)
    if not db_collection:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Коллекция не найдена"
        )
    
    # Обновляем только предоставленные поля
    update_data = collection.dict(exclude_unset=True)
    
    # Если slug изменяется, проверяем его уникальность
    if "slug" in update_data and update_data["slug"] != db_collection.slug:
        if get_collection_by_slug(db, update_data["slug"]):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Коллекция с таким slug уже существует"
            )
    
    # Если имя изменяется и slug не предоставлен, генерируем новый slug
    if "name" in update_data and "slug" not in update_data:
        update_data["slug"] = generate_slug(update_data["name"])
        # Проверяем уникальность сгенерированного slug
        if get_collection_by_slug(db, update_data["slug"]) and get_collection_by_slug(db, update_data["slug"]).id != collection_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Коллекция с таким slug уже существует"
            )
    
    # Применяем обновления
    for key, value in update_data.items():
        setattr(db_collection, key, value)
    
    try:
        db.commit()
        db.refresh(db_collection)
        return db_collection
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при обновлении коллекции"
        )


def delete_collection(db: Session, collection_id: int) -> bool:
    db_collection = get_collection(db, collection_id)
    if not db_collection:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Коллекция не найдена"
        )
    
    # Проверяем, есть ли у коллекции продукты
    if db.query(Product).filter(Product.collection_id == collection_id).count() > 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Нельзя удалить коллекцию, у которой есть продукты"
        )
    
    try:
        db.delete(db_collection)
        db.commit()
        return True
    except Exception:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при удалении коллекции"
        )


# Функции для работы с категориями
def get_category(db: Session, category_id: int) -> Optional[Category]:
    return db.query(Category).filter(Category.id == category_id).first()


def get_category_by_slug(db: Session, slug: str) -> Optional[Category]:
    return db.query(Category).filter(Category.slug == slug).first()


def get_categories(
    db: Session, 
    skip: int = 0, 
    limit: int = 100, 
    parent_id: Optional[int] = None
) -> List[Category]:
    query = db.query(Category)
    
    if parent_id is not None:
        query = query.filter(Category.parent_id == parent_id)
    else:
        query = query.filter(Category.parent_id == None)
    
    return query.offset(skip).limit(limit).all()


def create_category(db: Session, category: CategoryCreate) -> Category:
    # Если slug не предоставлен, генерируем его из имени
    if not category.slug:
        category.slug = generate_slug(category.name)
    
    # Проверяем, что категория с таким slug не существует
    if get_category_by_slug(db, category.slug):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Категория с таким slug уже существует"
        )
    
    # Проверяем, что родительская категория существует, если указана
    if category.parent_id and not get_category(db, category.parent_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Родительская категория не найдена"
        )
    
    # Создаем новую категорию
    db_category = Category(
        name=category.name,
        slug=category.slug,
        description=category.description,
        parent_id=category.parent_id,
        is_active=category.is_active
    )
    
    try:
        db.add(db_category)
        db.commit()
        db.refresh(db_category)
        return db_category
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при создании категории"
        )


def update_category(db: Session, category_id: int, category: CategoryUpdate) -> Category:
    db_category = get_category(db, category_id)
    if not db_category:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Категория не найдена"
        )
    
    # Обновляем только предоставленные поля
    update_data = category.dict(exclude_unset=True)
    
    # Если slug изменяется, проверяем его уникальность
    if "slug" in update_data and update_data["slug"] != db_category.slug:
        if get_category_by_slug(db, update_data["slug"]):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Категория с таким slug уже существует"
            )
    
    # Если имя изменяется и slug не предоставлен, генерируем новый slug
    if "name" in update_data and "slug" not in update_data:
        update_data["slug"] = generate_slug(update_data["name"])
        # Проверяем уникальность сгенерированного slug
        if get_category_by_slug(db, update_data["slug"]) and get_category_by_slug(db, update_data["slug"]).id != category_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Категория с таким slug уже существует"
            )
    
    # Проверяем, что родительская категория существует, если указана
    if "parent_id" in update_data and update_data["parent_id"] and not get_category(db, update_data["parent_id"]):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Родительская категория не найдена"
        )
    
    # Проверяем, что категория не становится своим собственным родителем
    if "parent_id" in update_data and update_data["parent_id"] == category_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Категория не может быть своим собственным родителем"
        )
    
    # Применяем обновления
    for key, value in update_data.items():
        setattr(db_category, key, value)
    
    try:
        db.commit()
        db.refresh(db_category)
        return db_category
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при обновлении категории"
        )


def delete_category(db: Session, category_id: int) -> bool:
    db_category = get_category(db, category_id)
    if not db_category:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Категория не найдена"
        )
    
    # Проверяем, есть ли у категории подкатегории
    if db.query(Category).filter(Category.parent_id == category_id).count() > 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Нельзя удалить категорию, у которой есть подкатегории"
        )
    
    # Проверяем, есть ли у категории продукты
    if db.query(Product).filter(Product.category_id == category_id).count() > 0:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Нельзя удалить категорию, у которой есть продукты"
        )
    
    try:
        db.delete(db_category)
        db.commit()
        return True
    except Exception:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при удалении категории"
        )


# Функции для работы с продуктами
def get_product(db: Session, product_id: int) -> Optional[Product]:
    return db.query(Product).filter(Product.id == product_id).first()


def get_product_by_slug(db: Session, slug: str) -> Optional[Product]:
    return db.query(Product).filter(Product.slug == slug).first()


def get_products(
    db: Session, 
    skip: int = 0, 
    limit: int = 100, 
    category_id: Optional[int] = None,
    collection_id: Optional[int] = None,
    search: Optional[str] = None,
    min_price: Optional[float] = None,
    max_price: Optional[float] = None,
    is_active: Optional[bool] = True,
    include_variants: Optional[bool] = False
) -> List[Product]:
    query = db.query(Product)
    
    # Применяем фильтры
    if category_id:
        query = query.filter(Product.category_id == category_id)
    
    if collection_id:
        query = query.filter(Product.collection_id == collection_id)
    
    if search:
        query = query.filter(Product.name.ilike(f"%{search}%"))
    
    if is_active is not None:
        query = query.filter(Product.is_active == is_active)
    
    # Фильтрация по цене теперь должна быть через варианты продукта
    if min_price is not None or max_price is not None:
        query = query.join(ProductVariant)
        
        if min_price is not None:
            query = query.filter(ProductVariant.price >= min_price)
        
        if max_price is not None:
            query = query.filter(ProductVariant.price <= max_price)
    
    products = query.offset(skip).limit(limit).all()
    
    # Если нужно включить варианты, загружаем их для каждого продукта
    if include_variants:
        for product in products:
            product.variants = db.query(ProductVariant).filter(ProductVariant.product_id == product.id).all()
            product.images = db.query(ProductImage).filter(ProductImage.product_id == product.id).all()
    
    return products


def create_product(db: Session, product: ProductCreate) -> Product:
    # Если slug не предоставлен, генерируем его из имени
    if not product.slug:
        product.slug = generate_slug(product.name)
    
    # Проверяем, что продукт с таким slug не существует
    if get_product_by_slug(db, product.slug):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Продукт с таким slug уже существует"
        )
    
    # Проверяем, что категория существует
    if not get_category(db, product.category_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Категория не найдена"
        )
    
    # Проверяем, что коллекция существует, если указана
    if product.collection_id and not get_collection(db, product.collection_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Коллекция не найдена"
        )
    
    # Создаем новый продукт
    db_product = Product(
        name=product.name,
        slug=product.slug,
        description=product.description,
        is_active=product.is_active,
        category_id=product.category_id,
        collection_id=product.collection_id
    )
    
    try:
        db.add(db_product)
        db.commit()
        db.refresh(db_product)
        return db_product
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при создании продукта"
        )


def update_product(db: Session, product_id: int, product: ProductUpdate) -> Product:
    db_product = get_product(db, product_id)
    if not db_product:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Продукт не найден"
        )
    
    # Обновляем только предоставленные поля
    update_data = product.dict(exclude_unset=True)
    
    # Если slug изменяется, проверяем его уникальность
    if "slug" in update_data and update_data["slug"] != db_product.slug:
        if get_product_by_slug(db, update_data["slug"]):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Продукт с таким slug уже существует"
            )
    
    # Если имя изменяется и slug не предоставлен, генерируем новый slug
    if "name" in update_data and "slug" not in update_data:
        update_data["slug"] = generate_slug(update_data["name"])
        # Проверяем уникальность сгенерированного slug
        if get_product_by_slug(db, update_data["slug"]) and get_product_by_slug(db, update_data["slug"]).id != product_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Продукт с таким slug уже существует"
            )
    
    # Проверяем, что категория существует, если указана
    if "category_id" in update_data and update_data["category_id"] and not get_category(db, update_data["category_id"]):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Категория не найдена"
        )
    
    # Проверяем, что коллекция существует, если указана
    if "collection_id" in update_data and update_data["collection_id"] and not get_collection(db, update_data["collection_id"]):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Коллекция не найдена"
        )
    
    # Применяем обновления
    for key, value in update_data.items():
        setattr(db_product, key, value)
    
    try:
        db.commit()
        db.refresh(db_product)
        return db_product
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при обновлении продукта"
        )


def delete_product(db: Session, product_id: int) -> bool:
    db_product = get_product(db, product_id)
    if not db_product:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Продукт не найден"
        )
    
    try:
        db.delete(db_product)
        db.commit()
        return True
    except Exception:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при удалении продукта"
        )


# Функции для работы с вариантами продуктов
def get_product_variant(db: Session, variant_id: int) -> Optional[ProductVariant]:
    return db.query(ProductVariant).filter(ProductVariant.id == variant_id).first()


def get_product_variants(db: Session, product_id: int) -> List[ProductVariant]:
    return db.query(ProductVariant).filter(ProductVariant.product_id == product_id).all()


def create_product_variant(db: Session, variant: ProductVariantCreate) -> ProductVariant:
    # Проверяем, что продукт существует
    if not get_product(db, variant.product_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Продукт не найден"
        )
    
    # Проверяем, что вариант с таким SKU не существует
    if db.query(ProductVariant).filter(ProductVariant.sku == variant.sku).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Вариант с таким SKU уже существует"
        )
    
    # Создаем новый вариант продукта
    db_variant = ProductVariant(
        product_id=variant.product_id,
        name=variant.name,
        sku=variant.sku,
        price=variant.price,
        discount_price=variant.discount_price,
        stock=variant.stock,
        is_active=variant.is_active
    )
    
    try:
        db.add(db_variant)
        db.commit()
        db.refresh(db_variant)
        return db_variant
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при создании варианта продукта"
        )


def update_product_variant(db: Session, variant_id: int, variant: ProductVariantUpdate) -> ProductVariant:
    db_variant = get_product_variant(db, variant_id)
    if not db_variant:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Вариант продукта не найден"
        )
    
    # Обновляем только предоставленные поля
    update_data = variant.dict(exclude_unset=True)
    
    # Если product_id изменяется, проверяем, что продукт существует
    if "product_id" in update_data and update_data["product_id"] != db_variant.product_id:
        if not get_product(db, update_data["product_id"]):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Продукт не найден"
            )
    
    # Если SKU изменяется, проверяем его уникальность
    if "sku" in update_data and update_data["sku"] != db_variant.sku:
        if db.query(ProductVariant).filter(ProductVariant.sku == update_data["sku"]).first():
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Вариант с таким SKU уже существует"
            )
    
    # Применяем обновления
    for key, value in update_data.items():
        setattr(db_variant, key, value)
    
    try:
        db.commit()
        db.refresh(db_variant)
        return db_variant
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при обновлении варианта продукта"
        )


def delete_product_variant(db: Session, variant_id: int) -> bool:
    db_variant = get_product_variant(db, variant_id)
    if not db_variant:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Вариант продукта не найден"
        )
    
    try:
        db.delete(db_variant)
        db.commit()
        return True
    except Exception:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при удалении варианта продукта"
        )


# Функции для работы с изображениями продуктов
def get_product_image(db: Session, image_id: int) -> Optional[ProductImage]:
    return db.query(ProductImage).filter(ProductImage.id == image_id).first()


def get_product_images(db: Session, product_id: int) -> List[ProductImage]:
    return db.query(ProductImage).filter(ProductImage.product_id == product_id).all()


def create_product_image(db: Session, image: ProductImageCreate) -> ProductImage:
    # Проверяем, что продукт существует
    if not get_product(db, image.product_id):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Продукт не найден"
        )
    
    # Если изображение отмечено как основное, сбрасываем флаг у других изображений
    if image.is_primary:
        db.query(ProductImage).filter(
            ProductImage.product_id == image.product_id,
            ProductImage.is_primary == True
        ).update({"is_primary": False})
    
    # Создаем новое изображение продукта
    db_image = ProductImage(
        product_id=image.product_id,
        image_url=image.image_url,
        alt_text=image.alt_text,
        is_primary=image.is_primary
    )
    
    try:
        db.add(db_image)
        db.commit()
        db.refresh(db_image)
        return db_image
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при создании изображения продукта"
        )


def update_product_image(db: Session, image_id: int, is_primary: bool) -> ProductImage:
    db_image = get_product_image(db, image_id)
    if not db_image:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Изображение продукта не найдено"
        )
    
    # Если изображение отмечается как основное, сбрасываем флаг у других изображений
    if is_primary and not db_image.is_primary:
        db.query(ProductImage).filter(
            ProductImage.product_id == db_image.product_id,
            ProductImage.is_primary == True
        ).update({"is_primary": False})
    
    # Обновляем флаг
    db_image.is_primary = is_primary
    
    try:
        db.commit()
        db.refresh(db_image)
        return db_image
    except IntegrityError:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при обновлении изображения продукта"
        )


def delete_product_image(db: Session, image_id: int) -> bool:
    db_image = get_product_image(db, image_id)
    if not db_image:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Изображение продукта не найдено"
        )
    
    try:
        db.delete(db_image)
        db.commit()
        return True
    except Exception:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Ошибка при удалении изображения продукта"
        ) 