import { prisma } from "@/core/prisma";
import { tracked } from "@/core/tracked";
import { AppError } from "@/core/AppError";
import type { ProductCustomizationMode } from "ttm-shared";

const INCLUDES = {
    category: true,
    station: true,
    stationLinks: {
        include: {
            station: {
                select: {
                    id: true,
                    name: true,
                    mainType: true,
                    active: true,
                }
            }
        }
    },
    logisticsRoute: { select: { id: true, name: true } },
    recipe: {
        include: {
            recipeIngredients: {
                include: { ingredient: true },
                orderBy: { ingredient: { name: "asc" as const } },
            },
        },
    },
    configurator: {
        select: {
            id: true,
            shopId: true,
            name: true,
            description: true,
            active: true,
            _count: {
                select: {
                    products: true,
                }
            },
            createdAt: true,
            updatedAt: true,
        },
    },
};

function resolveEffectiveProductCustomizationMode(product: {
    customizationMode?: ProductCustomizationMode | null
    recipeId?: string | null
    configuratorId?: string | null
}): ProductCustomizationMode {
    if (product.customizationMode === "CONFIGURATOR" || product.configuratorId) {
        return "CONFIGURATOR"
    }

    if (product.customizationMode === "RECIPE" || product.recipeId) {
        return "RECIPE"
    }

    return "NONE"
}

function buildProductPersistenceData(
    data: Record<string, any>,
    existing?: { recipeId?: string | null; configuratorId?: string | null; customizationMode?: ProductCustomizationMode | null } | null,
): Record<string, any> {
    let nextRecipeId = Object.prototype.hasOwnProperty.call(data, "recipeId")
        ? data.recipeId ?? null
        : existing?.recipeId ?? null
    let nextConfiguratorId = Object.prototype.hasOwnProperty.call(data, "configuratorId")
        ? data.configuratorId ?? null
        : existing?.configuratorId ?? null

    const requestedCustomizationMode = data.customizationMode ?? null

    if (requestedCustomizationMode === "NONE") {
        nextRecipeId = null
        nextConfiguratorId = null
    }

    if (requestedCustomizationMode === "RECIPE") {
        nextConfiguratorId = null
    }

    if (requestedCustomizationMode === "CONFIGURATOR") {
        nextRecipeId = null
    }

    if (nextRecipeId && nextConfiguratorId) {
        throw new AppError("Product cannot reference both a recipe and a configurator", 400)
    }

    const nextCustomizationMode = requestedCustomizationMode
        ?? (nextConfiguratorId ? "CONFIGURATOR" : nextRecipeId ? "RECIPE" : existing?.customizationMode ?? "NONE")

    if (nextCustomizationMode === "CONFIGURATOR" && !nextConfiguratorId) {
        throw new AppError("A configurator product must reference a configurator", 400)
    }

    return {
        ...data,
        recipeId: nextRecipeId,
        configuratorId: nextConfiguratorId,
        ...(Object.prototype.hasOwnProperty.call(data, "recipeId") ? { recipeId: nextRecipeId } : {}),
        ...(Object.prototype.hasOwnProperty.call(data, "configuratorId") ? { configuratorId: nextConfiguratorId } : {}),
        customizationMode: nextCustomizationMode,
    }
}

async function assertConfiguratorBelongsToShop(shopId: string, configuratorId: string | null | undefined) {
    if (!configuratorId) return

    const configurator = await prisma.productConfigurator.findFirst({
        where: { id: configuratorId, shopId, active: true },
        select: { id: true },
    })

    if (!configurator) {
        throw new AppError("Product configurator not found", 400)
    }
}

function serializeProduct(product: any) {
    const stationLinks = (product.stationLinks ?? [])
        .filter((link: any) => link.station?.active !== false)
        .map((link: any) => ({
            stationId: link.stationId,
            stationName: link.station?.name ?? "Station",
            mainType: link.station?.mainType ?? "PRODUCTION",
        }))

    if (product.station && !stationLinks.some((link: any) => link.stationId === product.station.id)) {
        stationLinks.push({
            stationId: product.station.id,
            stationName: product.station.name,
            mainType: product.station.mainType,
        })
    }

    return {
        ...product,
        customizationMode: resolveEffectiveProductCustomizationMode(product),
        configurator: product.configurator
            ? {
                ...product.configurator,
                productCount: product.configurator._count?.products ?? 0,
            }
            : null,
        stationLinks,
    }
}

async function syncProductionStationLink(shopId: string, productId: string, stationId: string | null | undefined) {
    await prisma.stationProductLink.deleteMany({
        where: {
            productId,
            station: {
                shopId,
                mainType: "PRODUCTION",
            }
        }
    })

    if (!stationId) return

    const station = await prisma.station.findFirst({
        where: {
            id: stationId,
            shopId,
            active: true,
            mainType: "PRODUCTION",
        },
        select: { id: true }
    })

    if (!station) {
        throw new AppError("Production station not found", 400)
    }

    await prisma.stationProductLink.create({
        data: {
            stationId,
            productId,
        }
    })
}

export const productService = {
    async findAll(
        shopId: string,
        orderBy: any,
        categoryId?: string
    ) {
        const products = await prisma.product.findMany({
            where: {
                shopId,
                active: true,
                ...(categoryId ? { categoryId } : {})
            },
            orderBy,
            include: INCLUDES
        });
        return products.map(serializeProduct)
    },

    /**
     * Retourne tous les produits actifs enrichis d'un compteur orderCount
     * basé sur le nombre de order_items associés (all-time).
     */
    async findAllWithPopularity(shopId: string, categoryId?: string) {
        const products = await prisma.product.findMany({
            where: {
                shopId,
                active: true,
                ...(categoryId ? { categoryId } : {})
            },
            include: INCLUDES
        });

        // Compter les commandes par produit (somme des quantités dans order_items)
        const counts = await prisma.orderItem.groupBy({
            by: ["productId"],
            where: {
                order: { shopId },
                productId: { in: products.map(p => p.id) },
                status: { not: "CANCELLED" }
            },
            _sum: { quantity: true }
        });

        const countMap = new Map(
            counts.map(c => [c.productId, c._sum.quantity ?? 0])
        );

        return products.map(p => ({
            ...serializeProduct(p),
            orderCount: countMap.get(p.id) ?? 0
        }));
    },

    async findById(shopId: string, id: string) {
        const product = await prisma.product.findFirst({
            where: { id, shopId },
            include: INCLUDES
        });
        return product ? serializeProduct(product) : null
    },

    async create(shopId: string, data: any, userId?: string, senderSocketId?: string) {
        const t = tracked({ shopId, entity: "Product", actorUserId: userId, senderSocketId });
        const persistedData = buildProductPersistenceData(data)

        await assertConfiguratorBelongsToShop(shopId, persistedData.configuratorId)

        const product = await t.create("product", { ...persistedData, shopId }, { include: INCLUDES },
            "product.created",
            {
                name: persistedData.name,
                price: persistedData.price,
                categoryId: persistedData.categoryId,
                customizationMode: persistedData.customizationMode,
                configuratorId: persistedData.configuratorId ?? null,
            }
        );

        if (persistedData.stationId !== undefined) {
            await syncProductionStationLink(shopId, product.id, persistedData.stationId ?? null)
        }

        return product;
    },

    async update(shopId: string, id: string, data: any, userId?: string, senderSocketId?: string) {
        const existing = await prisma.product.findFirst({
            where: { id, shopId }
        });

        if (!existing) {
            throw new AppError("Product not found", 404);
        }

        const persistedData = buildProductPersistenceData(data, existing)
        await assertConfiguratorBelongsToShop(shopId, persistedData.configuratorId)

        const t = tracked({ shopId, entity: "Product", actorUserId: userId, senderSocketId });

        const updated = await t.update("product", { id }, persistedData, { include: INCLUDES },
            "product.updated",
            { productId: id, changes: Object.keys(persistedData).filter(k => k !== "shopId"), newValues: persistedData }
        );

        if (Object.prototype.hasOwnProperty.call(persistedData, "stationId")) {
            await syncProductionStationLink(shopId, id, persistedData.stationId ?? null)
        }

        return updated;
    },

    async softDelete(shopId: string, id: string, userId?: string, senderSocketId?: string) {
        const product = await prisma.product.findFirst({
            where: { id, shopId }
        });

        if (!product) {
            throw new AppError("Product not found", 404);
        }

        const t = tracked({ shopId, entity: "Product", actorUserId: userId, senderSocketId });

        return t.update("product", { id }, { active: false }, undefined,
            "product.deleted",
            { productId: id, name: product.name }
        );
    }
};