import prisma from '../../../config/database';
import { ShippingProviderFactory } from './shipping-provider.factory';
import { logger } from '../../../config/logger';
import { Decimal } from '@prisma/client/runtime/library';

export class ShippingService {
  /**
   * 1. CREATE SHIPPING ORDER (ORDER SYNC)
   */
  static async syncOrderToShipping(orderId: string) {
    const order = await prisma.order.findUnique({
      where: { id: orderId },
      include: {
        shippingAddress: true,
        user: true,
      },
    });

    if (!order) throw new Error('Order not found');

    // Get default shipping provider configured
    let providerConfig = await prisma.shippingProvider.findFirst({
      where: { isEnabled: true, defaultProvider: true },
    });

    if (!providerConfig) {
      providerConfig = await prisma.shippingProvider.findFirst({
        where: { isEnabled: true },
        orderBy: { priority: 'asc' },
      });
    }

    if (!providerConfig) {
      throw new Error('No active shipping provider configured in settings.');
    }

    const providerClient = await ShippingProviderFactory.getProvider(providerConfig.providerName);

    // Prepare details for delivery sync
    const address = order.shippingAddress;
    const recipientAddressStr = address.street;

    const shipmentDetails = {
      orderId: order.id,
      pickupLocation: providerConfig.pickupLocation || 'Warehouse-Main',
      weight: 0.85, // Default weight estimate
      length: 15,
      width: 15,
      height: 10,
      recipientName: address.fullName,
      recipientAddress: recipientAddressStr,
      recipientCity: address.city,
      recipientState: address.state,
      recipientPincode: address.postalCode,
      recipientPhone: address.phone,
    };

    const syncRes = await providerClient.createShipment(shipmentDetails);

    if (!syncRes.success) {
      throw new Error(syncRes.errorDetails || 'Failed to sync shipping with provider');
    }

    // Save ShippingOrder to database
    const shippingOrder = await prisma.shippingOrder.create({
      data: {
        orderId: order.id,
        providerId: providerConfig.id,
        shipmentId: syncRes.shipmentId || `mnl_ship_${Date.now()}`,
        status: syncRes.status || 'PENDING',
        estimatedDelivery: syncRes.estimatedDelivery || null,
        shippingCharges: new Decimal(order.shippingFee || 0.0),
        weight: new Decimal(shipmentDetails.weight),
        dimensionsLength: shipmentDetails.length,
        dimensionsWidth: shipmentDetails.width,
        dimensionsHeight: shipmentDetails.height,
      },
    });

    // Create initial Tracking Log
    await prisma.shipmentTracking.create({
      data: {
        shippingOrderId: shippingOrder.id,
        status: 'ORDERED',
        location: providerConfig.warehouseName || 'Warehouse Handoff',
        eventDetails: 'Order confirmed and synced for packaging.',
      },
    });

    // Audit logs entry
    await prisma.shippingAudit.create({
      data: {
        providerId: providerConfig.id,
        action: 'ORDER_SYNCED',
        performedBy: 'SYSTEM',
        details: `Synced order ${order.orderNumber} with provider: ${providerConfig.displayName}`,
      },
    });

    logger.info(`Synced order: ${order.orderNumber} to shipping order ID: ${shippingOrder.id}`);
    return shippingOrder;
  }

  /**
   * 2. GENERATE AWB & ASSIGN COURIER
   */
  static async generateAwb(shippingOrderId: string) {
    const shippingOrder = await prisma.shippingOrder.findUnique({
      where: { id: shippingOrderId },
      include: { provider: true },
    });

    if (!shippingOrder) throw new Error('Shipping order not found');

    const providerClient = await ShippingProviderFactory.getProvider(shippingOrder.provider.providerName);
    const awbRes = await providerClient.generateAwb(shippingOrder.shipmentId || '');

    if (!awbRes.success) {
      throw new Error(awbRes.courierName || 'AWB dispatch assignment failed');
    }

    const updated = await prisma.shippingOrder.update({
      where: { id: shippingOrderId },
      data: {
        awbNumber: awbRes.awbNumber,
        courierName: awbRes.courierName,
        status: 'READY_TO_SHIP',
      },
    });

    // Append event timeline
    await prisma.shipmentTracking.create({
      data: {
        shippingOrderId,
        status: 'READY_TO_SHIP',
        location: shippingOrder.provider.warehouseName,
        eventDetails: `AWB assigned: ${awbRes.awbNumber} via ${awbRes.courierName}. Ready to ship.`,
      },
    });

    // Save label URL attachment
    const labelRes = await providerClient.generateLabel(shippingOrder.shipmentId || '');
    if (labelRes.success) {
      await prisma.shippingLabel.create({
        data: {
          shippingOrderId,
          labelUrl: labelRes.labelUrl,
          format: 'PDF',
        },
      });
    }

    // Save commercial shipping invoice
    const invoiceRes = await providerClient.generateInvoice(shippingOrder.shipmentId || '');
    if (invoiceRes.success) {
      await prisma.shippingInvoice.create({
        data: {
          shippingOrderId,
          invoiceUrl: invoiceRes.invoiceUrl,
          invoiceType: 'SHIPPING',
        },
      });
    }

    return updated;
  }

  /**
   * 3. DISPATCH PICKUP SCHEDULE
   */
  static async schedulePickup(shippingOrderId: string, pickupTime: Date) {
    const shippingOrder = await prisma.shippingOrder.findUnique({
      where: { id: shippingOrderId },
      include: { provider: true },
    });

    if (!shippingOrder || !shippingOrder.awbNumber) {
      throw new Error('Shipping order not found or AWB number missing.');
    }

    const providerClient = await ShippingProviderFactory.getProvider(shippingOrder.provider.providerName);
    const pickupRes = await providerClient.requestPickup(shippingOrder.awbNumber, pickupTime);

    if (!pickupRes.success) {
      throw new Error(pickupRes.status || 'Failed to schedule pickup');
    }

    // Create Pickup Request
    const pickup = await prisma.pickupRequest.create({
      data: {
        shippingOrderId,
        pickupToken: pickupRes.pickupToken,
        pickupTime,
        status: pickupRes.status,
      },
    });

    // Update shipping status
    await prisma.shippingOrder.update({
      where: { id: shippingOrderId },
      data: { status: 'PICKUP_SCHEDULED' },
    });

    // Append timeline log
    await prisma.shipmentTracking.create({
      data: {
        shippingOrderId,
        status: 'READY_TO_SHIP',
        location: shippingOrder.provider.warehouseName,
        eventDetails: `Pickup scheduled token: ${pickupRes.pickupToken} for timing: ${pickupTime.toISOString()}`,
      },
    });

    return pickup;
  }

  /**
   * 4. CANCEL SHIPMENT DISPATCH
   */
  static async cancelShipment(shippingOrderId: string) {
    const shippingOrder = await prisma.shippingOrder.findUnique({
      where: { id: shippingOrderId },
      include: { provider: true, pickups: true },
    });

    if (!shippingOrder) throw new Error('Shipping order not found');

    const providerClient = await ShippingProviderFactory.getProvider(shippingOrder.provider.providerName);

    // Cancel pickups
    for (const pickup of shippingOrder.pickups) {
      if (pickup.pickupToken) {
        await providerClient.cancelPickup(pickup.pickupToken);
        await prisma.pickupRequest.update({
          where: { id: pickup.id },
          data: { status: 'CANCELLED' },
        });
      }
    }

    // Update status
    await prisma.shippingOrder.update({
      where: { id: shippingOrderId },
      data: { status: 'CANCELLED' },
    });

    // Add cancel timeline track
    await prisma.shipmentTracking.create({
      data: {
        shippingOrderId,
        status: 'CANCELLED',
        eventDetails: 'Shipment cancellation requested by admin.',
      },
    });

    return { success: true };
  }

  /**
   * 5. TRACK SHIPMENT (REALTIME API POLL)
   */
  static async trackShipment(awbNumber: string) {
    const shippingOrder = await prisma.shippingOrder.findFirst({
      where: { awbNumber },
      include: { provider: true },
    });

    if (!shippingOrder) throw new Error('Shipping order not found for this tracking number');

    const providerClient = await ShippingProviderFactory.getProvider(shippingOrder.provider.providerName);
    const trackingRes = await providerClient.trackShipment(awbNumber);

    return trackingRes;
  }
}
