import { InternalServerErrorException } from '@nestjs/common';
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
import { DomainException } from '@modules/shared';
import {
  type IAVMService,
  type BatchValuationResult,
  type ValuationResult,
} from '../../domain/services/avm-service';
import { BatchValuationHandler } from '../queries/batch-valuation/batch-valuation.handler';
import { BatchValuationQuery } from '../queries/batch-valuation/batch-valuation.query';

describe('BatchValuationHandler', () => {
  let handler: BatchValuationHandler;
  let mockAvm: { [K in keyof IAVMService]: ReturnType<typeof vi.fn> };
  let mockLogger: { error: ReturnType<typeof vi.fn> };

  const sampleValuation: ValuationResult = {
    estimatedPrice: '5000000000',
    confidence: 0.85,
    pricePerM2: 75000000,
    comparables: [],
    modelVersion: 'avm-v1.0',
  };

  const sampleBatchResult: BatchValuationResult[] = [
    { propertyId: 'prop-1', valuation: sampleValuation },
    { propertyId: 'prop-2', valuation: sampleValuation },
  ];

  beforeEach(() => {
    mockAvm = {
      estimateValue: vi.fn(),
      getComparables: vi.fn(),
      estimateBatch: vi.fn(),
    };
    mockLogger = { error: vi.fn() };
    const mockCache = {
      getOrSet: vi.fn((_key: string, loader: () => Promise<unknown>) => loader()),
    } as unknown as CacheService;
    handler = new BatchValuationHandler(
      mockAvm as any,
      mockCache,
      mockLogger as any,
    );
  });

  it('returns batch valuation results for multiple properties', async () => {
    mockAvm.estimateBatch.mockResolvedValue(sampleBatchResult);

    const query = new BatchValuationQuery(['prop-1', 'prop-2']);
    const result = await handler.execute(query);

    expect(result).toHaveLength(2);
    expect(result[0]!.propertyId).toBe('prop-1');
    expect(result[0]!.valuation!.estimatedPrice).toBe('5000000000');
    expect(result[1]!.propertyId).toBe('prop-2');
    expect(mockAvm.estimateBatch).toHaveBeenCalledWith([
      { propertyId: 'prop-1' },
      { propertyId: 'prop-2' },
    ]);
  });

  it('handles partial failures in batch results', async () => {
    const partialResult: BatchValuationResult[] = [
      { propertyId: 'prop-1', valuation: sampleValuation },
      { propertyId: 'prop-2', valuation: null, error: 'Not found' },
    ];
    mockAvm.estimateBatch.mockResolvedValue(partialResult);

    const query = new BatchValuationQuery(['prop-1', 'prop-2']);
    const result = await handler.execute(query);

    expect(result).toHaveLength(2);
    expect(result[0]!.valuation).not.toBeNull();
    expect(result[1]!.valuation).toBeNull();
    expect(result[1]!.error).toBe('Not found');
  });

  it('re-throws DomainException directly', async () => {
    const domainError = new DomainException('VALIDATION_ERROR', 'Invalid input');
    mockAvm.estimateBatch.mockRejectedValue(domainError);

    const query = new BatchValuationQuery(['prop-1']);

    await expect(handler.execute(query)).rejects.toThrow(DomainException);
  });

  it('wraps unexpected errors in InternalServerErrorException', async () => {
    mockAvm.estimateBatch.mockRejectedValue(new Error('Database timeout'));

    const query = new BatchValuationQuery(['prop-1']);

    await expect(handler.execute(query)).rejects.toThrow(InternalServerErrorException);
    expect(mockLogger.error).toHaveBeenCalled();
  });

  it('passes single property batch correctly', async () => {
    const singleResult: BatchValuationResult[] = [
      { propertyId: 'prop-solo', valuation: sampleValuation },
    ];
    mockAvm.estimateBatch.mockResolvedValue(singleResult);

    const query = new BatchValuationQuery(['prop-solo']);
    const result = await handler.execute(query);

    expect(result).toHaveLength(1);
    expect(result[0]!.propertyId).toBe('prop-solo');
  });
});
