import uuid
from typing import Any

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import ConflictError, NotFoundError, ValidationError
from app.flows.registry import FLOW_NAMES
from app.models import Category, Product
from app.repositories import CategoryRepository, ProductRepository
from app.utils.helpers import slugify


class ProductService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.products = ProductRepository(session)
        self.categories = CategoryRepository(session)

    # -- categories -------------------------------------------------
    async def list_categories(self, only_enabled: bool = True) -> list[Category]:
        return await self.categories.list_ordered(only_enabled)

    async def get_category(self, category_id: uuid.UUID) -> Category:
        category = await self.categories.get(category_id)
        if not category:
            raise NotFoundError("category_not_found")
        return category

    async def create_category(self, **values: Any) -> Category:
        values["slug"] = slugify(values.get("slug") or values["title"])
        if await self.categories.get_by_slug(values["slug"]):
            raise ConflictError("slug_exists")
        return await self.categories.create(**values)

    async def update_category(self, category_id: uuid.UUID, **values: Any) -> Category:
        category = await self.get_category(category_id)
        payload = {key: value for key, value in values.items() if value is not None}
        if "slug" in payload:
            payload["slug"] = slugify(payload["slug"])
            other = await self.categories.get_by_slug(payload["slug"])
            if other and other.id != category.id:
                raise ConflictError("slug_exists")
        return await self.categories.update(category, **payload)

    async def delete_category(self, category_id: uuid.UUID) -> None:
        category = await self.get_category(category_id)
        products = await self.products.list_filtered(category_id=category.id)
        if products:
            raise ConflictError("category_not_empty")
        await self.categories.delete(category)

    # -- products ---------------------------------------------------
    async def list_products(self, **filters: Any) -> list[Product]:
        return await self.products.list_filtered(**filters)

    async def list_by_category_slug(self, slug: str) -> list[Product]:
        return await self.products.list_by_category_slug(slug)

    async def search_products(self, page: int, size: int, **filters: Any) -> tuple[list[Product], int]:
        return await self.products.search(page, size, **filters)

    async def get_product(self, product_id: uuid.UUID) -> Product:
        product = await self.products.get(product_id)
        if not product:
            raise NotFoundError("product_not_found")
        return product

    async def get_available_product(self, product_id: uuid.UUID) -> Product:
        product = await self.get_product(product_id)
        if not product.enabled:
            raise ValidationError("product_unavailable")
        return product

    def _validate_flow(self, flow: str | None) -> None:
        if flow and flow not in FLOW_NAMES:
            raise ValidationError("unknown_flow")

    async def create_product(self, **values: Any) -> Product:
        self._validate_flow(values.get("flow"))
        values["slug"] = slugify(values.get("slug") or values["title"])
        if await self.products.get_by_slug(values["slug"]):
            values["slug"] = f"{values['slug']}-{uuid.uuid4().hex[:4]}"
        await self.get_category(values["category_id"])
        return await self.products.create(**values)

    async def update_product(self, product_id: uuid.UUID, **values: Any) -> Product:
        product = await self.get_product(product_id)
        payload = {key: value for key, value in values.items() if value is not None}
        self._validate_flow(payload.get("flow"))
        if "slug" in payload:
            payload["slug"] = slugify(payload["slug"])
            other = await self.products.get_by_slug(payload["slug"])
            if other and other.id != product.id:
                raise ConflictError("slug_exists")
        if "category_id" in payload:
            await self.get_category(payload["category_id"])
        return await self.products.update(product, **payload)

    async def delete_product(self, product_id: uuid.UUID) -> None:
        product = await self.get_product(product_id)
        await self.products.delete(product)

    async def duplicate_product(self, product_id: uuid.UUID) -> Product:
        product = await self.get_product(product_id)
        return await self.products.create(
            category_id=product.category_id,
            title=f"{product.title} (copy)",
            slug=f"{product.slug}-{uuid.uuid4().hex[:4]}",
            description=product.description,
            price=product.price,
            discount_price=product.discount_price,
            currency=product.currency,
            flow=product.flow,
            icon=product.icon,
            image=product.image,
            sort_order=product.sort_order,
            enabled=False,
            data=dict(product.data or {}),
        )
