import { type OrderStatus } from '@prisma/client';
import { PrismaOrderRepository } from '../repositories/prisma-order.repository';

describe('PrismaOrderRepository', () => {
  let repository: PrismaOrderRepository;
  let mockPrisma: {
    order: {
      findUnique: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
      count: ReturnType<typeof vi.fn>;
      create: ReturnType<typeof vi.fn>;
      update: ReturnType<typeof vi.fn>;
    };
  };

  const now = new Date('2026-04-01T10:00:00Z');
  const later = new Date('2026-04-01T10:05:00Z');

  const mockPrismaOrder = {
    id: 'order-1',
    buyerId: 'buyer-1',
    sellerId: 'seller-1',
    listingId: 'listing-1',
    status: 'CREATED' as OrderStatus,
    amountVND: 500_000_000n,
    platformFeeVND: 25_000_000n,
    sellerPayoutVND: 475_000_000n,
    idempotencyKey: 'idem-key-1',
    metadata: { note: 'test order' },
    createdAt: now,
    updatedAt: later,
  };

  beforeEach(() => {
    mockPrisma = {
      order: {
        findUnique: vi.fn(),
        findMany: vi.fn(),
        count: vi.fn(),
        create: vi.fn(),
        update: vi.fn(),
      },
    };
    repository = new PrismaOrderRepository(mockPrisma as any);
  });

  describe('findById', () => {
    it('returns domain entity when order exists', async () => {
      mockPrisma.order.findUnique.mockResolvedValue(mockPrismaOrder);

      const result = await repository.findById('order-1');

      expect(mockPrisma.order.findUnique).toHaveBeenCalledWith({
        where: { id: 'order-1' },
      });
      expect(result).not.toBeNull();
      expect(result!.id).toBe('order-1');
      expect(result!.buyerId).toBe('buyer-1');
      expect(result!.sellerId).toBe('seller-1');
      expect(result!.listingId).toBe('listing-1');
      expect(result!.status).toBe('CREATED');
      expect(result!.amount.value).toBe(500_000_000n);
      expect(result!.platformFee.value).toBe(25_000_000n);
      expect(result!.sellerPayout.value).toBe(475_000_000n);
      expect(result!.idempotencyKey).toBe('idem-key-1');
    });

    it('returns null when order does not exist', async () => {
      mockPrisma.order.findUnique.mockResolvedValue(null);

      const result = await repository.findById('nonexistent');

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

  describe('findByIdempotencyKey', () => {
    it('returns domain entity when key matches', async () => {
      mockPrisma.order.findUnique.mockResolvedValue(mockPrismaOrder);

      const result = await repository.findByIdempotencyKey('idem-key-1');

      expect(mockPrisma.order.findUnique).toHaveBeenCalledWith({
        where: { idempotencyKey: 'idem-key-1' },
      });
      expect(result).not.toBeNull();
      expect(result!.id).toBe('order-1');
      expect(result!.idempotencyKey).toBe('idem-key-1');
    });

    it('returns null when key does not match', async () => {
      mockPrisma.order.findUnique.mockResolvedValue(null);

      const result = await repository.findByIdempotencyKey('unknown-key');

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

  describe('findByBuyerId', () => {
    it('returns paginated items and total count', async () => {
      const orders = [
        { ...mockPrismaOrder, id: 'order-2' },
        { ...mockPrismaOrder, id: 'order-1' },
      ];
      mockPrisma.order.findMany.mockResolvedValue(orders);
      mockPrisma.order.count.mockResolvedValue(5);

      const result = await repository.findByBuyerId('buyer-1', {
        limit: 2,
        offset: 0,
      });

      expect(mockPrisma.order.findMany).toHaveBeenCalledWith({
        where: { buyerId: 'buyer-1' },
        orderBy: { createdAt: 'desc' },
        take: 2,
        skip: 0,
      });
      expect(mockPrisma.order.count).toHaveBeenCalledWith({
        where: { buyerId: 'buyer-1' },
      });
      expect(result.items).toHaveLength(2);
      expect(result.total).toBe(5);
      expect(result.items[0]!.id).toBe('order-2');
    });

    it('filters by status when provided', async () => {
      mockPrisma.order.findMany.mockResolvedValue([]);
      mockPrisma.order.count.mockResolvedValue(0);

      await repository.findByBuyerId('buyer-1', { status: 'PAYMENT_PENDING' });

      expect(mockPrisma.order.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { buyerId: 'buyer-1', status: 'PAYMENT_PENDING' },
        }),
      );
    });

    it('uses default limit and offset when not provided', async () => {
      mockPrisma.order.findMany.mockResolvedValue([]);
      mockPrisma.order.count.mockResolvedValue(0);

      await repository.findByBuyerId('buyer-1');

      expect(mockPrisma.order.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          take: 20,
          skip: 0,
        }),
      );
    });
  });

  describe('findBySellerId', () => {
    it('returns paginated items and total count', async () => {
      const orders = [mockPrismaOrder];
      mockPrisma.order.findMany.mockResolvedValue(orders);
      mockPrisma.order.count.mockResolvedValue(1);

      const result = await repository.findBySellerId('seller-1', {
        limit: 10,
        offset: 0,
      });

      expect(mockPrisma.order.findMany).toHaveBeenCalledWith({
        where: { sellerId: 'seller-1' },
        orderBy: { createdAt: 'desc' },
        take: 10,
        skip: 0,
      });
      expect(result.items).toHaveLength(1);
      expect(result.total).toBe(1);
      expect(result.items[0]!.sellerId).toBe('seller-1');
    });

    it('filters by status when provided', async () => {
      mockPrisma.order.findMany.mockResolvedValue([]);
      mockPrisma.order.count.mockResolvedValue(0);

      await repository.findBySellerId('seller-1', { status: 'ESCROW_HELD' });

      expect(mockPrisma.order.findMany).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { sellerId: 'seller-1', status: 'ESCROW_HELD' },
        }),
      );
    });
  });

  describe('save', () => {
    it('persists a new order with correct field mapping', async () => {
      mockPrisma.order.create.mockResolvedValue(mockPrismaOrder);

      // Reconstruct a domain entity to pass to save
      const { OrderEntity } = await import('../../domain/entities/order.entity');
      const { Money } = await import('../../domain/value-objects/money.vo');
      const { PlatformFee } = await import('../../domain/value-objects/platform-fee.vo');

      const amount = Money.create(500_000_000n).unwrap();
      const fee = PlatformFee.create(25_000_000n).unwrap();
      const payout = Money.create(475_000_000n).unwrap();

      const entity = new OrderEntity('order-1', {
        buyerId: 'buyer-1',
        sellerId: 'seller-1',
        listingId: 'listing-1',
        status: 'CREATED',
        amount,
        platformFee: fee,
        sellerPayout: payout,
        idempotencyKey: 'idem-key-1',
        metadata: { note: 'test' },
      });

      await repository.save(entity);

      expect(mockPrisma.order.create).toHaveBeenCalledWith({
        data: {
          id: 'order-1',
          buyerId: 'buyer-1',
          sellerId: 'seller-1',
          listingId: 'listing-1',
          status: 'CREATED',
          amountVND: 500_000_000n,
          platformFeeVND: 25_000_000n,
          sellerPayoutVND: 475_000_000n,
          idempotencyKey: 'idem-key-1',
          metadata: { note: 'test' },
        },
      });
    });
  });

  describe('update', () => {
    it('updates status and metadata', async () => {
      mockPrisma.order.update.mockResolvedValue({
        ...mockPrismaOrder,
        status: 'PAYMENT_PENDING',
        metadata: { note: 'updated' },
      });

      const { OrderEntity } = await import('../../domain/entities/order.entity');
      const { Money } = await import('../../domain/value-objects/money.vo');
      const { PlatformFee } = await import('../../domain/value-objects/platform-fee.vo');

      const amount = Money.create(500_000_000n).unwrap();
      const fee = PlatformFee.create(25_000_000n).unwrap();
      const payout = Money.create(475_000_000n).unwrap();

      const entity = new OrderEntity('order-1', {
        buyerId: 'buyer-1',
        sellerId: 'seller-1',
        listingId: 'listing-1',
        status: 'PAYMENT_PENDING',
        amount,
        platformFee: fee,
        sellerPayout: payout,
        idempotencyKey: 'idem-key-1',
        metadata: { note: 'updated' },
      });

      await repository.update(entity);

      expect(mockPrisma.order.update).toHaveBeenCalledWith({
        where: { id: 'order-1' },
        data: {
          status: 'PAYMENT_PENDING',
          metadata: { note: 'updated' },
        },
      });
    });
  });

  describe('toDomain mapping', () => {
    it('correctly maps bigint amountVND to Money value object', async () => {
      mockPrisma.order.findUnique.mockResolvedValue({
        ...mockPrismaOrder,
        amountVND: 999_999_999_999n,
      });

      const result = await repository.findById('order-1');

      expect(result!.amount.value).toBe(999_999_999_999n);
    });

    it('preserves null idempotencyKey', async () => {
      mockPrisma.order.findUnique.mockResolvedValue({
        ...mockPrismaOrder,
        idempotencyKey: null,
      });

      const result = await repository.findById('order-1');

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

    it('preserves createdAt and updatedAt timestamps', async () => {
      mockPrisma.order.findUnique.mockResolvedValue(mockPrismaOrder);

      const result = await repository.findById('order-1');

      expect(result!.createdAt).toEqual(now);
      expect(result!.updatedAt).toEqual(later);
    });
  });
});
