import { eq, and } from 'drizzle-orm';
import { db } from '../db';
import { orders, users, pharmacies, couriers } from '@shared/schema';
import { GeolocationService } from './geolocationService';
import { storage } from '../storage';
import { getWebSocketService } from '../websocket';

export interface CreateOrderData {
  medicineName: string;
  quantity: number;
  clientPhone: string;
  clientLat: number;
  clientLng: number;
  clientAddress: string;
  clientId: string;
  prescriptionUrl?: string;
}

export interface PharmacyResponse {
  orderId: string;
  action: 'accept' | 'reject';
  unitPrice?: number;
  reason?: string;
  pharmacyId: string;
}

export interface CourierResponse {
  orderId: string;
  courierId: string;
}

export class OrderService {
  private geoService = new GeolocationService();

  // Create a new SOS order
  async createOrder(orderData: CreateOrderData) {
    const orderId = Math.random().toString(36).substring(2, 15);
    
    // Create order in database
    const [order] = await db.insert(orders).values({
      clientId: orderData.clientId,
      medicineName: orderData.medicineName,
      qty: orderData.quantity,
      clientPhone: orderData.clientPhone,
      clientLat: orderData.clientLat.toString(),
      clientLng: orderData.clientLng.toString(),
      clientAddress: orderData.clientAddress,
      status: 'CREATED',
      prescriptionUrl: orderData.prescriptionUrl
    }).returning();

    // Find nearby pharmacies
    const nearbyPharmacies = await this.findNearbyPharmacies(
      orderData.clientLat, 
      orderData.clientLng,
      10 // 10km radius
    );

    if (nearbyPharmacies.length === 0) {
      // Update order status to indicate no pharmacies found
      await db.update(orders)
        .set({ 
          status: 'CLOSED',
          updatedAt: new Date()
        })
        .where(eq(orders.id, order.id));

      throw new Error('Aucune pharmacie disponible dans votre zone');
    }

    // Notify nearby pharmacies via WebSocket
    this.notifyNearbyPharmacies(order.id, nearbyPharmacies);

    // Auto-assign to closest pharmacy after 2 minutes if no response
    setTimeout(() => {
      this.autoAssignPharmacy(order.id);
    }, 2 * 60 * 1000);

    return {
      orderId: order.id,
      nearbyPharmacies: nearbyPharmacies.slice(0, 5), // Return top 5
      estimatedDeliveryTime: this.calculateEstimatedDeliveryTime(nearbyPharmacies[0]?.duration || 20)
    };
  }

  // Pharmacy responds to order
  async handlePharmacyResponse(response: PharmacyResponse) {
    const order = await this.getOrder(response.orderId);
    if (!order) {
      throw new Error('Commande introuvable');
    }

    if (order.status !== 'CREATED') {
      throw new Error('Cette commande a déjà été traitée');
    }

    if (response.action === 'accept') {
      // Accept order
      await db.update(orders)
        .set({
          status: 'PHARMACY_CONFIRMED',
          pharmacyId: response.pharmacyId,
          medPriceTotal: response.unitPrice || 0,
          updatedAt: new Date()
        })
        .where(eq(orders.id, response.orderId));

      // Find nearby couriers
      const pharmacy = await this.getPharmacy(response.pharmacyId);
      if (pharmacy) {
        const nearbyCouriers = await this.findNearbyCouriers(
          parseFloat(pharmacy.lat!),
          parseFloat(pharmacy.lng!),
          5 // 5km radius
        );

        // Auto-assign courier if available
        if (nearbyCouriers.length > 0) {
          await this.assignCourier(response.orderId, nearbyCouriers[0].id);
        }
      }

      // Notify client and pharmacy
      this.notifyOrderUpdate(response.orderId, 'PHARMACY_CONFIRMED', {
        pharmacyName: pharmacy?.name,
        unitPrice: response.unitPrice
      });

    } else {
      // Reject order - try next pharmacy
      await this.reassignToNextPharmacy(response.orderId, response.pharmacyId);
    }

    return { success: true };
  }

  // Assign courier to order
  async assignCourier(orderId: string, courierId: string) {
    await db.update(orders)
      .set({
        status: 'COURIER_ASSIGNED',
        courierId: courierId,
        updatedAt: new Date()
      })
      .where(eq(orders.id, orderId));

    const courier = await this.getCourier(courierId);
    
    this.notifyOrderUpdate(orderId, 'COURIER_ASSIGNED', {
      courierName: courier?.name,
      courierPhone: courier?.phone
    });

    return { success: true };
  }

  // Update order status
  async updateOrderStatus(orderId: string, newStatus: string, details?: any) {
    // Cast newStatus to the correct enum type
    const statusValue = newStatus as 'CREATED' | 'PHARMACY_CONFIRMED' | 'COURIER_ASSIGNED' | 'PICKED_UP' | 'DELIVERED' | 'CLOSED';
    
    await db.update(orders)
      .set({
        status: statusValue,
        updatedAt: new Date()
      })
      .where(eq(orders.id, orderId));

    this.notifyOrderUpdate(orderId, newStatus, details);

    // Handle status-specific logic
    switch (newStatus) {
      case 'PICKED_UP':
        // Calculate estimated delivery time (client-side calculation)
        break;
      
      case 'DELIVERED':
        // Mark order as completed
        await db.update(orders)
          .set({ 
            status: 'DELIVERED',
            updatedAt: new Date()
          })
          .where(eq(orders.id, orderId));
        break;
    }

    return { success: true };
  }

  // Get order details with all related data
  async getOrderDetails(orderId: string) {
    const order = await storage.getOrderWithDetails(orderId);

    if (!order) {
      throw new Error('Commande introuvable');
    }

    return order;
  }

  // Private helper methods
  private async getOrder(orderId: string) {
    const [order] = await db.select().from(orders).where(eq(orders.id, orderId));
    return order;
  }

  private async getPharmacy(pharmacyId: string) {
    // Get pharmacy with user details
    const [pharmacy] = await db.select()
      .from(pharmacies)
      .innerJoin(users, eq(pharmacies.userId, users.id))
      .where(eq(pharmacies.id, pharmacyId));
    
    return pharmacy ? {
      ...pharmacy.pharmacies,
      name: pharmacy.users.name,
      phone: pharmacy.users.phone
    } : null;
  }

  private async getCourier(courierId: string) {
    // Get courier with user details  
    const [courier] = await db.select()
      .from(couriers)
      .innerJoin(users, eq(couriers.userId, users.id))
      .where(eq(couriers.id, courierId));
    
    return courier ? {
      ...courier.couriers,
      name: courier.users.name,
      phone: courier.users.phone
    } : null;
  }

  private async findNearbyPharmacies(lat: number, lng: number, radiusKm: number) {
    // Get all pharmacies with user details
    const pharmaciesData = await db.select()
      .from(pharmacies)
      .innerJoin(users, eq(pharmacies.userId, users.id))
      .where(eq(users.role, 'pharmacy'));

    const pharmacyPoints = pharmaciesData.map((p: any) => ({ 
      id: p.pharmacies.id, 
      lat: parseFloat(p.pharmacies.lat!), 
      lng: parseFloat(p.pharmacies.lng!),
      name: p.users.name 
    }));

    return this.geoService.findNearbyPoints(
      { lat, lng },
      pharmacyPoints,
      radiusKm
    );
  }

  private async findNearbyCouriers(lat: number, lng: number, radiusKm: number) {
    // Get all available couriers with user details
    const couriersData = await db.select()
      .from(couriers)
      .innerJoin(users, eq(couriers.userId, users.id))
      .where(and(
        eq(users.role, 'courier'),
        eq(couriers.available, true)
      ));

    const courierPoints = couriersData
      .filter(c => c.couriers.lat && c.couriers.lng)
      .map(c => ({ 
        id: c.couriers.id, 
        lat: parseFloat(c.couriers.lat!), 
        lng: parseFloat(c.couriers.lng!),
        name: c.users.name 
      }));

    return this.geoService.findNearbyPoints(
      { lat, lng },
      courierPoints,
      radiusKm
    );
  }

  private async autoAssignPharmacy(orderId: string) {
    const order = await this.getOrder(orderId);
    if (!order || order.status !== 'CREATED') {
      return; // Order was already handled
    }

    const nearbyPharmacies = await this.findNearbyPharmacies(
      parseFloat(order.clientLat), 
      parseFloat(order.clientLng), 
      10
    );

    if (nearbyPharmacies.length > 0) {
      // Auto-assign to closest pharmacy
      await this.handlePharmacyResponse({
        orderId,
        action: 'accept',
        pharmacyId: nearbyPharmacies[0].id,
        unitPrice: 1000 // Default price
      });
    } else {
      // No pharmacies available
      await db.update(orders)
        .set({ status: 'CLOSED' })
        .where(eq(orders.id, orderId));

      this.notifyOrderUpdate(orderId, 'CLOSED', {
        message: 'Aucune pharmacie disponible dans votre zone'
      });
    }
  }

  private async reassignToNextPharmacy(orderId: string, rejectedPharmacyId: string) {
    const order = await this.getOrder(orderId);
    if (!order) return;

    const nearbyPharmacies = await this.findNearbyPharmacies(
      parseFloat(order.clientLat), 
      parseFloat(order.clientLng), 
      10
    );

    const nextPharmacies = nearbyPharmacies.filter(p => p.id !== rejectedPharmacyId);
    
    if (nextPharmacies.length > 0) {
      // Notify next pharmacy
      const wsService = getWebSocketService();
      if (wsService) {
        wsService.sendToUser(nextPharmacies[0].id, {
          type: 'new_order',
          data: order,
          timestamp: new Date().toISOString()
        });
      }
    } else {
      // No more pharmacies available
      await db.update(orders)
        .set({ status: 'CLOSED' })
        .where(eq(orders.id, orderId));

      this.notifyOrderUpdate(orderId, 'CLOSED', {
        message: 'Aucune pharmacie disponible dans votre zone'
      });
    }
  }

  private notifyNearbyPharmacies(orderId: string, pharmacies: any[]) {
    const wsService = getWebSocketService();
    if (!wsService) return;

    pharmacies.forEach(pharmacy => {
      wsService.sendToUser(pharmacy.id, {
        type: 'new_order',
        data: { orderId, medicineName: 'Nouvelle commande SOS' },
        timestamp: new Date().toISOString()
      });
    });
  }

  private notifyOrderUpdate(orderId: string, status: string, details?: any) {
    const wsService = getWebSocketService();
    if (!wsService) return;

    // Get all users involved in this order
    this.getOrder(orderId).then(order => {
      if (!order) return;

      const userIds = [order.clientId];
      if (order.pharmacyId) userIds.push(order.pharmacyId);
      if (order.courierId) userIds.push(order.courierId);

      wsService.notifyOrderStatusChange(orderId, status, userIds, details);
    });
  }

  private calculateEstimatedDeliveryTime(pharmacyDistanceMinutes: number): string {
    const now = new Date();
    const preparationTime = 10; // 10 minutes to prepare
    const deliveryTime = pharmacyDistanceMinutes + 10; // Distance + 10 min delivery
    const totalMinutes = preparationTime + deliveryTime;
    
    const estimatedTime = new Date(now.getTime() + totalMinutes * 60 * 1000);
    return estimatedTime.toLocaleTimeString('fr-FR', { 
      hour: '2-digit', 
      minute: '2-digit' 
    });
  }
}

export const orderService = new OrderService();