import db from '../config/database';
import { logger } from '../utils/logger';

export interface LogisticsOption {
  carrierId: string;
  carrierName: string;
  serviceType: 'STANDARD' | 'EXPRESS' | 'ECONOMY' | 'AIR_FREIGHT' | 'SEA_FREIGHT';
  estimatedDays: number;
  baseCost: number;
  reliability: number;
  trackingSupported: boolean;
}

export interface LogisticsStrategy {
  orderId: string;
  destination: string;
  recommendedOption: LogisticsOption;
  alternatives: LogisticsOption[];
  strategyReason: string;
}

export interface ChannelSelection {
  orderId: string;
  channelId: string;
  channelName: string;
  carrierId: string;
  estimatedDelivery: Date;
  cost: number;
}

export interface FreightCalculation {
  productId: string;
  quantity: number;
  destination: string;
  weight: number;
  volume: number;
  freightCost: number;
  breakdown: {
    baseFreight: number;
    fuelSurcharge: number;
    remoteAreaFee: number;
    insuranceFee: number;
  };
}

export interface LogisticsStrategyInput {
  orderId: string;
  destination: string;
  tenantId: string;
  shopId?: string;
  taskId?: string;
  traceId: string;
  businessType: 'TOC' | 'TOB';
}

/**
 * [BE-LOG001] 物流策略引擎
 * [BE-LOG002] 渠道选择算法
 * [BE-LOG003] 运费计算接口
 * 
 * @description 物流策略与渠道选择核心服务
 */
export class LogisticsService {
  private static readonly CARRIERS = [
    { id: 'DHL', name: 'DHL Express', type: 'EXPRESS', baseRate: 15, reliability: 0.95 },
    { id: 'FEDEX', name: 'FedEx', type: 'EXPRESS', baseRate: 14, reliability: 0.93 },
    { id: 'UPS', name: 'UPS', type: 'STANDARD', baseRate: 12, reliability: 0.92 },
    { id: 'SF', name: 'SF Express', type: 'EXPRESS', baseRate: 10, reliability: 0.90 },
    { id: 'EMS', name: 'EMS', type: 'STANDARD', baseRate: 8, reliability: 0.85 },
    { id: 'SEA_ECONOMY', name: 'Sea Freight Economy', type: 'SEA_FREIGHT', baseRate: 3, reliability: 0.80 },
  ];

  /**
   * [BE-LOG001] 物流策略引擎
   * 根据订单ID和目的地推荐最优物流方案
   */
  static async getLogisticsStrategy(input: LogisticsStrategyInput): Promise<LogisticsStrategy> {
    const { orderId, destination, tenantId, shopId, taskId, traceId, businessType } = input;

    logger.info(`[LogisticsService] Getting logistics strategy - orderId: ${orderId}, destination: ${destination}, tenantId: ${tenantId}, traceId: ${traceId}`);

    try {
      const order = await this.getOrderInfo(orderId, tenantId);
      
      const options = await this.evaluateLogisticsOptions(destination, order);

      const recommendedOption = this.selectOptimalOption(options, businessType);
      
      const alternatives = options.filter(o => o.carrierId !== recommendedOption.carrierId).slice(0, 3);

      const strategyReason = this.generateStrategyReason(recommendedOption, businessType);

      const strategy: LogisticsStrategy = {
        orderId,
        destination,
        recommendedOption,
        alternatives,
        strategyReason,
      };

      await this.logStrategyDecision(tenantId, orderId, strategy, traceId);

      return strategy;
    } catch (error: any) {
      logger.error(`[LogisticsService] Failed to get logistics strategy - orderId: ${orderId}, error: ${error.message}`);
      throw error;
    }
  }

  /**
   * [BE-LOG002] 渠道选择算法
   * 根据订单和物流方案选择最优渠道
   */
  static async selectChannel(
    orderId: string,
    logisticsOption: LogisticsOption,
    options: {
      tenantId: string;
      shopId?: string;
      taskId?: string;
      traceId: string;
    }
  ): Promise<ChannelSelection> {
    const { tenantId, traceId } = options;

    logger.info(`[LogisticsService] Selecting channel - orderId: ${orderId}, carrierId: ${logisticsOption.carrierId}, tenantId: ${tenantId}, traceId: ${traceId}`);

    try {
      const channel = await this.findOptimalChannel(logisticsOption, tenantId);

      const estimatedDelivery = this.calculateEstimatedDelivery(logisticsOption.estimatedDays);

      const selection: ChannelSelection = {
        orderId,
        channelId: channel.id,
        channelName: channel.name,
        carrierId: logisticsOption.carrierId,
        estimatedDelivery,
        cost: logisticsOption.baseCost,
      };

      await db('cf_logistics_channel_selections').insert({
        tenant_id: tenantId,
        order_id: orderId,
        channel_id: channel.id,
        carrier_id: logisticsOption.carrierId,
        estimated_delivery: estimatedDelivery,
        cost: logisticsOption.baseCost,
        trace_id: traceId,
        created_at: new Date(),
      });

      logger.info(`[LogisticsService] Channel selected - orderId: ${orderId}, channelId: ${channel.id}`);

      return selection;
    } catch (error: any) {
      logger.error(`[LogisticsService] Failed to select channel - orderId: ${orderId}, error: ${error.message}`);
      throw error;
    }
  }

  /**
   * [BE-LOG003] 运费计算接口
   * 根据商品ID、数量和目的地计算运费
   */
  static async calculateFreight(
    productId: string,
    quantity: number,
    destination: string,
    options: {
      tenantId: string;
      shopId?: string;
      taskId?: string;
      traceId: string;
    }
  ): Promise<FreightCalculation> {
    const { tenantId, traceId } = options;

    logger.info(`[LogisticsService] Calculating freight - productId: ${productId}, quantity: ${quantity}, destination: ${destination}, tenantId: ${tenantId}, traceId: ${traceId}`);

    try {
      const product = await this.getProductInfo(productId, tenantId);
      
      const weight = (product.weight || 0.5) * quantity;
      const volume = (product.volume || 0.001) * quantity;

      const baseFreight = this.calculateBaseFreight(weight, volume, destination);
      const fuelSurcharge = baseFreight * 0.15;
      const remoteAreaFee = this.getRemoteAreaFee(destination);
      const insuranceFee = baseFreight * 0.02;

      const freightCost = Number((baseFreight + fuelSurcharge + remoteAreaFee + insuranceFee).toFixed(2));

      const calculation: FreightCalculation = {
        productId,
        quantity,
        destination,
        weight,
        volume,
        freightCost,
        breakdown: {
          baseFreight: Number(baseFreight.toFixed(2)),
          fuelSurcharge: Number(fuelSurcharge.toFixed(2)),
          remoteAreaFee: Number(remoteAreaFee.toFixed(2)),
          insuranceFee: Number(insuranceFee.toFixed(2)),
        },
      };

      await db('cf_freight_calculations').insert({
        tenant_id: tenantId,
        product_id: productId,
        quantity,
        destination,
        weight,
        volume,
        freight_cost: freightCost,
        breakdown: JSON.stringify(calculation.breakdown),
        trace_id: traceId,
        created_at: new Date(),
      });

      logger.info(`[LogisticsService] Freight calculated - productId: ${productId}, freightCost: ${freightCost}`);

      return calculation;
    } catch (error: any) {
      logger.error(`[LogisticsService] Failed to calculate freight - productId: ${productId}, error: ${error.message}`);
      throw error;
    }
  }

  private static async getOrderInfo(orderId: string, tenantId: string): Promise<any> {
    const order = await db('cf_consumer_orders')
      .where({ id: orderId, tenant_id: tenantId })
      .first();
    
    return order || { id: orderId, weight: 1, value: 100, priority: 'NORMAL' };
  }

  private static async getProductInfo(productId: string, tenantId: string): Promise<any> {
    const product = await db('cf_product')
      .where({ id: productId, tenant_id: tenantId })
      .first();
    
    return product || { id: productId, weight: 0.5, volume: 0.001 };
  }

  private static async evaluateLogisticsOptions(destination: string, order: any): Promise<LogisticsOption[]> {
    const isRemote = this.isRemoteDestination(destination);
    const isHighValue = (order.value || 100) > 500;
    
    const options: LogisticsOption[] = this.CARRIERS.map(carrier => {
      let estimatedDays = this.getEstimatedDays(carrier.type, destination);
      let baseCost = carrier.baseRate * (order.weight || 1);
      
      if (isRemote) {
        baseCost *= 1.3;
        estimatedDays += 2;
      }
      
      if (isHighValue && carrier.type !== 'EXPRESS') {
        estimatedDays += 1;
      }

      return {
        carrierId: carrier.id,
        carrierName: carrier.name,
        serviceType: carrier.type as any,
        estimatedDays,
        baseCost: Number(baseCost.toFixed(2)),
        reliability: carrier.reliability,
        trackingSupported: carrier.type !== 'SEA_FREIGHT',
      };
    });

    return options;
  }

  private static selectOptimalOption(options: LogisticsOption[], businessType: string): LogisticsOption {
    const scoredOptions = options.map(option => {
      let score = 0;
      
      score += option.reliability * 40;
      
      score += (1 / option.estimatedDays) * 30;
      
      score += (1 / option.baseCost) * 20;
      
      if (option.trackingSupported) {
        score += 10;
      }
      
      if (businessType === 'TOB') {
        score += option.reliability * 10;
      }

      return { ...option, score };
    });

    scoredOptions.sort((a, b) => b.score - a.score);
    
    return scoredOptions[0];
  }

  private static generateStrategyReason(option: LogisticsOption, businessType: string): string {
    return `Recommended ${option.carrierName} (${option.serviceType}) for ${businessType} order. ` +
      `Estimated delivery: ${option.estimatedDays} days. ` +
      `Reliability: ${(option.reliability * 100).toFixed(0)}%. ` +
      `Cost: $${option.baseCost}.`;
  }

  private static async logStrategyDecision(
    tenantId: string,
    orderId: string,
    strategy: LogisticsStrategy,
    traceId: string
  ): Promise<void> {
    try {
      await db('cf_logistics_strategy_decisions').insert({
        tenant_id: tenantId,
        order_id: orderId,
        recommended_carrier: strategy.recommendedOption.carrierId,
        recommended_cost: strategy.recommendedOption.baseCost,
        estimated_days: strategy.recommendedOption.estimatedDays,
        strategy_reason: strategy.strategyReason,
        alternatives: JSON.stringify(strategy.alternatives),
        trace_id: traceId,
        created_at: new Date(),
      });
    } catch (error: any) {
      logger.warn(`[LogisticsService] Failed to log strategy decision: ${error.message}`);
    }
  }

  private static async findOptimalChannel(option: LogisticsOption, tenantId: string): Promise<{ id: string; name: string }> {
    const channel = await db('cf_logistics_channels')
      .where({ tenant_id: tenantId, carrier_id: option.carrierId, status: 'ACTIVE' })
      .first();

    if (channel) {
      return { id: channel.id, name: channel.name };
    }

    return {
      id: `CH-${option.carrierId}-${Date.now()}`,
      name: `${option.carrierName} Channel`,
    };
  }

  private static calculateEstimatedDelivery(days: number): Date {
    const delivery = new Date();
    delivery.setDate(delivery.getDate() + days);
    return delivery;
  }

  private static calculateBaseFreight(weight: number, volume: number, destination: string): number {
    const volumetricWeight = volume * 5000;
    const chargeableWeight = Math.max(weight, volumetricWeight);
    
    let ratePerKg = 5;
    
    if (destination.startsWith('US') || destination.startsWith('CA')) {
      ratePerKg = 8;
    } else if (destination.startsWith('EU') || destination.startsWith('UK')) {
      ratePerKg = 7;
    } else if (destination.startsWith('AU') || destination.startsWith('NZ')) {
      ratePerKg = 6;
    }

    return chargeableWeight * ratePerKg;
  }

  private static getRemoteAreaFee(destination: string): number {
    const remoteAreas = ['AK', 'HI', 'PR', 'GU', 'VI', 'NT', 'YT', 'NU'];
    const isRemote = remoteAreas.some(area => destination.includes(area));
    return isRemote ? 15 : 0;
  }

  private static isRemoteDestination(destination: string): boolean {
    const remotePatterns = ['AK', 'HI', 'PR', 'GU', 'VI', 'NT', 'YT', 'NU', 'REMOTE'];
    return remotePatterns.some(pattern => destination.toUpperCase().includes(pattern));
  }

  private static getEstimatedDays(serviceType: string, destination: string): number {
    const baseDays: Record<string, number> = {
      'EXPRESS': 3,
      'STANDARD': 7,
      'ECONOMY': 14,
      'AIR_FREIGHT': 5,
      'SEA_FREIGHT': 30,
    };

    let days = baseDays[serviceType] || 7;

    if (destination.startsWith('US') || destination.startsWith('CA')) {
      days += 2;
    } else if (destination.startsWith('EU') || destination.startsWith('UK')) {
      days += 3;
    } else if (destination.startsWith('AU') || destination.startsWith('NZ')) {
      days += 4;
    }

    return days;
  }

  static async initTables(): Promise<void> {
    logger.info('[LogisticsService] Initializing logistics tables');

    try {
      if (!(await db.schema.hasTable('cf_logistics_strategy_decisions'))) {
        await db.schema.createTable('cf_logistics_strategy_decisions', (table) => {
          table.string('id').primary().defaultTo(db.raw('uuid()'));
          table.string('tenant_id').notNullable().index();
          table.string('order_id').notNullable().index();
          table.string('recommended_carrier').notNullable();
          table.decimal('recommended_cost', 10, 2).notNullable();
          table.integer('estimated_days').notNullable();
          table.text('strategy_reason');
          table.json('alternatives');
          table.string('trace_id');
          table.timestamp('created_at').defaultTo(db.fn.now());
        });
        logger.info('[LogisticsService] Table cf_logistics_strategy_decisions created');
      }

      if (!(await db.schema.hasTable('cf_logistics_channel_selections'))) {
        await db.schema.createTable('cf_logistics_channel_selections', (table) => {
          table.string('id').primary().defaultTo(db.raw('uuid()'));
          table.string('tenant_id').notNullable().index();
          table.string('order_id').notNullable().index();
          table.string('channel_id').notNullable();
          table.string('channel_name');
          table.string('carrier_id').notNullable();
          table.date('estimated_delivery');
          table.decimal('cost', 10, 2).notNullable();
          table.string('trace_id');
          table.timestamp('created_at').defaultTo(db.fn.now());
        });
        logger.info('[LogisticsService] Table cf_logistics_channel_selections created');
      }

      if (!(await db.schema.hasTable('cf_freight_calculations'))) {
        await db.schema.createTable('cf_freight_calculations', (table) => {
          table.string('id').primary().defaultTo(db.raw('uuid()'));
          table.string('tenant_id').notNullable().index();
          table.string('product_id').notNullable().index();
          table.integer('quantity').notNullable();
          table.string('destination').notNullable();
          table.decimal('weight', 10, 3).notNullable();
          table.decimal('volume', 10, 6).notNullable();
          table.decimal('freight_cost', 10, 2).notNullable();
          table.json('breakdown');
          table.string('trace_id');
          table.timestamp('created_at').defaultTo(db.fn.now());
        });
        logger.info('[LogisticsService] Table cf_freight_calculations created');
      }

      if (!(await db.schema.hasTable('cf_logistics_channels'))) {
        await db.schema.createTable('cf_logistics_channels', (table) => {
          table.string('id').primary();
          table.string('tenant_id').notNullable().index();
          table.string('name').notNullable();
          table.string('carrier_id').notNullable().index();
          table.enum('status', ['ACTIVE', 'INACTIVE', 'SUSPENDED']).notNullable().defaultTo('ACTIVE');
          table.string('trace_id');
          table.timestamp('created_at').defaultTo(db.fn.now());
          table.timestamp('updated_at').defaultTo(db.fn.now());
        });
        logger.info('[LogisticsService] Table cf_logistics_channels created');
      }

      logger.info('[LogisticsService] Logistics tables initialized successfully');
    } catch (error: any) {
      logger.error(`[LogisticsService] Failed to initialize logistics tables - error: ${error.message}`);
      throw error;
    }
  }
}
