import { OrderService } from '../services/order/OrderService';
import db from '../config/database';

// 模拟数据库连接
jest.mock('../config/database', () => ({
  schema: {
    hasTable: jest.fn(),
    createTable: jest.fn(),
  },
  where: jest.fn().mockReturnThis(),
  first: jest.fn(),
  insert: jest.fn().mockReturnThis(),
  returning: jest.fn(),
  update: jest.fn().mockReturnThis(),
  delete: jest.fn(),
  select: jest.fn(),
  count: jest.fn().mockReturnThis(),
  offset: jest.fn().mockReturnThis(),
  limit: jest.fn().mockReturnThis(),
  orderBy: jest.fn().mockReturnThis(),
  del: jest.fn(),
  table: jest.fn().mockReturnThis(),
  from: jest.fn().mockReturnThis(),
  raw: jest.fn(),
}));

// 模拟数据库初始化
jest.mock('../config/database', () => ({
  ...jest.requireActual('../config/database'),
  default: {
    schema: {
      hasTable: jest.fn(),
      createTable: jest.fn(),
    },
    where: jest.fn().mockReturnThis(),
    first: jest.fn(),
    insert: jest.fn().mockReturnThis(),
    returning: jest.fn(),
    update: jest.fn().mockReturnThis(),
    delete: jest.fn(),
    select: jest.fn(),
    count: jest.fn().mockReturnThis(),
    offset: jest.fn().mockReturnThis(),
    limit: jest.fn().mockReturnThis(),
    orderBy: jest.fn().mockReturnThis(),
    del: jest.fn(),
    table: jest.fn().mockReturnThis(),
    from: jest.fn().mockReturnThis(),
    raw: jest.fn(),
  },
  initializeDatabase: jest.fn().mockResolvedValue(undefined),
}));


// 模拟BullMQService
jest.mock('../services/core/BullMQService', () => ({
  BullMQService: {
    addJob: jest.fn().mockResolvedValue(undefined),
  },
}));

// 模拟ConsumerOrderService
jest.mock('../domains/Trade/ConsumerOrderService', () => ({
  ConsumerOrderService: {
    transitionStatus: jest.fn().mockResolvedValue(undefined),
    batchAudit: jest.fn().mockResolvedValue({ success: 2, failed: 0 }),
    batchShip: jest.fn().mockResolvedValue({ success: 2, failed: 0 }),
    markAsException: jest.fn().mockResolvedValue(undefined),
    autoReroute: jest.fn().mockResolvedValue(undefined),
    retryException: jest.fn().mockResolvedValue(undefined),
    cancelOrder: jest.fn().mockResolvedValue(undefined),
    requestRefund: jest.fn().mockResolvedValue('refund-1'),
    approveRefund: jest.fn().mockResolvedValue(undefined),
    requestAfterSales: jest.fn().mockResolvedValue('service-1'),
    processAfterSales: jest.fn().mockResolvedValue(undefined),
    completeOrder: jest.fn().mockResolvedValue(undefined),
    initTable: jest.fn().mockResolvedValue(undefined),
  },
  ConsumerOrder: {},
}));

describe('OrderService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('createOrder', () => {
    it('should create a new order', async () => {
      const orderData = {
        tenant_id: 'tenant-1',
        shop_id: 'shop-1',
        platform: 'shopify',
        platform_order_id: 'shopify-order-1',
        customer_name: 'John Doe',
        customer_email: 'john@example.com',
        shipping_address: { street: '123 Main St', city: 'New York' },
        items: [{ skuId: 'sku-1', title: 'Product 1', price: 100, quantity: 1 }],
        total_amount: 100,
        currency: 'USD',
        status: 'PAID',
      };

      (db.insert as jest.Mock).mockReturnThis();

      const orderId = await OrderService.createOrder(orderData);

      expect(orderId).toMatch(/ORD-\d+-\d+/);
      expect(db.insert).toHaveBeenCalledWith(expect.objectContaining({
        ...orderData,
        id: expect.any(String),
        shipping_address: JSON.stringify(orderData.shipping_address),
        items: JSON.stringify(orderData.items),
        created_at: expect.any(Date),
        updated_at: expect.any(Date),
      }));
    });
  });

  describe('getOrderById', () => {
    it('should return order by id', async () => {
      const mockOrder = {
        id: 'order-1',
        tenant_id: 'tenant-1',
        platform: 'shopify',
        platform_order_id: 'shopify-order-1',
        customer_name: 'John Doe',
        customer_email: 'john@example.com',
        shipping_address: JSON.stringify({ street: '123 Main St', city: 'New York' }),
        items: JSON.stringify([{ skuId: 'sku-1', title: 'Product 1', price: 100, quantity: 1 }]),
        total_amount: 100,
        currency: 'USD',
        status: 'PAID',
      };

      (db.where as jest.Mock).mockReturnThis();
      (db.first as jest.Mock).mockResolvedValue(mockOrder);

      const result = await OrderService.getOrderById('order-1', 'tenant-1');

      expect(db.where).toHaveBeenCalledWith({ id: 'order-1', tenant_id: 'tenant-1' });
      expect(result).toEqual({
        ...mockOrder,
        shipping_address: JSON.parse(mockOrder.shipping_address),
        items: JSON.parse(mockOrder.items),
      });
    });

    it('should return null if order not found', async () => {
      (db.where as jest.Mock).mockReturnThis();
      (db.first as jest.Mock).mockResolvedValue(null);

      const result = await OrderService.getOrderById('order-1', 'tenant-1');

      expect(result).toBeNull();
    });
  });

  describe('updateOrder', () => {
    it('should update order by id', async () => {
      const updateData = { status: 'SHIPPED' };

      (db.where as jest.Mock).mockReturnThis();
      (db.update as jest.Mock).mockReturnThis();

      await OrderService.updateOrder('order-1', 'tenant-1', updateData);

      expect(db.where).toHaveBeenCalledWith({ id: 'order-1', tenant_id: 'tenant-1' });
      expect(db.update).toHaveBeenCalledWith(expect.objectContaining({
        ...updateData,
        updated_at: expect.any(Date),
      }));
    });
  });

  describe('deleteOrder', () => {
    it('should delete order by id', async () => {
      (db.where as jest.Mock).mockReturnThis();
      (db.del as jest.Mock).mockResolvedValue(1);

      await OrderService.deleteOrder('order-1', 'tenant-1');

      expect(db.where).toHaveBeenCalledWith({ id: 'order-1', tenant_id: 'tenant-1' });
      expect(db.del).toHaveBeenCalled();
    });
  });

  describe('getOrders', () => {
    it('should return orders with pagination and filters', async () => {
      const mockOrders = [
        {
          id: 'order-1',
          tenant_id: 'tenant-1',
          platform: 'shopify',
          platform_order_id: 'shopify-order-1',
          customer_name: 'John Doe',
          customer_email: 'john@example.com',
          shipping_address: JSON.stringify({ street: '123 Main St', city: 'New York' }),
          items: JSON.stringify([{ skuId: 'sku-1', title: 'Product 1', price: 100, quantity: 1 }]),
          total_amount: 100,
          currency: 'USD',
          status: 'PAID',
          created_at: new Date(),
        },
      ];

      const mockCount = { count: '1' };

      (db.where as jest.Mock).mockReturnThis();
      (db.count as jest.Mock).mockReturnThis();
      (db.first as jest.Mock).mockResolvedValue(mockCount);
      (db.offset as jest.Mock).mockReturnThis();
      (db.limit as jest.Mock).mockReturnThis();
      (db.orderBy as jest.Mock).mockReturnThis();
      (db.select as jest.Mock).mockResolvedValue(mockOrders);

      const result = await OrderService.getOrders('tenant-1', {
        page: 1,
        pageSize: 20,
        status: 'PAID',
        platform: 'shopify',
      });

      expect(result).toEqual({
        orders: mockOrders.map(order => ({
          ...order,
          shipping_address: JSON.parse(order.shipping_address),
          items: JSON.parse(order.items),
        })),
        total: 1,
      });
    });
  });

  describe('batchUpdateOrders', () => {
    it('should batch update orders', async () => {
      const orderIds = ['order-1', 'order-2'];
      const updateData = { status: 'SHIPPED' };

      (db.where as jest.Mock).mockReturnThis();
      (db.update as jest.Mock).mockResolvedValue(2);

      const result = await OrderService.batchUpdateOrders('tenant-1', orderIds, updateData);

      expect(result).toEqual({ success: 2, failed: 0 });
      expect(db.where).toHaveBeenCalledWith('id', 'in', orderIds);
      expect(db.where).toHaveBeenCalledWith('tenant_id', 'tenant-1');
      expect(db.update).toHaveBeenCalledWith(expect.objectContaining({
        ...updateData,
        updated_at: expect.any(Date),
      }));
    });
  });

  describe('transitionOrderStatus', () => {
    it('should transition order status', async () => {
      await OrderService.transitionOrderStatus('tenant-1', 'order-1', 'SHIPPED', 'Order shipped');

      expect(require('../domains/Trade/ConsumerOrderService').ConsumerOrderService.transitionStatus).toHaveBeenCalledWith(
        'tenant-1',
        'order-1',
        'SHIPPED',
        'Order shipped'
      );
    });
  });

  describe('mapPlatformPayloadToOrder', () => {
    it('should map Shopify payload to order', () => {
      const shopifyPayload = {
        id: 12345,
        name: '#12345',
        customer: {
          first_name: 'John',
          last_name: 'Doe',
          email: 'john@example.com',
        },
        shipping_address: {
          street: '123 Main St',
          city: 'New York',
          province: 'NY',
          zip: '10001',
          country: 'United States',
        },
        line_items: [
          {
            sku: 'sku-1',
            title: 'Product 1',
            price: '100.00',
            quantity: 1,
            cost_price: '50.00',
          },
        ],
        total_price: '100.00',
        currency: 'USD',
        financial_status: 'paid',
        fulfillment_status: 'unfulfilled',
      };

      const result = OrderService.mapPlatformPayloadToOrder('shopify', shopifyPayload, 'tenant-1', 'shop-1');

      expect(result).toEqual({
        tenant_id: 'tenant-1',
        shop_id: 'shop-1',
        platform: 'SHOPIFY',
        platform_order_id: '12345',
        customer_name: 'John Doe',
        customer_email: 'john@example.com',
        shipping_address: shopifyPayload.shipping_address,
        items: [
          {
            skuId: 'sku-1',
            title: 'Product 1',
            price: 100,
            quantity: 1,
            costPrice: 50,
          },
        ],
        total_amount: 100,
        currency: 'USD',
        status: 'PAID',
        payment_status: 'COMPLETED',
        fulfillment_status: 'PENDING',
        trace_id: expect.any(String),
      });
    });

    it('should map Amazon payload to order', () => {
      const amazonPayload = {
        AmazonOrderId: 'AMZ123456789',
        BuyerInfo: {
          BuyerName: 'Amazon Customer',
          BuyerEmail: 'customer@amazon.com',
        },
        ShippingAddress: {
          City: 'Seattle',
          StateOrRegion: 'WA',
          PostalCode: '98101',
          CountryCode: 'US',
        },
        OrderItems: [
          {
            SellerSKU: 'AMZ-SKU-1',
            Title: 'Amazon Product 1',
            ItemPrice: {
              Amount: '200.00',
              CurrencyCode: 'USD',
            },
            QuantityOrdered: 1,
          },
        ],
        OrderTotal: {
          Amount: '200.00',
          CurrencyCode: 'USD',
        },
        OrderStatus: 'Shipped',
      };

      const result = OrderService.mapPlatformPayloadToOrder('amazon', amazonPayload, 'tenant-1', 'shop-1');

      expect(result).toEqual({
        tenant_id: 'tenant-1',
        shop_id: 'shop-1',
        platform: 'AMAZON',
        platform_order_id: 'AMZ123456789',
        customer_name: 'Amazon Customer',
        customer_email: 'customer@amazon.com',
        shipping_address: amazonPayload.ShippingAddress,
        items: [
          {
            skuId: 'AMZ-SKU-1',
            title: 'Amazon Product 1',
            price: 200,
            quantity: 1,
            costPrice: 0,
          },
        ],
        total_amount: 200,
        currency: 'USD',
        status: 'SHIPPED',
        payment_status: 'COMPLETED',
        fulfillment_status: 'SHIPPED',
        trace_id: expect.any(String),
      });
    });

    it('should map TikTok payload to order', () => {
      const tiktokPayload = {
        order_id: 'TT123456789',
        recipient_address: {
          name: 'TikTok Customer',
          street: '456 TikTok St',
          city: 'Beijing',
          country: 'China',
        },
        item_list: [
          {
            sku_id: 'TT-SKU-1',
            product_name: 'TikTok Product 1',
            sku_sale_price: '50.00',
          },
        ],
        total_amount: '50.00',
        currency: 'CNY',
        order_status: 100,
      };

      const result = OrderService.mapPlatformPayloadToOrder('tiktok', tiktokPayload, 'tenant-1', 'shop-1');

      expect(result).toEqual({
        tenant_id: 'tenant-1',
        shop_id: 'shop-1',
        platform: 'TIKTOK',
        platform_order_id: 'TT123456789',
        customer_name: 'TikTok Customer',
        shipping_address: tiktokPayload.recipient_address,
        items: [
          {
            skuId: 'TT-SKU-1',
            title: 'TikTok Product 1',
            price: 50,
            quantity: 1,
            costPrice: 0,
          },
        ],
        total_amount: 50,
        currency: 'CNY',
        status: 'PAID',
        payment_status: 'COMPLETED',
        fulfillment_status: 'PENDING',
        trace_id: expect.any(String),
      });
    });
  });

  describe('findAlternativeProducts', () => {
    it('should find alternative products', async () => {
      const mockOriginalProduct = { id: 1, product_id: 'prod-1', price: 100 };
      const mockAlternatives = [
        { product_id: 'prod-2', name: 'Alternative 1', price: 90, stock: 10 },
        { product_id: 'prod-3', name: 'Alternative 2', price: 110, stock: 5 },
      ];

      (db.where as jest.Mock).mockReturnThis();
      (db.first as jest.Mock).mockResolvedValue(mockOriginalProduct);
      (db.select as jest.Mock).mockResolvedValue(mockAlternatives);

      const result = await OrderService.findAlternativeProducts('tenant-1', 'prod-1', {
        maxPrice: 150,
        minStock: 1,
        limit: 2,
      });

      expect(result).toEqual([
        {
          productId: 'prod-2',
          name: 'Alternative 1',
          price: 90,
          stock: 10,
          similarity: 0.8,
        },
        {
          productId: 'prod-3',
          name: 'Alternative 2',
          price: 110,
          stock: 5,
          similarity: 0.8,
        },
      ]);
    });
  });

  describe('initTables', () => {
    it('should initialize order-related tables', async () => {
      // 模拟表不存在
      (db.schema.hasTable as jest.Mock)
        .mockResolvedValueOnce(false) // cf_refunds
        .mockResolvedValueOnce(false); // cf_after_sales

      await OrderService.initTables();

      expect(require('../domains/Trade/ConsumerOrderService').ConsumerOrderService.initTable).toHaveBeenCalled();
      expect(db.schema.createTable).toHaveBeenCalledWith('cf_refunds', expect.any(Function));
      expect(db.schema.createTable).toHaveBeenCalledWith('cf_after_sales', expect.any(Function));
    });
  });

  describe('retryFailedTask', () => {
    it('should retry failed task', async () => {
      const mockTask = { id: 'task-1', retry_count: 0 };

      (db.where as jest.Mock).mockReturnThis();
      (db.first as jest.Mock).mockResolvedValue(mockTask);
      (db.update as jest.Mock).mockReturnThis();

      await OrderService.retryFailedTask('task-1');

      expect(db.where).toHaveBeenCalledWith({ id: 'task-1' });
      expect(db.update).toHaveBeenCalledWith({
        status: 'PENDING',
        retry_count: 1,
        updated_at: expect.any(Date),
      });
    });

    it('should throw error if task not found', async () => {
      (db.where as jest.Mock).mockReturnThis();
      (db.first as jest.Mock).mockResolvedValue(null);

      await expect(OrderService.retryFailedTask('task-1')).rejects.toThrow('Task not found: task-1');
    });
  });
});
