import { prisma } from "@/core/prisma";
import { AppError } from "@/core/AppError";
import { tracked } from "@/core/tracked";
import type {
    CreateLogisticsRouteDto,
    CreateLogisticsRouteStepInput,
    UpdateLogisticsRouteDto,
    LogisticsRouteListItemDto,
    LogisticsRouteDetailDto,
    LogisticsRouteStepDto,
} from "ttm-shared";

// ─── Select helpers ────────────────────────────────────────────────────────

const stepSelect = {
    id: true,
    routeId: true,
    stationId: true,
    station: { select: { id: true, name: true } },
    destinationStationId: true,
    destinationStation: { select: { id: true, name: true } },
    stepType: true,
    position: true,
    label: true,
    handlerMode: true,
    assignedRunnerId: true,
    assignedRunner: { select: { id: true, displayName: true, username: true } },
    estimatedMinutes: true,
} as const;

const routeDetailSelect = {
    id: true,
    shopId: true,
    name: true,
    description: true,
    isDefault: true,
    active: true,
    revision: true,
    createdAt: true,
    updatedAt: true,
    steps: {
        select: stepSelect,
        orderBy: { position: "asc" as const },
    },
} as const;

// ─── Mappers ────────────────────────────────────────────────────────────────

function toStepDto(s: any): LogisticsRouteStepDto {
    return {
        id: s.id,
        routeId: s.routeId,
        stationId: s.stationId ?? null,
        station: s.station ?? null,
        destinationStationId: s.destinationStationId ?? null,
        destinationStation: s.destinationStation ?? null,
        stepType: s.stepType,
        position: s.position,
        label: s.label ?? null,
        handlerMode: s.handlerMode ?? null,
        assignedRunner: s.assignedRunner
            ? {
                  id: s.assignedRunner.id,
                  displayName: s.assignedRunner.displayName,
                  username: s.assignedRunner.username,
              }
            : null,
        estimatedMinutes: s.estimatedMinutes ?? null,
    };
}

function toListItem(r: any): LogisticsRouteListItemDto {
    const steps = (r.steps ?? []).sort((a: any, b: any) => a.position - b.position);
    return {
        id: r.id,
        name: r.name,
        description: r.description ?? null,
        isDefault: r.isDefault,
        active: r.active,
        stepsCount: steps.length,
        stepsSummary: steps.map((s: any) => {
            if (s.stepType === "TRANSPORT") return `🚚→${s.destinationStation?.name ?? "?"}`;
            return `🍳${s.station?.name ?? "?"}`;
        }).join(" · "),
        createdAt: r.createdAt.toISOString(),
    };
}

function toDetail(r: any): LogisticsRouteDetailDto {
    return {
        id: r.id,
        shopId: r.shopId,
        name: r.name,
        description: r.description ?? null,
        isDefault: r.isDefault,
        active: r.active,
        revision: r.revision,
        steps: (r.steps ?? []).map(toStepDto),
        createdAt: r.createdAt.toISOString(),
        updatedAt: r.updatedAt.toISOString(),
    };
}

// ─── Service ────────────────────────────────────────────────────────────────

export async function listLogisticsRoutes(
    shopId: string
): Promise<LogisticsRouteListItemDto[]> {
    const routes = await prisma.logisticsRoute.findMany({
        where: { shopId },
        select: routeDetailSelect,
        orderBy: [{ isDefault: "desc" }, { name: "asc" }],
    });
    return routes.map(toListItem);
}

export async function getLogisticsRouteById(
    shopId: string,
    id: string
): Promise<LogisticsRouteDetailDto> {
    const route = await prisma.logisticsRoute.findFirst({
        where: { id, shopId },
        select: routeDetailSelect,
    });
    if (!route) throw new AppError("Route logistique introuvable", 404);
    return toDetail(route);
}

export async function createLogisticsRoute(
    shopId: string,
    actorId: string | null,
    data: CreateLogisticsRouteDto
): Promise<LogisticsRouteDetailDto> {
    // Vérifier que toutes les stations (stationId + destinationStationId) appartiennent au shop
    const allStationIds = [...new Set(
        data.steps.flatMap((s: CreateLogisticsRouteStepInput) =>
            [s.stationId, s.destinationStationId].filter(Boolean) as string[]
        )
    )];
    if (allStationIds.length > 0) {
        const stations = await prisma.station.findMany({
            where: { id: { in: allStationIds }, shopId },
            select: { id: true },
        });
        if (stations.length !== allStationIds.length) {
            throw new AppError("Une ou plusieurs stations n'appartiennent pas à ce shop", 400);
        }
    }

    // Vérifier runners si fournis
    const runnerIds = data.steps
        .filter((s: CreateLogisticsRouteStepInput) => s.assignedRunnerId)
        .map((s: CreateLogisticsRouteStepInput) => s.assignedRunnerId!);
    if (runnerIds.length > 0) {
        const runners = await prisma.shopUser.findMany({
            where: { id: { in: runnerIds }, shopId, role: "RUNNER" },
            select: { id: true },
        });
        if (runners.length !== new Set(runnerIds).size) {
            throw new AppError("Un ou plusieurs runners sont introuvables ou de rôle invalide", 400);
        }
    }

    // Si isDefault, retirer le flag des autres routes
    if (data.isDefault) {
        await prisma.logisticsRoute.updateMany({
            where: { shopId, isDefault: true },
            data: { isDefault: false },
        });
    }

    const route = await prisma.logisticsRoute.create({
        data: {
            shopId,
            name: data.name,
            description: data.description ?? null,
            isDefault: data.isDefault ?? false,
            steps: {
                create: data.steps.map((s: CreateLogisticsRouteStepInput) => ({
                    stationId: s.stationId ?? null,
                    destinationStationId: s.destinationStationId ?? null,
                    stepType: s.stepType,
                    position: s.position,
                    label: s.label ?? null,
                    handlerMode: s.handlerMode ?? null,
                    assignedRunnerId: s.assignedRunnerId ?? null,
                    estimatedMinutes: s.estimatedMinutes ?? null,
                })),
            },
        },
        select: routeDetailSelect,
    });

    // Si isDefault, aussi mettre à jour le shop
    if (data.isDefault) {
        await prisma.shop.update({
            where: { id: shopId },
            data: { defaultLogisticsRouteId: route.id },
        });
    }

    const t = tracked({ shopId, entity: "LogisticsRoute", actorUserId: actorId });
    await t.emit("logistics_route.created", toListItem(route) as any, route.id, 1);

    return toDetail(route);
}

export async function updateLogisticsRoute(
    shopId: string,
    id: string,
    actorId: string | null,
    data: UpdateLogisticsRouteDto
): Promise<LogisticsRouteDetailDto> {
    const existing = await prisma.logisticsRoute.findFirst({ where: { id, shopId } });
    if (!existing) throw new AppError("Route logistique introuvable", 404);

    // Si on change isDefault → true, retirer le flag des autres
    if (data.isDefault === true) {
        await prisma.logisticsRoute.updateMany({
            where: { shopId, isDefault: true, id: { not: id } },
            data: { isDefault: false },
        });
    }

    // Vérifier les stations et runners si steps fournis
    if (data.steps) {
        const allStationIds = [...new Set(
            data.steps.flatMap((s: CreateLogisticsRouteStepInput) =>
                [s.stationId, s.destinationStationId].filter(Boolean) as string[]
            )
        )];
        if (allStationIds.length > 0) {
            const stations = await prisma.station.findMany({
                where: { id: { in: allStationIds }, shopId },
                select: { id: true },
            });
            if (stations.length !== allStationIds.length) {
                throw new AppError("Une ou plusieurs stations n'appartiennent pas à ce shop", 400);
            }
        }

        const runnerIds = data.steps
            .filter((s: CreateLogisticsRouteStepInput) => s.assignedRunnerId)
            .map((s: CreateLogisticsRouteStepInput) => s.assignedRunnerId!);
        if (runnerIds.length > 0) {
            const runners = await prisma.shopUser.findMany({
                where: { id: { in: runnerIds }, shopId, role: "RUNNER" },
                select: { id: true },
            });
            if (runners.length !== new Set(runnerIds).size) {
                throw new AppError("Un ou plusieurs runners sont introuvables", 400);
            }
        }
    }

    // Transaction : mise à jour route + recréation steps si fournis
    const updated = await prisma.$transaction(async (tx) => {
        if (data.steps) {
            // Supprimer les anciennes étapes
            await tx.logisticsRouteStep.deleteMany({ where: { routeId: id } });
            // Recréer les nouvelles
            await tx.logisticsRouteStep.createMany({
                data: data.steps!.map((s: CreateLogisticsRouteStepInput) => ({
                    routeId: id,
                    stationId: s.stationId ?? null,
                    destinationStationId: s.destinationStationId ?? null,
                    stepType: s.stepType,
                    position: s.position,
                    label: s.label ?? null,
                    handlerMode: s.handlerMode ?? null,
                    assignedRunnerId: s.assignedRunnerId ?? null,
                    estimatedMinutes: s.estimatedMinutes ?? null,
                })),
            });
        }

        return tx.logisticsRoute.update({
            where: { id },
            data: {
                ...(data.name !== undefined && { name: data.name }),
                ...(data.description !== undefined && { description: data.description }),
                ...(data.isDefault !== undefined && { isDefault: data.isDefault }),
                ...(data.active !== undefined && { active: data.active }),
            },
            select: routeDetailSelect,
        });
    });

    // Mettre à jour le champ shop.defaultLogisticsRouteId
    if (data.isDefault === true) {
        await prisma.shop.update({
            where: { id: shopId },
            data: { defaultLogisticsRouteId: id },
        });
    } else if (data.isDefault === false && existing.isDefault) {
        await prisma.shop.update({
            where: { id: shopId },
            data: { defaultLogisticsRouteId: null },
        });
    }

    const t = tracked({ shopId, entity: "LogisticsRoute", actorUserId: actorId });
    await t.emit("logistics_route.updated", toListItem(updated) as any, id, 1);

    return toDetail(updated);
}

export async function deleteLogisticsRoute(
    shopId: string,
    id: string,
    actorId: string | null
): Promise<void> {
    const existing = await prisma.logisticsRoute.findFirst({ where: { id, shopId } });
    if (!existing) throw new AppError("Route logistique introuvable", 404);

    await prisma.logisticsRoute.delete({ where: { id } });

    // Si c'était la route par défaut, nettoyer
    if (existing.isDefault) {
        await prisma.shop.update({
            where: { id: shopId },
            data: { defaultLogisticsRouteId: null },
        });
    }

    const t = tracked({ shopId, entity: "LogisticsRoute", actorUserId: actorId });
    await t.emit("logistics_route.deleted", { id }, id, 1);
}

/**
 * Résoudre la route logistique à appliquer pour un produit.
 * Priorité : product.logisticsRouteId > shop.defaultLogisticsRouteId
 */
export async function resolveLogisticsRoute(
    productId: string,
    shopId: string
): Promise<LogisticsRouteDetailDto | null> {
    // 1. Override produit ?
    const product = await prisma.product.findFirst({
        where: { id: productId, shopId },
        select: { logisticsRouteId: true },
    });
    if (product?.logisticsRouteId) {
        const route = await prisma.logisticsRoute.findFirst({
            where: { id: product.logisticsRouteId, active: true },
            select: routeDetailSelect,
        });
        if (route) return toDetail(route);
    }

    // 2. Route par défaut du shop
    const shop = await prisma.shop.findFirst({
        where: { id: shopId },
        select: { defaultLogisticsRouteId: true },
    });
    if (shop?.defaultLogisticsRouteId) {
        const route = await prisma.logisticsRoute.findFirst({
            where: { id: shop.defaultLogisticsRouteId, active: true },
            select: routeDetailSelect,
        });
        if (route) return toDetail(route);
    }

    return null;
}

