import { prisma } from "../../core/prisma";
import { AppError } from "../../core/AppError";
import { tracked, trackedTransaction } from "../../core/tracked";
import type {
    CreateStationInput,
    ProductionStationStatsDto,
    ReplaceStationProductDepositLinksInput,
    SetStationProductsInput,
    SetStationShopUsersInput,
    StationDetailDto,
    StationProductionWorkflowDto,
    StationProductionWorkflowInput,
    StationProductDepositLinkDto,
    StationListItemDto,
    StationQueueStatDto,
    UpdateStationInput
} from "ttm-shared";
import {
    DEFAULT_PRODUCTION_WORKFLOW_STEPS,
    toProductionWorkflowDto,
} from "./production-workflow";
import { buildOrderItemProductionDisplayData } from "./station-stock.service";
import { buildShopUserPermissionSnapshot } from "../shop-user/shop-user-permissions.service";

/**
 * Construit un identifiant court de commande lisible.
 * Préfixe avec un tag court de la station source pour distinguer les caisses.
 * Ex: "Caisse 1" → "C1-#3", "Caisse de Choute" → "Ch-#2", "Caisse de IClou" → "IC-#1"
 */
function buildOrderShortId(
    orderId: string,
    sessionOrderNumber: number | null | undefined,
    stationName: string | null | undefined
): string {
    if (sessionOrderNumber == null) return orderId.slice(0, 8);
    if (!stationName) return `#${sessionOrderNumber}`;

    // S'il y a un chiffre dans le nom → première lettre + chiffre (ex: "Caisse 1" → "C1")
    const digitMatch = stationName.match(/\d+/);
    if (digitMatch) {
        return `${stationName.charAt(0).toUpperCase()}${digitMatch[0]}-#${sessionOrderNumber}`;
    }

    // Sinon, prendre le dernier mot significatif (ignorer les mots de liaison)
    const fillers = new Set(["de", "du", "des", "la", "le", "les", "l", "d", "a", "au", "aux"]);
    const words = stationName.split(/[\s\-_']+/).filter(w => w.length > 0);
    const meaningful = words.filter(w => !fillers.has(w.toLowerCase()));

    // Utiliser le dernier mot significatif, tronqué à 2 caractères
    const lastWord = meaningful.length > 0 ? meaningful[meaningful.length - 1] : words[words.length - 1] ?? "";
    const tag = lastWord.slice(0, 2);
    // Première lettre majuscule, deuxième minuscule pour la lisibilité
    const formatted = tag.charAt(0).toUpperCase() + (tag.charAt(1)?.toLowerCase() ?? "");

    return `${formatted}-#${sessionOrderNumber}`;
}

function startOfLocalDay(date = new Date()): Date {
    const value = new Date(date)
    value.setHours(0, 0, 0, 0)
    return value
}

function diffInSeconds(from: Date, to: Date): number {
    return Math.max(0, Math.round((to.getTime() - from.getTime()) / 1000))
}

function averageSeconds(values: Array<number | null | undefined>): number | null {
    const filtered = values.filter((value): value is number => typeof value === "number" && Number.isFinite(value) && value >= 0)
    if (filtered.length === 0) return null
    return Math.round(filtered.reduce((sum, value) => sum + value, 0) / filtered.length)
}

function formatHourLabel(date: Date): string {
    return `${date.getHours().toString().padStart(2, "0")}h`
}

function roundToSingleDecimal(value: number): number {
    return Math.round(value * 10) / 10
}

function toListItem(station: any): StationListItemDto {
    return {
        id: station.id,
        name: station.name,
        mainType: station.mainType,
        active: station.active,
        revision: station.revision,
        createdAt: station.createdAt.toISOString()
    }
}

function mapShopUser(row: any) {
    const { permissionOverrides, effectivePermissions } = buildShopUserPermissionSnapshot(row.shopUser)

    return {
        id: row.shopUser.id,
        email: row.shopUser.email,
        displayName: row.shopUser.displayName,
        username: row.shopUser.username,
        role: row.shopUser.role,
        active: row.shopUser.active,
        mustChangePassword: row.shopUser.mustChangePassword,
        permissionOverrides,
        effectivePermissions,
        createdAt: row.shopUser.createdAt.toISOString()
    }
}

function mapLinkedProduct(product: any) {
    return {
        id: product.id,
        name: product.name,
        categoryId: product.categoryId,
        categoryName: product.category?.name ?? "",
    }
}

function mapStationProductDepositLink(link: any): StationProductDepositLinkDto {
    return {
        id: link.id,
        stationId: link.stationId,
        productId: link.productId,
        depositDefinitionId: link.depositDefinitionId,
        depositLabel: link.depositDefinition?.label ?? "Consigne",
        depositAmount: link.depositDefinition?.amount ?? 0,
        defaultQuantity: link.defaultQuantity,
        displayOrder: link.displayOrder,
        applicationMode: link.applicationMode,
        isRemovableByCashier: link.isRemovableByCashier,
        autoAddEnabled: link.autoAddEnabled,
    }
}

async function requireStationProductContext(tx: any, shopId: string, stationId: string, productId: string) {
    const station = await tx.station.findFirst({
        where: { id: stationId, shopId },
        select: { id: true, mainType: true, name: true }
    })

    if (!station) {
        throw new AppError("Station not found", 404)
    }

    const supportsProductLinks = station.mainType === "CASH" || station.mainType === "PRODUCTION"
    if (!supportsProductLinks) {
        throw new AppError("This station type does not support product deposit links", 400)
    }

    const product = await tx.product.findFirst({
        where: {
            id: productId,
            shopId,
            active: true,
        },
        select: {
            id: true,
            name: true,
            stationId: true,
            stationLinks: {
                where: { stationId },
                select: { id: true }
            }
        }
    })

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

    const isLinkedToStation = product.stationId === stationId || (product.stationLinks?.length ?? 0) > 0
    if (!isLinkedToStation) {
        throw new AppError("Product is not linked to this station", 400)
    }

    return { station, product }
}

function toDetail(station: any): StationDetailDto {
    const linkedProducts = new Map<string, ReturnType<typeof mapLinkedProduct>>()

    for (const link of station.productLinks ?? []) {
        if (link.product?.active === false || !link.product) continue
        linkedProducts.set(link.product.id, mapLinkedProduct(link.product))
    }

    for (const product of station.products ?? []) {
        if (product?.active === false || !product) continue
        if (!linkedProducts.has(product.id)) {
            linkedProducts.set(product.id, mapLinkedProduct(product))
        }
    }

    return {
        ...toListItem(station),
        shopId: station.shopId,
        shopUsers: (station.shopUsers ?? []).map(mapShopUser),
        linkedProducts: Array.from(linkedProducts.values()).sort((a, b) => a.name.localeCompare(b.name)),
        productionWorkflow: toProductionWorkflowDto(station),
    }
}

const DETAIL_INCLUDE = {
    shopUsers: {
        include: {
            shopUser: true
        }
    },
    products: {
        where: { active: true },
        include: {
            category: true,
        }
    },
    productLinks: {
        include: {
            product: {
                include: {
                    category: true,
                }
            }
        }
    },
    productionWorkflowSteps: {
        orderBy: { position: "asc" as const }
    }
}

function resolveRelevantTransportStep(
    steps: Array<{ position: number; stepType: string; handlerMode: string | null }> | undefined,
    currentStepPosition: number | null | undefined
) {
    if (!steps || steps.length === 0) return null;

    const currentPos = currentStepPosition ?? 0;
    const currentStep = steps.find((s) => s.position === currentPos) ?? null;
    if (currentStep?.stepType === "TRANSPORT") return currentStep;

    return steps.find((s) => s.position > currentPos && s.stepType === "TRANSPORT") ?? null;
}

function isProductionRelevantOrderItem(
    item: {
        currentStepStationId?: string | null;
        logisticsRouteId?: string | null;
        product?: { stationId?: string | null } | null;
    },
    stationId: string
) {
    if (item.currentStepStationId === stationId) return true;
    if (!item.logisticsRouteId && item.product?.stationId === stationId) return true;
    return false;
}

function summarizeOrderProgress(
    orderItems: Array<{
        status: string;
        quantity: number;
        currentStepStationId?: string | null;
        logisticsRouteId?: string | null;
        product?: { stationId?: string | null } | null;
    }> | undefined,
    options?: { stationId?: string }
) {
    const rawItems = orderItems ?? [];
    const items = options?.stationId
        ? rawItems.filter((item) => isProductionRelevantOrderItem(item, options.stationId!))
        : rawItems;
    const sumByStatus = (status: string) =>
        items
            .filter((item) => item.status === status)
            .reduce((sum, item) => sum + item.quantity, 0);

    const count_ready = sumByStatus("READY");
    const count_preparing = sumByStatus("PREPARING");
    const count_pending = sumByStatus("PENDING");
    return {
        orderReadyQuantity: sumByStatus("READY"),
        orderPreparingQuantity: sumByStatus("PREPARING"),
        orderPendingQuantity: sumByStatus("PENDING"),
        orderPickedUpQuantity: sumByStatus("PICKED_UP"),
        orderTotalActiveQuantity: count_ready + count_preparing + count_pending,
        orderTotalQuantity: items.reduce((sum, item) => sum + item.quantity, 0),
    };
}

export const stationService = {
    async findAll(shopId: string): Promise<StationListItemDto[]> {
        const stations = await prisma.station.findMany({
            where: {
                shopId,
                active: true
            },
            orderBy: {
                name: "asc"
            }
        });

        return stations.map(toListItem)
    },

    async findById(shopId: string, id: string): Promise<StationDetailDto | null> {
        const station = await prisma.station.findFirst({
            where: {
                id,
                shopId
            },
            include: DETAIL_INCLUDE
        });

        return station ? toDetail(station) : null
    },

    async create(
        shopId: string,
        input: CreateStationInput,
        actorUserId?: string,
        senderSocketId?: string
    ): Promise<StationDetailDto> {
        const existing = await prisma.station.findFirst({
            where: {
                shopId,
                name: input.name,
                active: true
            }
        })

        if (existing) {
            throw new AppError("Station name already exists", 400)
        }

        const t = tracked({ shopId, entity: "Station", actorUserId, senderSocketId })

        const station = await t.create<any>(
            "station",
            {
                shopId,
                name: input.name,
                mainType: input.mainType,
                productionWorkflowEnabled: input.mainType === "PRODUCTION",
                productionWorkflowRevision: 1,
                ...(input.mainType === "PRODUCTION"
                    ? {
                        productionWorkflowSteps: {
                            create: DEFAULT_PRODUCTION_WORKFLOW_STEPS.map((step) => ({
                                code: step.code,
                                label: step.label,
                                color: step.color,
                                position: step.position,
                                systemRole: step.systemRole,
                                active: step.active,
                            }))
                        }
                    }
                    : {})
            },
            { include: DETAIL_INCLUDE },
            "station.created",
            {
                name: input.name,
                mainType: input.mainType,
                active: true,
                productionWorkflow: input.mainType === "PRODUCTION"
                    ? {
                        enabled: true,
                        revision: 1,
                        steps: DEFAULT_PRODUCTION_WORKFLOW_STEPS,
                    }
                    : null,
            }
        )

        return toDetail(station)
    },

    async getProductionWorkflow(shopId: string, stationId: string): Promise<StationProductionWorkflowDto> {
        const station = await prisma.station.findFirst({
            where: { id: stationId, shopId },
            include: { productionWorkflowSteps: { orderBy: { position: "asc" } } },
        })

        if (!station) {
            throw new AppError("Station not found", 404)
        }
        if (station.mainType !== "PRODUCTION") {
            throw new AppError("Station is not a production station", 400)
        }

        return toProductionWorkflowDto(station) ?? {
            enabled: true,
            revision: 1,
            steps: DEFAULT_PRODUCTION_WORKFLOW_STEPS,
        }
    },

    async updateProductionWorkflow(
        shopId: string,
        stationId: string,
        input: StationProductionWorkflowInput,
        actorUserId?: string,
        senderSocketId?: string,
    ): Promise<StationProductionWorkflowDto> {
        return trackedTransaction(shopId, "Station", async (t, tx: any) => {
            const station = await tx.station.findFirst({
                where: { id: stationId, shopId },
                include: { productionWorkflowSteps: { orderBy: { position: "asc" } } },
            })

            if (!station) {
                throw new AppError("Station not found", 404)
            }
            if (station.mainType !== "PRODUCTION") {
                throw new AppError("Station is not a production station", 400)
            }

            await tx.stationProductionWorkflowStep.deleteMany({ where: { stationId } })

            await tx.station.update({
                where: { id: stationId },
                data: {
                    productionWorkflowEnabled: input.enabled,
                    productionWorkflowRevision: { increment: 1 },
                    revision: { increment: 1 },
                },
            })

            await tx.stationProductionWorkflowStep.createMany({
                data: input.steps.map((step) => ({
                    stationId,
                    code: step.code,
                    label: step.label,
                    color: step.color,
                    position: step.position,
                    systemRole: step.systemRole,
                    active: step.active,
                })),
            })

            const updated = await tx.station.findUnique({
                where: { id: stationId },
                include: { productionWorkflowSteps: { orderBy: { position: "asc" } } },
            })

            const workflow = toProductionWorkflowDto(updated) ?? {
                enabled: input.enabled,
                revision: (station.productionWorkflowRevision ?? 1) + 1,
                steps: input.steps,
            }

            await t.emit(
                "station.updated",
                {
                    changes: ["productionWorkflow"],
                    productionWorkflow: workflow,
                },
                stationId,
                updated?.revision ?? (station.revision + 1)
            )

            return workflow
        }, { actorUserId, senderSocketId })
    },

    async update(
        shopId: string,
        id: string,
        data: UpdateStationInput,
        actorUserId?: string,
        senderSocketId?: string
    ): Promise<StationDetailDto> {
        const existing = await prisma.station.findFirst({
            where: {
                id,
                shopId
            }
        });

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

        if (data.name && data.name !== existing.name) {
            const duplicate = await prisma.station.findFirst({
                where: {
                    shopId,
                    name: data.name,
                    active: true,
                    NOT: { id }
                }
            })

            if (duplicate) {
                throw new AppError("Station name already exists", 400)
            }
        }

        const t = tracked({ shopId, entity: "Station", actorUserId, senderSocketId })

        const station = await t.update<any>(
            "station",
            { id },
            data,
            { include: DETAIL_INCLUDE },
            "station.updated",
            {
                changes: Object.keys(data),
                ...data
            }
        )

        return toDetail(station)
    },

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

        if (!station) {
            throw new AppError("Station not found", 404)
        }

        const t = tracked({ shopId, entity: "Station", actorUserId, senderSocketId })

        await t.update(
            "station",
            { id },
            { active: false },
            undefined,
            "station.deleted",
            { id, name: station.name }
        )
    },

    async getStationStats(shopId: string, stationId: string): Promise<StationQueueStatDto[]> {
        const stats = await prisma.orderItem.groupBy({
            by: ["status"],
            where: {
                order: { shopId },
                product: { stationId },
                status: {
                    notIn: ["DELIVERED", "CANCELLED"]
                }
            },
            _count: true
        });

        return stats.map((row) => ({
            status: row.status,
            count: row._count
        }))
    },

    async getProductionStationStats(shopId: string, stationId: string): Promise<ProductionStationStatsDto> {
        const station = await prisma.station.findFirst({
            where: { id: stationId, shopId },
            select: { id: true, name: true, mainType: true }
        })

        if (!station) {
            throw new AppError("Station not found", 404)
        }

        if (station.mainType !== "PRODUCTION") {
            throw new AppError("Station is not a production station", 400)
        }

        const periodStart = startOfLocalDay()
        const periodEnd = new Date()

        const items = await prisma.orderItem.findMany({
            where: {
                order: {
                    shopId,
                    status: { not: "DRAFT" },
                },
                lifecycleEvents: {
                    some: {
                        eventType: "READY",
                        stationId,
                        occurredAt: {
                            gte: periodStart,
                            lte: periodEnd,
                        },
                    },
                },
            },
            select: {
                id: true,
                name: true,
                quantity: true,
                productId: true,
                orderId: true,
                createdAt: true,
                product: {
                    select: {
                        id: true,
                        name: true,
                    },
                },
                order: {
                    select: {
                        id: true,
                        createdById: true,
                        createdBy: {
                            select: {
                                id: true,
                                displayName: true,
                                username: true,
                            },
                        },
                    },
                },
                lifecycleEvents: {
                    where: {
                        stationId,
                    },
                    orderBy: {
                        occurredAt: "asc",
                    },
                    select: {
                        eventType: true,
                        occurredAt: true,
                    },
                },
            },
        })

        type CompletionRecord = {
            itemId: string
            orderId: string
            productId: string
            productName: string
            quantity: number
            cashierId: string | null
            cashierName: string
            readyAt: Date
            leadTimeSec: number
            prepTimeSec: number | null
        }

        const completions: CompletionRecord[] = []

        for (const item of items) {
            const readyEvents = item.lifecycleEvents.filter((event: any) =>
                event.eventType === "READY"
                && event.occurredAt >= periodStart
                && event.occurredAt <= periodEnd
            )

            const readyEvent = readyEvents[readyEvents.length - 1]
            if (!readyEvent) continue

            const acknowledgedEvent = [...item.lifecycleEvents]
                .filter((event: any) => event.eventType === "ACKNOWLEDGED" && event.occurredAt <= readyEvent.occurredAt)
                .pop()

            completions.push({
                itemId: item.id,
                orderId: item.orderId,
                productId: item.product.id,
                productName: item.product.name,
                quantity: item.quantity,
                cashierId: item.order.createdBy?.id ?? item.order.createdById ?? null,
                cashierName: item.order.createdBy?.displayName ?? item.order.createdBy?.username ?? "Inconnu",
                readyAt: readyEvent.occurredAt,
                leadTimeSec: diffInSeconds(item.createdAt, readyEvent.occurredAt),
                prepTimeSec: acknowledgedEvent ? diffInSeconds(acknowledgedEvent.occurredAt, readyEvent.occurredAt) : null,
            })
        }

        const totalUnits = completions.reduce((sum, completion) => sum + completion.quantity, 0)
        const totalOrders = new Set(completions.map((completion) => completion.orderId)).size
        const uniqueProductCount = new Set(completions.map((completion) => completion.productId)).size

        const hourlyMap = new Map<string, { hourLabel: string; completedLineCount: number; completedUnitCount: number }>()
        const cashierMap = new Map<string, {
            cashierId: string | null
            cashierName: string
            completedLineCount: number
            completedUnitCount: number
            orderIds: Set<string>
            productIds: Set<string>
            leadTimes: number[]
        }>()
        const productMap = new Map<string, {
            productId: string
            productName: string
            completedLineCount: number
            completedUnitCount: number
            orderIds: Set<string>
            leadTimes: number[]
        }>()

        for (const completion of completions) {
            const hourLabel = formatHourLabel(completion.readyAt)
            const hourlyEntry = hourlyMap.get(hourLabel) ?? {
                hourLabel,
                completedLineCount: 0,
                completedUnitCount: 0,
            }
            hourlyEntry.completedLineCount += 1
            hourlyEntry.completedUnitCount += completion.quantity
            hourlyMap.set(hourLabel, hourlyEntry)

            const cashierKey = completion.cashierId ?? `unknown:${completion.cashierName}`
            const cashierEntry = cashierMap.get(cashierKey) ?? {
                cashierId: completion.cashierId,
                cashierName: completion.cashierName,
                completedLineCount: 0,
                completedUnitCount: 0,
                orderIds: new Set<string>(),
                productIds: new Set<string>(),
                leadTimes: [],
            }
            cashierEntry.completedLineCount += 1
            cashierEntry.completedUnitCount += completion.quantity
            cashierEntry.orderIds.add(completion.orderId)
            cashierEntry.productIds.add(completion.productId)
            cashierEntry.leadTimes.push(completion.leadTimeSec)
            cashierMap.set(cashierKey, cashierEntry)

            const productEntry = productMap.get(completion.productId) ?? {
                productId: completion.productId,
                productName: completion.productName,
                completedLineCount: 0,
                completedUnitCount: 0,
                orderIds: new Set<string>(),
                leadTimes: [],
            }
            productEntry.completedLineCount += 1
            productEntry.completedUnitCount += completion.quantity
            productEntry.orderIds.add(completion.orderId)
            productEntry.leadTimes.push(completion.leadTimeSec)
            productMap.set(completion.productId, productEntry)
        }

        const hourly = [...hourlyMap.values()].sort((a, b) => a.hourLabel.localeCompare(b.hourLabel, "fr"))
        const peakHour = [...hourly].sort((a, b) => b.completedUnitCount - a.completedUnitCount || b.completedLineCount - a.completedLineCount)[0] ?? null

        const cashiers = [...cashierMap.values()]
            .map((entry) => ({
                cashierId: entry.cashierId,
                cashierName: entry.cashierName,
                completedOrderCount: entry.orderIds.size,
                completedLineCount: entry.completedLineCount,
                completedUnitCount: entry.completedUnitCount,
                uniqueProductCount: entry.productIds.size,
                shareOfUnits: totalUnits > 0 ? roundToSingleDecimal((entry.completedUnitCount / totalUnits) * 100) : 0,
                avgLeadTimeSec: averageSeconds(entry.leadTimes),
            }))
            .sort((a, b) =>
                b.completedUnitCount - a.completedUnitCount
                || b.completedOrderCount - a.completedOrderCount
                || a.cashierName.localeCompare(b.cashierName, "fr")
            )

        const topProducts = [...productMap.values()]
            .map((entry) => ({
                productId: entry.productId,
                productName: entry.productName,
                completedOrderCount: entry.orderIds.size,
                completedLineCount: entry.completedLineCount,
                completedUnitCount: entry.completedUnitCount,
                avgLeadTimeSec: averageSeconds(entry.leadTimes),
            }))
            .sort((a, b) =>
                b.completedUnitCount - a.completedUnitCount
                || b.completedOrderCount - a.completedOrderCount
                || a.productName.localeCompare(b.productName, "fr")
            )

        const topCashier = cashiers[0] ?? null

        return {
            stationId: station.id,
            stationName: station.name,
            generatedAt: periodEnd.toISOString(),
            periodStart: periodStart.toISOString(),
            periodEnd: periodEnd.toISOString(),
            periodLabel: "Aujourd'hui",
            overview: {
                completedOrderCount: totalOrders,
                completedLineCount: completions.length,
                completedUnitCount: totalUnits,
                uniqueProductCount,
                avgLeadTimeSec: averageSeconds(completions.map((completion) => completion.leadTimeSec)),
                avgPrepTimeSec: averageSeconds(completions.map((completion) => completion.prepTimeSec)),
                peakHourLabel: peakHour?.hourLabel ?? null,
                peakHourUnitCount: peakHour?.completedUnitCount ?? 0,
                topCashierName: topCashier?.cashierName ?? null,
                topCashierUnitCount: topCashier?.completedUnitCount ?? 0,
                topCashierOrderCount: topCashier?.completedOrderCount ?? 0,
            },
            cashiers,
            topProducts,
            hourly,
        }
    },

    async getQueue(shopId: string, stationId: string) {
        return prisma.orderItem.findMany({
            where: {
                status: {
                    notIn: ["DELIVERED", "CANCELLED"]
                },
                order: {
                    shopId,
                    status: {
                        notIn: ["DRAFT", "CANCELLED"]
                    }
                },
                product: {
                    stationId
                }
            },
            include: {
                order: true,
                product: true
            },
            orderBy: {
                createdAt: "asc"
            }
        });
    },

    /**
     * Queue enrichie pour le KDS : items groupés par commande source
     * avec infos station d'origine (la caisse qui a passé la commande)
     */
    async getProductionQueue(shopId: string, stationId: string) {
        const items = await prisma.orderItem.findMany({
            where: {
                status: {
                    notIn: ["DELIVERED", "CANCELLED"]
                },
                order: {
                    shopId,
                    status: {
                        notIn: ["DRAFT", "CANCELLED"]
                    },
                    OR: [
                        { cashSessionId: null },
                        { cashSession: { status: "OPEN" } },
                    ],
                },
                OR: [
                    { currentStepStationId: stationId },
                    {
                        logisticsRouteId: null,
                        product: { stationId }
                    }
                ]
            },
            include: {
                order: {
                    include: {
                        station: true,
                        createdBy: true,
                        orderItems: {
                            where: {
                                status: { notIn: ["CANCELLED", "DELIVERED"] }
                            },
                            select: {
                                status: true,
                                quantity: true,
                                currentStepStationId: true,
                                logisticsRouteId: true,
                                product: {
                                    select: {
                                        stationId: true,
                                    }
                                },
                            }
                        }
                    }
                },
                product: true,
                takenBy: true,
                pickedUpBy: true,
                customizations: true,
                lifecycleEvents: {
                    orderBy: { occurredAt: "asc" }
                },
                logisticsRoute: {
                    include: {
                        steps: {
                            select: {
                                position: true,
                                stepType: true,
                                handlerMode: true,
                            },
                            orderBy: { position: "asc" }
                        }
                    }
                },
            },
            orderBy: {
                createdAt: "asc"
            }
        });

        // Grouper par orderId
        const groupMap = new Map<string, {
            orderId: string
            orderShortId: string
            createdByName: string | null
            sourceStationId: string | null
            sourceStationName: string | null
            createdAt: string
            items: any[]
        }>();

        for (const item of items) {
            if (!groupMap.has(item.orderId)) {
                const orderNum = (item.order as any).sessionOrderNumber
                const stationName = (item.order as any).station?.name ?? null
                const createdByName = (item.order as any).createdBy?.displayName ?? (item.order as any).createdBy?.username ?? null
                groupMap.set(item.orderId, {
                    orderId: item.orderId,
                    orderShortId: buildOrderShortId(item.orderId, orderNum, stationName),
                    createdByName,
                    sourceStationId: item.order.stationId ?? null,
                    sourceStationName: stationName,
                    createdAt: item.order.createdAt.toISOString(),
                    items: []
                });
            }

            const orderNum = (item.order as any).sessionOrderNumber
            const stationName = (item.order as any).station?.name ?? null
            const createdByName = (item.order as any).createdBy?.displayName ?? (item.order as any).createdBy?.username ?? null
            const currentHandlerMode = resolveRelevantTransportStep(
                (item as any).logisticsRoute?.steps,
                (item as any).currentStepPosition ?? null
            )?.handlerMode ?? null
            const orderProgress = summarizeOrderProgress((item.order as any).orderItems, { stationId })
            const productionDisplay = buildOrderItemProductionDisplayData({
                status: item.status,
                configurationSnapshot: item.configurationSnapshot ?? null,
                productionOverrideSnapshot: (item as any).productionOverrideSnapshot ?? null,
            })
            groupMap.get(item.orderId)!.items.push({
                id: item.id,
                name: item.name,
                productId: item.productId,
                quantity: item.quantity,
                unitPrice: item.unitPrice,
                status: item.status,
                orderId: item.orderId,
                orderShortId: buildOrderShortId(item.orderId, orderNum, stationName),
                createdByName,
                sourceStationId: item.order.stationId ?? null,
                sourceStationName: stationName,
                createdAt: item.createdAt.toISOString(),
                takenByName: item.takenBy?.displayName ?? item.takenBy?.username ?? null,
                pickedUpByName: (() => {
                    const events = (item as any).lifecycleEvents ?? []
                    const lastPick = [...events].reverse().find((e: any) => e.eventType === "PICKED_UP")
                    return lastPick?.actorName
                        ?? (item as any).pickedUpBy?.displayName
                        ?? (item as any).pickedUpBy?.username
                        ?? null
                })(),
                currentHandlerMode,
                ...orderProgress,
                recipeId: item.recipeId ?? null,
                recipeName: item.recipeName ?? null,
                configurationLabel: item.configurationLabel ?? null,
                configurationSnapshot: item.configurationSnapshot ?? null,
                productionConfigurationSnapshot: productionDisplay.productionConfigurationSnapshot,
                stockCoverage: productionDisplay.stockCoverage,
                customizations: ((item as any).customizations ?? []).map((c: any) => ({
                    id: c.id,
                    ingredientId: c.ingredientId,
                    ingredientName: c.ingredientName,
                    action: c.action,
                    quantity: c.quantity,
                    extraPrice: c.extraPrice,
                })),
                lifecycleEvents: ((item as any).lifecycleEvents ?? []).map((e: any) => ({
                    id: e.id,
                    eventType: e.eventType,
                    fromStatus: e.fromStatus,
                    toStatus: e.toStatus,
                    fromProductionStepIndex: e.fromProductionStepIndex ?? null,
                    toProductionStepIndex: e.toProductionStepIndex ?? null,
                    fromProductionStepCode: e.fromProductionStepCode ?? null,
                    toProductionStepCode: e.toProductionStepCode ?? null,
                    actorId: e.actorId,
                    actorName: e.actorName,
                    stationId: e.stationId,
                    occurredAt: e.occurredAt.toISOString(),
                })),
                productionStep: {
                    index: (item as any).productionStepIndex ?? null,
                    code: (item as any).productionStepCode ?? null,
                    label: (item as any).productionStepLabel ?? null,
                    workflowRevision: (item as any).productionWorkflowRevision ?? null,
                },
                productionWorkflowSnapshot: (item as any).productionWorkflowSnapshot ?? null,
            });
        }

        return Array.from(groupMap.values()).sort(
            (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
        );
    },

    async setShopUsers(
        shopId: string,
        stationId: string,
        input: SetStationShopUsersInput,
        actorUserId?: string,
        senderSocketId?: string
    ): Promise<StationDetailDto> {
        const station = await prisma.station.findFirst({
            where: { id: stationId, shopId }
        })

        if (!station) {
            throw new AppError("Station not found", 404)
        }

        const uniqueShopUserIds = [...new Set(input.shopUserIds)]

        if (uniqueShopUserIds.length > 0) {
            const users = await prisma.shopUser.findMany({
                where: {
                    id: { in: uniqueShopUserIds },
                    shopId,
                    active: true
                },
                select: { id: true }
            })

            if (users.length !== uniqueShopUserIds.length) {
                throw new AppError("One or more shop users are invalid for this shop", 400)
            }
        }

        return trackedTransaction(shopId, "Station", async (t, tx: any) => {
            await tx.shopUserStation.deleteMany({
                where: { stationId }
            })

            if (uniqueShopUserIds.length > 0) {
                await tx.shopUserStation.createMany({
                    data: uniqueShopUserIds.map((shopUserId) => ({ stationId, shopUserId })),
                    skipDuplicates: true
                })
            }

            const stationWithUsersBeforeRevision = await tx.station.findFirst({
                where: { id: stationId, shopId },
                include: DETAIL_INCLUDE
            })

            const preview = stationWithUsersBeforeRevision ? toDetail(stationWithUsersBeforeRevision) : null

            const updated = await t.update<any>(
                "station",
                { id: stationId },
                {},
                { include: DETAIL_INCLUDE },
                "station.updated",
                {
                    changes: ["shopUsers"],
                    shopUserIds: uniqueShopUserIds,
                    shopUsers: preview?.shopUsers ?? [],
                    name: preview?.name ?? station.name,
                    mainType: preview?.mainType ?? station.mainType,
                    active: preview?.active ?? station.active
                }
            )

            return toDetail(updated)
        }, { actorUserId, senderSocketId })
    },

    async setStationProducts(
        shopId: string,
        stationId: string,
        input: SetStationProductsInput,
        actorUserId?: string,
        senderSocketId?: string
    ): Promise<StationDetailDto> {
        const station = await prisma.station.findFirst({
            where: { id: stationId, shopId }
        })

        if (!station) {
            throw new AppError("Station not found", 404)
        }

        const uniqueProductIds = [...new Set(input.productIds)]
        const supportsProductLinks = station.mainType === "CASH" || station.mainType === "PRODUCTION"

        if (!supportsProductLinks && uniqueProductIds.length > 0) {
            throw new AppError("This station type does not support product links", 400)
        }

        if (uniqueProductIds.length > 0) {
            const products = await prisma.product.findMany({
                where: {
                    id: { in: uniqueProductIds },
                    shopId,
                    active: true,
                },
                select: { id: true }
            })

            if (products.length !== uniqueProductIds.length) {
                throw new AppError("One or more products are invalid for this shop", 400)
            }
        }

        if (station.mainType === "PRODUCTION" && uniqueProductIds.length > 0) {
            const conflicts = await prisma.stationProductLink.findMany({
                where: {
                    productId: { in: uniqueProductIds },
                    stationId: { not: stationId },
                    station: {
                        shopId,
                        mainType: "PRODUCTION",
                        active: true,
                    }
                },
                include: {
                    product: { select: { id: true, name: true } },
                    station: { select: { id: true, name: true } }
                }
            })

            if (conflicts.length > 0) {
                const details = conflicts
                    .map((c: any) => `${c.product?.name ?? "Produit"} → ${c.station?.name ?? "Station"}`)
                    .join(", ")
                throw new AppError(`Some products are already linked to another production station: ${details}`, 400)
            }
        }

        return trackedTransaction(shopId, "Station", async (t, tx: any) => {
            const existingLinks = await tx.stationProductLink.findMany({
                where: { stationId },
                select: { productId: true }
            })

            const existingProductIds = existingLinks.map((link: any) => link.productId)
            const removedProductIds = existingProductIds.filter((productId: string) => !uniqueProductIds.includes(productId))
            const newProductIds = uniqueProductIds.filter((productId: string) => !existingProductIds.includes(productId))

            await tx.stationProductLink.deleteMany({
                where: {
                    stationId,
                    ...(removedProductIds.length > 0 ? { productId: { in: removedProductIds } } : uniqueProductIds.length === 0 ? {} : { productId: { in: [] } })
                }
            })

            if (newProductIds.length > 0) {
                await tx.stationProductLink.createMany({
                    data: newProductIds.map((productId: string) => ({ stationId, productId })),
                    skipDuplicates: true,
                })
            }

            if (station.mainType === "PRODUCTION") {
                if (removedProductIds.length > 0) {
                    await tx.product.updateMany({
                        where: {
                            id: { in: removedProductIds },
                            shopId,
                            stationId,
                        },
                        data: { stationId: null }
                    })
                }

                if (uniqueProductIds.length > 0) {
                    await tx.product.updateMany({
                        where: {
                            id: { in: uniqueProductIds },
                            shopId,
                        },
                        data: { stationId }
                    })
                }
            } else {
                await tx.product.updateMany({
                    where: {
                        shopId,
                        stationId,
                    },
                    data: { stationId: null }
                })
            }

            const stationWithProductsBeforeRevision = await tx.station.findFirst({
                where: { id: stationId, shopId },
                include: DETAIL_INCLUDE
            })

            const preview = stationWithProductsBeforeRevision ? toDetail(stationWithProductsBeforeRevision) : null

            const updated = await t.update<any>(
                "station",
                { id: stationId },
                {},
                { include: DETAIL_INCLUDE },
                "station.updated",
                {
                    changes: ["linkedProducts"],
                    productIds: uniqueProductIds,
                    linkedProducts: preview?.linkedProducts ?? [],
                    name: preview?.name ?? station.name,
                    mainType: preview?.mainType ?? station.mainType,
                    active: preview?.active ?? station.active,
                }
            )

            return toDetail(updated)
        }, { actorUserId, senderSocketId })
    },

    /**
     * Récupère les produits actifs assignés à une station.
     */
    async getStationProducts(shopId: string, stationId: string) {
        const products = await prisma.product.findMany({
            where: {
                shopId,
                active: true,
                OR: [
                    { stationLinks: { some: { stationId } } },
                    { stationId },
                ]
            },
            orderBy: { name: "asc" },
            select: {
                id: true,
                name: true,
                price: true,
                categoryId: true,
                category: { select: { name: true } },
                stationDepositLinks: {
                    where: { stationId },
                    include: {
                        depositDefinition: {
                            select: {
                                id: true,
                                label: true,
                                amount: true,
                            }
                        }
                    },
                    orderBy: [
                        { displayOrder: "asc" },
                        { createdAt: "asc" },
                    ]
                }
            }
        })

        return products.map(p => ({
            id: p.id,
            name: p.name,
            price: p.price,
            categoryId: p.categoryId,
            categoryName: p.category.name,
            depositLinks: (p.stationDepositLinks ?? []).map(mapStationProductDepositLink)
        }))
    },

    async getStationProductDepositLinks(
        shopId: string,
        stationId: string,
        productId: string,
    ): Promise<StationProductDepositLinkDto[]> {
        await requireStationProductContext(prisma, shopId, stationId, productId)

        const links = await prisma.stationProductDepositLink.findMany({
            where: {
                stationId,
                productId,
            },
            include: {
                depositDefinition: {
                    select: {
                        id: true,
                        label: true,
                        amount: true,
                    }
                }
            },
            orderBy: [
                { displayOrder: "asc" },
                { createdAt: "asc" },
            ]
        })

        return links.map(mapStationProductDepositLink)
    },

    async replaceStationProductDepositLinks(
        shopId: string,
        stationId: string,
        productId: string,
        input: ReplaceStationProductDepositLinksInput,
        actorUserId?: string,
        senderSocketId?: string,
    ): Promise<StationProductDepositLinkDto[]> {
        return trackedTransaction(shopId, "Station", async (t, tx: any) => {
            const { station, product } = await requireStationProductContext(tx, shopId, stationId, productId)

            const depositDefinitionIds = input.links.map((link) => link.depositDefinitionId)
            const uniqueDepositDefinitionIds = [...new Set(depositDefinitionIds)]

            if (uniqueDepositDefinitionIds.length !== depositDefinitionIds.length) {
                throw new AppError("Duplicate deposit definitions are not allowed for the same station product", 400)
            }

            const definitions = uniqueDepositDefinitionIds.length > 0
                ? await tx.depositDefinition.findMany({
                    where: {
                        shopId,
                        active: true,
                        id: { in: uniqueDepositDefinitionIds },
                    },
                    select: {
                        id: true,
                        label: true,
                        amount: true,
                    }
                })
                : []

            if (definitions.length !== uniqueDepositDefinitionIds.length) {
                throw new AppError("One or more deposit definitions are invalid or inactive", 400)
            }

            const normalizedLinks = input.links.map((link, index) => {
                if (link.applicationMode === "REQUIRED") {
                    return {
                        ...link,
                        displayOrder: link.displayOrder ?? index,
                        autoAddEnabled: true,
                        isRemovableByCashier: false,
                    }
                }

                return {
                    ...link,
                    displayOrder: link.displayOrder ?? index,
                    isRemovableByCashier: true,
                }
            })

            await tx.stationProductDepositLink.deleteMany({
                where: {
                    stationId,
                    productId,
                }
            })

            if (normalizedLinks.length > 0) {
                await tx.stationProductDepositLink.createMany({
                    data: normalizedLinks.map((link) => ({
                        stationId,
                        productId,
                        depositDefinitionId: link.depositDefinitionId,
                        defaultQuantity: link.defaultQuantity,
                        displayOrder: link.displayOrder,
                        applicationMode: link.applicationMode,
                        isRemovableByCashier: link.isRemovableByCashier,
                        autoAddEnabled: link.autoAddEnabled,
                    }))
                })
            }

            const updatedLinks = await tx.stationProductDepositLink.findMany({
                where: {
                    stationId,
                    productId,
                },
                include: {
                    depositDefinition: {
                        select: {
                            id: true,
                            label: true,
                            amount: true,
                        }
                    }
                },
                orderBy: [
                    { displayOrder: "asc" },
                    { createdAt: "asc" },
                ]
            })

            const payload = updatedLinks.map(mapStationProductDepositLink)

            await t.update<any>(
                "station",
                { id: stationId },
                {},
                { select: { id: true } },
                "station.updated",
                {
                    changes: ["productDepositLinks"],
                    stationId,
                    stationName: station.name,
                    productId,
                    productName: product.name,
                    depositLinks: payload,
                }
            )

            return payload
        }, { actorUserId, senderSocketId })
    },

    /**
     * Queue pour le runner : items READY à récupérer + items PICKED_UP à livrer.
     * Enrichi avec les infos de TransferRoute (mode de transport, runner assigné).
     * @param productionStationIds — stations de production assignées au runner
     */
    /**
     * Queue du runner : items READY/PICKED_UP qui ont une route logistique.
     * Le runner voit tous les items en transit de son shop.
     * Si le runner est nommément assigné à des étapes TRANSPORT, on pourrait filtrer —
     * pour l'instant on montre tout le shop.
     */
    async getRunnerQueue(shopId: string, _runnerId: string) {
        const items: any[] = await prisma.orderItem.findMany({
            where: {
                status: { in: ["READY", "PICKED_UP"] },
                order: {
                    shopId,
                    status: { notIn: ["DRAFT", "CANCELLED"] }
                },
                logisticsRouteId: { not: null }
            },
            include: {
                order: {
                    include: {
                        station: true,
                        orderItems: {
                            where: {
                                status: { notIn: ["CANCELLED", "DELIVERED"] }
                            },
                            select: {
                                status: true,
                                quantity: true,
                            }
                        }
                    }
                },
                product: { include: { station: true } },
                takenBy: true,
                pickedUpBy: true,
                customizations: true,
                lifecycleEvents: {
                    orderBy: { occurredAt: "asc" as const },
                },
                logisticsRoute: {
                    include: {
                        steps: {
                            include: {
                                station: { select: { id: true, name: true } },
                                destinationStation: { select: { id: true, name: true } },
                                assignedRunner: { select: { id: true, displayName: true, username: true } },
                            },
                            orderBy: { position: "asc" as const },
                        }
                    }
                },
            },
            orderBy: { createdAt: "asc" }
        })

        // Group READY items by production station
        const readyMap = new Map<string, {
            stationId: string
            stationName: string
            items: any[]
        }>()

        // Group PICKED_UP items by destination cash station
        const pickedUpMap = new Map<string, {
            stationId: string | null
            stationName: string
            items: any[]
        }>()

        for (const item of items) {
            const transportStep = resolveRelevantTransportStep(
                (item as any).logisticsRoute?.steps,
                (item as any).currentStepPosition ?? null
            )
            if (transportStep?.handlerMode !== "RUNNER") {
                continue
            }

            const orderNum = (item.order as any).sessionOrderNumber
            const orderStationName = (item.order as any).station?.name ?? null

            // Résoudre pickedUpByName depuis le lifecycle event PICKED_UP le plus récent (source of truth)
            const allEvents = (item as any).lifecycleEvents ?? []
            const lastPickedUpEvent = [...allEvents].reverse().find((e: any) => e.eventType === "PICKED_UP") ?? null
            const pickedUpByName = lastPickedUpEvent?.actorName
                ?? (item as any).pickedUpBy?.displayName
                ?? (item as any).pickedUpBy?.username
                ?? null

            // Résoudre la station de production et la destination depuis la route logistique
            const resolvedProductionStationId = (item as any).currentStepStationId ?? item.product.stationId ?? null;
            const resolvedProductionStationName = (() => {
                if ((item as any).currentStepStationId && (item as any).logisticsRoute?.steps) {
                    const step = (item as any).logisticsRoute.steps.find(
                        (s: any) => s.stationId === (item as any).currentStepStationId
                    );
                    if (step?.station?.name) return step.station.name;
                }
                return (item.product as any).station?.name ?? "Production";
            })();
            // Destination = du TRANSPORT suivant la PRODUCTION courante, ou de la commande
            const resolvedDestination = (() => {
                if ((item as any).logisticsRoute?.steps) {
                    const steps = (item as any).logisticsRoute.steps;
                    const currentPos = (item as any).currentStepPosition ?? -1;
                    // Si on est sur un TRANSPORT, c'est sa destination
                    const currentTransport = steps.find((s: any) => s.position === currentPos && s.stepType === "TRANSPORT");
                    if (currentTransport?.destinationStationId) {
                        return { id: currentTransport.destinationStationId, name: currentTransport.destinationStation?.name ?? "Destination" };
                    }
                    // Si on est sur une PRODUCTION, chercher le prochain TRANSPORT
                    const nextTransport = steps.find((s: any) => s.position > currentPos && s.stepType === "TRANSPORT");
                    if (nextTransport?.destinationStationId) {
                        return { id: nextTransport.destinationStationId, name: nextTransport.destinationStation?.name ?? "Destination" };
                    }
                }
                return { id: item.order.stationId ?? null, name: orderStationName ?? "Commande directe" };
            })();

            const orderProgress = summarizeOrderProgress((item.order as any).orderItems)
            const serialized = {
                id: item.id,
                name: item.name,
                productId: item.productId,
                quantity: item.quantity,
                unitPrice: item.unitPrice,
                status: item.status,
                orderId: item.orderId,
                orderShortId: buildOrderShortId(item.orderId, orderNum, orderStationName),
                productionStationId: resolvedProductionStationId,
                productionStationName: resolvedProductionStationName,
                destinationStationId: resolvedDestination.id,
                destinationStationName: resolvedDestination.name,
                createdAt: item.createdAt.toISOString(),
                takenByName: item.takenBy?.displayName ?? item.takenBy?.username ?? null,
                pickedUpByName,

                transportedByName: (item as any).transportedByName ?? null,
                ...orderProgress,

                // Logistics route info
                logisticsRouteId: (item as any).logisticsRoute?.id ?? (item as any).logisticsRouteId ?? null,
                currentStepPosition: (item as any).currentStepPosition ?? null,
                logisticsRouteSteps: ((item as any).logisticsRoute?.steps ?? []).map((s: any) => ({
                    id: s.id,
                    stationId: s.stationId,
                    stationName: s.station?.name ?? null,
                    destinationStationId: s.destinationStationId ?? null,
                    destinationStationName: s.destinationStation?.name ?? null,
                    stepType: s.stepType,
                    position: s.position,
                    label: s.label ?? null,
                    handlerMode: s.handlerMode ?? null,
                    assignedRunnerId: s.assignedRunnerId ?? null,
                    assignedRunnerName: s.assignedRunner ? (s.assignedRunner.displayName ?? s.assignedRunner.username) : null,
                })),

                customizations: ((item as any).customizations ?? []).map((c: any) => ({
                    id: c.id,
                    ingredientId: c.ingredientId,
                    ingredientName: c.ingredientName,
                    action: c.action,
                    quantity: c.quantity,
                    extraPrice: c.extraPrice,
                })),
                lifecycleEvents: ((item as any).lifecycleEvents ?? []).map((e: any) => ({
                    id: e.id,
                    eventType: e.eventType,
                    fromStatus: e.fromStatus,
                    toStatus: e.toStatus,
                    actorId: e.actorId,
                    actorName: e.actorName,
                    stationId: e.stationId,
                    occurredAt: e.occurredAt.toISOString(),
                })),
            }

            if (item.status === "READY") {
                // Grouper par station de production courante (route) ou station du produit (legacy)
                const key = (item as any).currentStepStationId ?? item.product.stationId ?? "unknown"
                const stName = (() => {
                    // Chercher le nom de la station courante dans la route
                    if ((item as any).currentStepStationId && (item as any).logisticsRoute?.steps) {
                        const step = (item as any).logisticsRoute.steps.find(
                            (s: any) => s.stationId === (item as any).currentStepStationId
                        );
                        if (step?.station?.name) return step.station.name;
                    }
                    return (item.product as any).station?.name ?? "Production";
                })();
                if (!readyMap.has(key)) {
                    readyMap.set(key, {
                        stationId: key,
                        stationName: stName,
                        items: []
                    })
                }
                readyMap.get(key)!.items.push(serialized)
            } else {
                // PICKED_UP — grouper par station de destination
                // Route logistique : chercher le destinationStationId du TRANSPORT courant
                let destStationId: string | null = item.order.stationId ?? null;
                let destStationName: string = (item.order as any).station?.name ?? "Commande directe";
                if ((item as any).currentStepPosition != null && (item as any).logisticsRoute?.steps) {
                    const currentTransport = (item as any).logisticsRoute.steps.find(
                        (s: any) => s.position === (item as any).currentStepPosition && s.stepType === "TRANSPORT"
                    );
                    if (currentTransport?.destinationStationId) {
                        destStationId = currentTransport.destinationStationId;
                        destStationName = currentTransport.destinationStation?.name ?? "Destination";
                    }
                }
                const key = destStationId ?? "no-station"
                if (!pickedUpMap.has(key)) {
                    pickedUpMap.set(key, {
                        stationId: destStationId,
                        stationName: destStationName,
                        items: []
                    })
                }
                pickedUpMap.get(key)!.items.push(serialized)
            }
        }

        return {
            readyGroups: Array.from(readyMap.values()),
            pickedUpGroups: Array.from(pickedUpMap.values())
        }
    },

    /**
     * Détecte si un runner est assigné à une station de production.
     */
    async hasRunnerAssigned(shopId: string, stationId: string): Promise<boolean> {
        const runnerAssignment = await prisma.shopUserStation.findFirst({
            where: {
                stationId,
                shopUser: {
                    shopId,
                    role: "RUNNER",
                    active: true
                }
            }
        })
        return !!runnerAssignment
    },
};

