import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { AccountStatus, OrderStatus, PaymentMethod, PaymentStatus, Prisma, UserRole } from '@prisma/client';
import { CatalogService } from '../catalog/catalog.service';
import { PrismaService } from '../database/prisma.service';
import { FinanceService } from '../finance/finance.service';
import { MapsService } from '../maps/maps.service';
import { CreatePublicOrderDto } from './dto/create-public-order.dto';
import { OrdersGateway } from './orders.gateway';

type RequestUser = {
  sub: string;
  role: UserRole;
};

type RestaurantOrderFlowSettings = {
  acceptMode: 'MANUAL' | 'AUTOMATIC';
  autoRequestDeliveryWhenReady: boolean;
  driverPresenceWindowMinutes: number;
};

const defaultRestaurantOrderFlowSettings: RestaurantOrderFlowSettings = {
  acceptMode: 'MANUAL',
  autoRequestDeliveryWhenReady: false,
  driverPresenceWindowMinutes: 10,
};

@Injectable()
export class OrdersService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly catalogService: CatalogService,
    private readonly financeService: FinanceService,
    private readonly mapsService: MapsService,
    private readonly ordersGateway: OrdersGateway,
  ) {}

  list(user: RequestUser) {
    return this.prisma.order.findMany({
      take: 50,
      orderBy: { createdAt: 'desc' },
      where: this.resolveWhere(user),
      include: {
        customer: { include: { user: true } },
        restaurant: true,
        address: true,
        delivery: { include: { driver: { include: { user: true } } } },
        items: { include: { product: true } },
        payment: true,
      },
    });
  }

  async getRestaurantFlowSettings(userId: string) {
    const restaurant = await this.findRestaurantByUserId(userId);
    return this.readRestaurantFlowSettings(restaurant.id);
  }

  async updateRestaurantFlowSettings(
    userId: string,
    data: Partial<RestaurantOrderFlowSettings>,
  ) {
    const restaurant = await this.findRestaurantByUserId(userId);
    const current = await this.readRestaurantFlowSettings(restaurant.id);
    const next: RestaurantOrderFlowSettings = {
      acceptMode: data.acceptMode === 'AUTOMATIC' ? 'AUTOMATIC' : data.acceptMode === 'MANUAL' ? 'MANUAL' : current.acceptMode,
      autoRequestDeliveryWhenReady:
        typeof data.autoRequestDeliveryWhenReady === 'boolean'
          ? data.autoRequestDeliveryWhenReady
          : current.autoRequestDeliveryWhenReady,
      driverPresenceWindowMinutes: Math.max(
        1,
        Number(data.driverPresenceWindowMinutes ?? current.driverPresenceWindowMinutes),
      ),
    };

    await this.prisma.setting.upsert({
      where: { key: this.restaurantFlowSettingsKey(restaurant.id) },
      update: { value: next },
      create: { key: this.restaurantFlowSettingsKey(restaurant.id), value: next },
    });

    return next;
  }

  async acceptRestaurantOrder(user: RequestUser, id: string) {
    const order = await this.findRestaurantOwnedOrder(user.sub, id);
    const allowedStatuses = new Set<OrderStatus>([
      OrderStatus.SENT_TO_RESTAURANT,
      OrderStatus.PAYMENT_CONFIRMED,
      OrderStatus.CREATED,
    ]);
    if (!allowedStatuses.has(order.status)) {
      throw new ForbiddenException('Este pedido não está aguardando aceite da loja.');
    }

    const updated = await this.prisma.order.update({
      where: { id },
      data: { status: OrderStatus.PREPARING },
      include: this.orderDetailsInclude(),
    });

    this.emitRestaurantOrderLifecycle(updated, 'restaurant.order.accepted', {
      orderId: updated.id,
      status: updated.status,
      message: 'Pedido aceito pela loja e enviado para preparo.',
    });

    return updated;
  }

  async markRestaurantOrderReady(user: RequestUser, id: string) {
    const order = await this.findRestaurantOwnedOrder(user.sub, id);
    const allowedStatuses = new Set<OrderStatus>([OrderStatus.PREPARING, OrderStatus.RESTAURANT_ACCEPTED]);
    if (!allowedStatuses.has(order.status)) {
      throw new ForbiddenException('Somente pedidos em preparo podem ser marcados como prontos.');
    }

    const updated = await this.prisma.order.update({
      where: { id },
      data: { status: OrderStatus.READY_FOR_PICKUP },
      include: this.orderDetailsInclude(),
    });

    this.emitRestaurantOrderLifecycle(updated, 'restaurant.order.ready', {
      orderId: updated.id,
      status: updated.status,
      message: 'Pedido marcado como pronto para retirada.',
    });

    const settings = await this.readRestaurantFlowSettings(updated.restaurantId);
    if (settings.autoRequestDeliveryWhenReady) {
      return this.requestRestaurantDelivery(user, id);
    }

    return updated;
  }

  async requestRestaurantDelivery(user: RequestUser, id: string) {
    const order = await this.findRestaurantOwnedOrder(user.sub, id);
    const allowedStatuses = new Set<OrderStatus>([
      OrderStatus.READY_FOR_PICKUP,
      OrderStatus.SEARCHING_DRIVER,
      OrderStatus.DRIVER_ACCEPTED,
    ]);
    if (!allowedStatuses.has(order.status)) {
      throw new ForbiddenException('A entrega só pode ser solicitada quando o pedido estiver pronto.');
    }

    return this.dispatchRestaurantOrder(order.id);
  }

  async cancel(id: string, reason?: string) {
    const current = await this.prisma.order.findUnique({ where: { id } });
    if (!current) throw new NotFoundException('Pedido nao encontrado.');

    return this.prisma.order.update({
      where: { id },
      data: {
        status: OrderStatus.CANCELLED,
        notes: reason ? `${current.notes ?? ''}\n[Cancelado admin] ${reason}`.trim() : current.notes,
      },
      include: {
        customer: { include: { user: true } },
        restaurant: true,
        delivery: { include: { driver: { include: { user: true } } } },
        items: true,
        payment: true,
      },
    });
  }

  async createPublicOrder(body: CreatePublicOrderDto) {
    const restaurant = await this.prisma.restaurant.findUnique({
      where: { slug: body.restaurantSlug },
      include: { user: true },
    });
    if (!restaurant) throw new NotFoundException('Loja publica nao encontrada.');

    const flowSettings = await this.readRestaurantFlowSettings(restaurant.id);

    const products = await this.prisma.product.findMany({
      where: {
        restaurantId: restaurant.id,
        isActive: true,
        id: { in: body.items.map((item) => item.productId) },
      },
      include: {
        additionals: {
          where: { isActive: true },
        },
        variations: true,
      },
    });

    const productMap = new Map(products.map((product) => [product.id, product]));
    const normalizedItems = body.items.map((item) => {
      const product = productMap.get(item.productId);
      if (!product) {
        throw new NotFoundException(`Produto ${item.productId} nao encontrado na loja.`);
      }
      const quantity = Math.max(1, Number(item.quantity || 1));
      const selectedAdditionals = (item.selectedAdditionalIds ?? [])
        .map((additionalId) => product.additionals.find((entry) => entry.id === additionalId))
        .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry));
      const selectedVariation = item.selectedVariationId
        ? product.variations.find((entry) => entry.id === item.selectedVariationId) ?? null
        : null;
      if ((item.selectedAdditionalIds?.length ?? 0) !== selectedAdditionals.length) {
        throw new NotFoundException(`Adicional nao encontrado no produto ${product.name}.`);
      }
      if (item.selectedVariationId && !selectedVariation) {
        throw new NotFoundException(`Variacao nao encontrada no produto ${product.name}.`);
      }

      const unitPrice =
        Number(product.price) +
        Number(selectedVariation?.price ?? 0) +
        selectedAdditionals.reduce((sum, additional) => sum + Number(additional.price), 0);
      return {
        product,
        quantity,
        notes: item.notes,
        selectedAdditionals,
        selectedVariation,
        unitPrice: Number(unitPrice.toFixed(2)),
        totalPrice: Number((unitPrice * quantity).toFixed(2)),
      };
    });

    const subtotal = Number(normalizedItems.reduce((sum, item) => sum + item.totalPrice, 0).toFixed(2));
    const customerCoordinates = await this.resolveAddressCoordinates(body);
    const restaurantCoordinates = await this.resolveRestaurantCoordinates(restaurant);
    const deliveryRoute = restaurantCoordinates && customerCoordinates
      ? await this.mapsService.getRoute(restaurantCoordinates, customerCoordinates)
      : null;
    const restaurantBaseDeliveryFee = Number(restaurant.baseDeliveryFee ?? 10);
    const restaurantPricePerKm = Math.max(Number(restaurant.pricePerKm ?? 1.5), 0);
    const deliveryDistanceKm = deliveryRoute?.distanceKm ?? 0;
    const simpleDeliveryFee =
      deliveryRoute && deliveryDistanceKm > 0
        ? Number(Math.max(restaurantBaseDeliveryFee, deliveryDistanceKm * restaurantPricePerKm).toFixed(2))
        : Number(restaurantBaseDeliveryFee.toFixed(2));

    const now = new Date();
    const [activePromotions, coupon] = await Promise.all([
      this.prisma.promotion.findMany({
        where: {
          restaurantId: restaurant.id,
          isActive: true,
          startsAt: { lte: now },
          endsAt: { gte: now },
        },
        orderBy: [{ discountValue: 'desc' }, { endsAt: 'asc' }],
      }),
      body.couponCode
        ? this.prisma.coupon.findFirst({
            where: {
              restaurantId: restaurant.id,
              code: body.couponCode.trim().toUpperCase(),
            },
          })
        : Promise.resolve(null),
    ]);

    if (body.couponCode?.trim() && !coupon) {
      throw new NotFoundException('Cupom nao encontrado.');
    }

    const activePromotion = activePromotions[0] ?? null;
    const promotionDiscount = activePromotion
      ? this.catalogService.applyDiscountRule(
          subtotal,
          activePromotion.discountType,
          Number(activePromotion.discountValue),
          simpleDeliveryFee,
        )
      : 0;

    if (
      coupon &&
      (!coupon.isActive ||
        coupon.validFrom > now ||
        coupon.validUntil < now ||
        (coupon.minOrderValue && subtotal < Number(coupon.minOrderValue)))
    ) {
      throw new ForbiddenException('Cupom indisponivel para este pedido.');
    }

    const couponDiscount = coupon
      ? this.catalogService.applyDiscountRule(
          subtotal - promotionDiscount,
          coupon.type,
          Number(coupon.value),
          simpleDeliveryFee,
        )
      : 0;
    const totalDiscount = Number(Math.min(subtotal + simpleDeliveryFee, promotionDiscount + couponDiscount).toFixed(2));

    const financePreview = await this.financeService.calculateOrder({
      orderSubtotal: subtotal,
      distanceKm: deliveryDistanceKm,
      availableDrivers: 0,
      pendingOrders: 0,
      weatherCondition: 'sunny',
      cityZone: restaurant.neighborhood ?? 'default',
      restaurantCommissionPercentage: Number(restaurant.commissionPercentage ?? 18) / 100,
      deliveryFeeOverride: simpleDeliveryFee,
    });
    const orderTotal = Number(Math.max(0, financePreview.gross - totalDiscount).toFixed(2));

    const normalizedEmail = body.email?.trim().toLowerCase();
    const normalizedPhone = body.phone.trim();
    const [existingUserByPhone, existingUserByEmail] = await Promise.all([
      this.prisma.user.findUnique({ where: { phone: normalizedPhone } }),
      normalizedEmail ? this.prisma.user.findUnique({ where: { email: normalizedEmail } }) : Promise.resolve(null),
    ]);
    const targetUser = existingUserByEmail ?? existingUserByPhone;

    const user = targetUser
      ? await this.prisma.user.update({
          where: { id: targetUser.id },
          data: {
            status: AccountStatus.ACTIVE,
            email:
              normalizedEmail && (!existingUserByEmail || existingUserByEmail.id === targetUser.id)
                ? normalizedEmail
                : undefined,
            phone:
              normalizedPhone && (!existingUserByPhone || existingUserByPhone.id === targetUser.id)
                ? normalizedPhone
                : undefined,
          },
        })
      : await this.prisma.user.create({
          data: {
            email: normalizedEmail,
            phone: normalizedPhone,
            passwordHash: 'guest-public-order',
            role: UserRole.CUSTOMER,
            status: AccountStatus.ACTIVE,
          },
        });

    const customer = await this.prisma.customer.upsert({
      where: { userId: user.id },
      update: { fullName: body.fullName },
      create: { userId: user.id, fullName: body.fullName },
    });

    const address = await this.prisma.address.create({
      data: {
        userId: user.id,
        label: 'Entrega atual',
        street: body.street,
        number: body.number,
        complement: body.complement,
        neighborhood: body.neighborhood,
        city: body.city,
        state: body.state,
        zipCode: body.zipCode,
        isDefault: false,
      },
    });

    const onlinePaymentMethods: PaymentMethod[] = [
      PaymentMethod.PIX,
      PaymentMethod.CREDIT_CARD,
      PaymentMethod.DEBIT_CARD,
      PaymentMethod.MERCADO_PAGO,
      PaymentMethod.STRIPE,
    ];
    const isOnlinePayment = onlinePaymentMethods.includes(body.paymentMethod);

    const initialOrderStatus = isOnlinePayment
      ? OrderStatus.WAITING_PAYMENT
      : flowSettings.acceptMode === 'AUTOMATIC'
        ? OrderStatus.PREPARING
        : OrderStatus.SENT_TO_RESTAURANT;

    const order = await this.prisma.order.create({
      data: {
        customerId: customer.id,
        restaurantId: restaurant.id,
        addressId: address.id,
        couponId: coupon?.id ?? null,
        status: initialOrderStatus,
        subtotal: new Prisma.Decimal(subtotal),
        deliveryFee: new Prisma.Decimal(financePreview.delivery_fee_customer),
        serviceFee: new Prisma.Decimal(financePreview.serviceFee),
        discountAmount: new Prisma.Decimal(totalDiscount),
        total: new Prisma.Decimal(orderTotal),
        notes: body.notes,
        items: {
          create: normalizedItems.map((item) => ({
            productId: item.product.id,
            quantity: item.quantity,
            unitPrice: new Prisma.Decimal(item.unitPrice),
            totalPrice: new Prisma.Decimal(item.totalPrice),
            notes: [
              item.selectedVariation ? `Variacao: ${item.selectedVariation.name}` : null,
              item.selectedAdditionals.length
                ? `Adicionais: ${item.selectedAdditionals.map((additional) => additional.name).join(', ')}`
                : null,
              item.notes?.trim() ? `Obs: ${item.notes.trim()}` : null,
            ]
              .filter(Boolean)
              .join(' | '),
          })),
        },
        payment: {
          create: {
            method: body.paymentMethod,
            status: isOnlinePayment ? PaymentStatus.PENDING : PaymentStatus.PENDING,
            externalReference: null,
            amount: new Prisma.Decimal(orderTotal),
          },
        },
      },
      include: {
        restaurant: true,
        address: true,
        customer: { include: { user: true } },
        items: { include: { product: true } },
        payment: true,
        delivery: { include: { driver: { include: { user: true } } } },
      },
    });

    if (customerCoordinates) {
      await this.prisma.address.update({
        where: { id: address.id },
        data: {
          latitude: new Prisma.Decimal(customerCoordinates.latitude),
          longitude: new Prisma.Decimal(customerCoordinates.longitude),
        },
      });
    }

    this.emitRestaurantOrderLifecycle(order, 'order.created', {
      orderId: order.id,
      status: order.status,
      restaurantId: restaurant.id,
      message:
        isOnlinePayment
          ? 'Pedido criado aguardando confirmacao do pagamento online.'
          : flowSettings.acceptMode === 'AUTOMATIC'
            ? 'Pedido criado e aceito automaticamente para preparo.'
            : 'Pedido criado e enviado para a fila de aceite da loja.',
    });

    return {
      id: order.id,
      status: order.status,
      restaurant: {
        id: restaurant.id,
        slug: restaurant.slug,
        name: restaurant.name,
      },
      summary: {
        subtotal,
        deliveryFee: financePreview.delivery_fee_customer,
        serviceFee: financePreview.serviceFee,
        discountAmount: totalDiscount,
        total: orderTotal,
      },
      payment: order.payment,
      items: order.items,
      customer: {
        id: customer.id,
        fullName: customer.fullName,
      },
      etaMinutes: Math.max(25, Math.round(18 + normalizedItems.length * 4)),
    };
  }

  private async dispatchRestaurantOrder(orderId: string) {
    const order = await this.prisma.order.findUnique({
      where: { id: orderId },
      include: this.orderDetailsInclude(),
    });
    if (!order) {
      throw new NotFoundException('Pedido não encontrado para despacho.');
    }

    const settings = await this.readRestaurantFlowSettings(order.restaurantId);
    const driver = await this.findEligibleDriver(settings.driverPresenceWindowMinutes);
    const etaPickup = 7;
    const etaDelivery = Math.max(18, Math.round(18 + Number(order.items.length) * 2));

    const updated = await this.prisma.$transaction(async (tx) => {
      const restaurantCoordinates = await this.resolveRestaurantCoordinates(order.restaurant);
      const customerCoordinates =
        this.toCoordinates(order.address?.latitude, order.address?.longitude) ??
        (order.address ? await this.resolveAddressCoordinates(order.address) : null);
      const deliveryRoute = restaurantCoordinates && customerCoordinates
        ? await this.mapsService.getRoute(restaurantCoordinates, customerCoordinates)
        : null;
      const pickupRoute = driver?.locations[0] && restaurantCoordinates
        ? await this.mapsService.getRoute(
            { latitude: Number(driver.locations[0].latitude), longitude: Number(driver.locations[0].longitude) },
            restaurantCoordinates,
          )
        : null;
      const deliveryDistanceKm = deliveryRoute?.distanceKm ?? Number(order.deliveryFee);

      const delivery = await tx.delivery.upsert({
        where: { orderId: order.id },
        update: {
          driverId: driver?.id ?? null,
          status: driver ? 'ASSIGNED' : 'PENDING',
          etaMinutes: deliveryRoute?.durationMinutes ?? etaDelivery,
          distanceKm: deliveryDistanceKm,
          baseFee: Number(order.restaurant.baseDeliveryFee ?? 5),
          pricePerKm: Number(order.restaurant.pricePerKm ?? 1.5),
          deliveryFeeCustomer: Number(order.deliveryFee),
          driverEarning: Number(order.deliveryFee),
          platformDeliveryProfit: 0,
        },
        create: {
          orderId: order.id,
          driverId: driver?.id ?? null,
          status: driver ? 'ASSIGNED' : 'PENDING',
          etaMinutes: deliveryRoute?.durationMinutes ?? etaDelivery,
          distanceKm: deliveryDistanceKm,
          baseFee: Number(order.restaurant.baseDeliveryFee ?? 5),
          pricePerKm: Number(order.restaurant.pricePerKm ?? 1.5),
          deliveryFeeCustomer: Number(order.deliveryFee),
          driverEarning: Number(order.deliveryFee),
          platformDeliveryProfit: 0,
        },
      });

      await tx.deliveryRoute.upsert({
        where: { orderId: order.id },
        update: {
          driverId: driver?.id ?? null,
          restaurantLatitude: restaurantCoordinates ? new Prisma.Decimal(restaurantCoordinates.latitude) : order.restaurant.latitude,
          restaurantLongitude: restaurantCoordinates ? new Prisma.Decimal(restaurantCoordinates.longitude) : order.restaurant.longitude,
          customerLatitude: customerCoordinates ? new Prisma.Decimal(customerCoordinates.latitude) : order.address?.latitude ?? null,
          customerLongitude: customerCoordinates ? new Prisma.Decimal(customerCoordinates.longitude) : order.address?.longitude ?? null,
          pickupDistanceKm: pickupRoute ? new Prisma.Decimal(pickupRoute.distanceKm) : null,
          deliveryDistanceKm: deliveryRoute ? new Prisma.Decimal(deliveryRoute.distanceKm) : null,
          estimatedPickupTime: pickupRoute?.durationMinutes ?? etaPickup,
          estimatedDeliveryTime: deliveryRoute?.durationMinutes ?? etaDelivery,
          status: 'SEARCHING_DRIVER',
        },
        create: {
          orderId: order.id,
          driverId: driver?.id ?? null,
          restaurantLatitude: restaurantCoordinates ? new Prisma.Decimal(restaurantCoordinates.latitude) : order.restaurant.latitude,
          restaurantLongitude: restaurantCoordinates ? new Prisma.Decimal(restaurantCoordinates.longitude) : order.restaurant.longitude,
          customerLatitude: customerCoordinates ? new Prisma.Decimal(customerCoordinates.latitude) : order.address?.latitude ?? null,
          customerLongitude: customerCoordinates ? new Prisma.Decimal(customerCoordinates.longitude) : order.address?.longitude ?? null,
          pickupDistanceKm: pickupRoute ? new Prisma.Decimal(pickupRoute.distanceKm) : null,
          deliveryDistanceKm: deliveryRoute ? new Prisma.Decimal(deliveryRoute.distanceKm) : null,
          estimatedPickupTime: pickupRoute?.durationMinutes ?? etaPickup,
          estimatedDeliveryTime: deliveryRoute?.durationMinutes ?? etaDelivery,
          status: 'SEARCHING_DRIVER',
        },
      });

      await tx.order.update({
        where: { id: order.id },
        data: { status: OrderStatus.SEARCHING_DRIVER },
      });

      if (driver?.locations[0]) {
        await tx.driverLocation.create({
          data: {
            driverId: driver.id,
            orderId: order.id,
            latitude: driver.locations[0].latitude,
            longitude: driver.locations[0].longitude,
            heading: driver.locations[0].heading,
            speed: driver.locations[0].speed,
            accuracy: driver.locations[0].accuracy,
          },
        });
      }

      return tx.order.findUniqueOrThrow({
        where: { id: order.id },
        include: this.orderDetailsInclude(),
      });
    });

    if (driver && updated.delivery) {
      const offerPayload = {
        orderId: updated.id,
        deliveryId: updated.delivery.id,
        driverId: driver.id,
        driverName: driver.fullName,
        etaMinutes: updated.delivery.etaMinutes,
        destination: {
          street: updated.address?.street ?? null,
          number: updated.address?.number ?? null,
          neighborhood: updated.address?.neighborhood ?? null,
          city: updated.address?.city ?? null,
          state: updated.address?.state ?? null,
          zipCode: updated.address?.zipCode ?? null,
        },
      };
      this.ordersGateway.emitToUser(driver.userId, 'driver.offer.created', offerPayload);
      this.emitRestaurantOrderLifecycle(updated, 'driver.search.started', {
        ...offerPayload,
        message: 'Oferta de entrega enviada para um motoboy disponível.',
      });
      return updated;
    }

    this.emitRestaurantOrderLifecycle(updated, 'driver.search.waiting', {
      orderId: updated.id,
      status: updated.status,
      message: 'Entrega solicitada. Nenhum motoboy ativo e online foi encontrado agora.',
    });
    return updated;
  }

  private async findEligibleDriver(presenceWindowMinutes: number, excludedDriverIds: string[] = []) {
    const threshold = new Date(Date.now() - presenceWindowMinutes * 60 * 1000);
    return this.prisma.driver.findFirst({
      where: {
        id: excludedDriverIds.length ? { notIn: excludedDriverIds } : undefined,
        isOnline: true,
        status: AccountStatus.ACTIVE,
        updatedAt: { gte: threshold },
        user: { status: AccountStatus.ACTIVE },
      },
      include: {
        user: true,
        locations: { orderBy: { createdAt: 'desc' }, take: 1 },
      },
      orderBy: { updatedAt: 'asc' },
    });
  }

  private async countEligibleDrivers(presenceWindowMinutes: number) {
    const threshold = new Date(Date.now() - presenceWindowMinutes * 60 * 1000);
    return this.prisma.driver.count({
      where: {
        isOnline: true,
        status: AccountStatus.ACTIVE,
        updatedAt: { gte: threshold },
        user: { status: AccountStatus.ACTIVE },
      },
    });
  }

  private async findRestaurantOwnedOrder(userId: string, orderId: string) {
    const order = await this.prisma.order.findUnique({
      where: { id: orderId },
      include: this.orderDetailsInclude(),
    });
    if (!order || order.restaurant.userId !== userId) {
      throw new NotFoundException('Pedido da loja não encontrado.');
    }
    return order;
  }

  private async findRestaurantByUserId(userId: string) {
    const restaurant = await this.prisma.restaurant.findUnique({ where: { userId } });
    if (!restaurant) {
      throw new NotFoundException('Loja autenticada não encontrada.');
    }
    return restaurant;
  }

  private restaurantFlowSettingsKey(restaurantId: string) {
    return `restaurant_order_flow:${restaurantId}`;
  }

  private async readRestaurantFlowSettings(restaurantId: string): Promise<RestaurantOrderFlowSettings> {
    const persisted = await this.prisma.setting.findUnique({
      where: { key: this.restaurantFlowSettingsKey(restaurantId) },
    });
    return {
      ...defaultRestaurantOrderFlowSettings,
      ...(persisted?.value && typeof persisted.value === 'object' ? (persisted.value as object) : {}),
    } as RestaurantOrderFlowSettings;
  }

  private orderDetailsInclude() {
    return {
      customer: { include: { user: true } },
      restaurant: { include: { user: true } },
      address: true,
      delivery: { include: { driver: { include: { user: true } } } },
      items: { include: { product: true } },
      payment: true,
    } satisfies Prisma.OrderInclude;
  }

  private toCoordinates(
    latitude?: Prisma.Decimal | number | string | null,
    longitude?: Prisma.Decimal | number | string | null,
  ): { latitude: number; longitude: number } | null {
    const lat = latitude === null || latitude === undefined ? null : Number(latitude);
    const lng = longitude === null || longitude === undefined ? null : Number(longitude);
    if (lat === null || lng === null || !Number.isFinite(lat) || !Number.isFinite(lng)) {
      return null;
    }
    return { latitude: lat, longitude: lng };
  }

  private async resolveAddressCoordinates(address: {
    street?: string | null;
    number?: string | null;
    complement?: string | null;
    neighborhood?: string | null;
    city?: string | null;
    state?: string | null;
    zipCode?: string | null;
  }): Promise<{ latitude: number; longitude: number } | null> {
    const geocoded = await this.mapsService.geocodeAddressStrict(address);
    if (!geocoded) {
      return null;
    }

    return {
      latitude: geocoded.latitude,
      longitude: geocoded.longitude,
    };
  }

  private async resolveRestaurantCoordinates(restaurant: {
    id: string;
    name?: string | null;
    street?: string | null;
    number?: string | null;
    complement?: string | null;
    neighborhood?: string | null;
    city?: string | null;
    state?: string | null;
    zipCode?: string | null;
    latitude?: Prisma.Decimal | number | string | null;
    longitude?: Prisma.Decimal | number | string | null;
  }): Promise<{ latitude: number; longitude: number } | null> {
    const geocoded = await this.mapsService.geocodeAddressStrict({
      street: restaurant.street,
      number: restaurant.number,
      complement: restaurant.complement,
      neighborhood: restaurant.neighborhood,
      city: restaurant.city,
      state: restaurant.state,
      zipCode: restaurant.zipCode,
      label: restaurant.name,
    });
    if (geocoded) {
      await this.prisma.restaurant.update({
        where: { id: restaurant.id },
        data: {
          latitude: new Prisma.Decimal(geocoded.latitude),
          longitude: new Prisma.Decimal(geocoded.longitude),
        },
      });

      return {
        latitude: geocoded.latitude,
        longitude: geocoded.longitude,
      };
    }

    return this.toCoordinates(restaurant.latitude, restaurant.longitude);
  }

  private emitRestaurantOrderLifecycle(
    order: {
      id: string;
      status: OrderStatus;
      restaurant: { id: string; userId: string; name: string };
      customer: { fullName: string };
      total: Prisma.Decimal | number;
    },
    event: string,
    extra: Record<string, unknown>,
  ) {
    const payload = {
      orderId: order.id,
      restaurantId: order.restaurant.id,
      restaurantName: order.restaurant.name,
      customerName: order.customer.fullName,
      total: Number(order.total),
      status: order.status,
      ...extra,
    };
    this.ordersGateway.emitToOrder(order.id, event, payload);
    this.ordersGateway.emitToUser(order.restaurant.userId, event, payload);
  }

  private resolveWhere(user: RequestUser) {
    if (user.role === UserRole.ADMIN || user.role === UserRole.SUPER_ADMIN) {
      return {};
    }

    if (user.role === UserRole.CUSTOMER) {
      return { customer: { userId: user.sub } };
    }

    if (user.role === UserRole.RESTAURANT) {
      return { restaurant: { userId: user.sub } };
    }

    if (user.role === UserRole.DRIVER) {
      return { delivery: { driver: { userId: user.sub } } };
    }

    return { id: '__forbidden__' };
  }
}
