/**
 * RateSearchService
 *
 * Domain service implementing the rate search business logic
 *
 * Business Rules:
 * - Query multiple carriers in parallel
 * - Cache results for 15 minutes
 * - Handle carrier timeouts gracefully (5s max)
 * - Return results even if some carriers fail
 */

import { RateQuote } from '../entities/rate-quote.entity';
import {
  SearchRatesPort,
  RateSearchInput,
  RateSearchOutput,
} from '@domain/ports/in/search-rates.port';
import { CarrierConnectorPort } from '@domain/ports/out/carrier-connector.port';
import { CachePort } from '@domain/ports/out/cache.port';
import { RateQuoteRepository } from '@domain/ports/out/rate-quote.repository';
import { PortRepository } from '@domain/ports/out/port.repository';
import { CarrierRepository } from '@domain/ports/out/carrier.repository';
import { PortNotFoundException } from '../exceptions/port-not-found.exception';
import { v4 as uuidv4 } from 'uuid';

export class RateSearchService implements SearchRatesPort {
  private static readonly CACHE_TTL_SECONDS = 15 * 60; // 15 minutes

  constructor(
    private readonly carrierConnectors: CarrierConnectorPort[],
    private readonly cache: CachePort,
    private readonly rateQuoteRepository: RateQuoteRepository,
    private readonly portRepository: PortRepository,
    private readonly carrierRepository: CarrierRepository
  ) {}

  async execute(input: RateSearchInput): Promise<RateSearchOutput> {
    const searchId = uuidv4();
    const searchedAt = new Date();

    // Validate ports exist
    await this.validatePorts(input.origin, input.destination);

    // Generate cache key
    const cacheKey = this.generateCacheKey(input);

    // Check cache first
    const cachedResults = await this.cache.get<RateSearchOutput>(cacheKey);
    if (cachedResults) {
      return cachedResults;
    }

    // Filter carriers if preferences specified
    const connectorsToQuery = this.filterCarrierConnectors(input.carrierPreferences);

    // Query all carriers in parallel with Promise.allSettled
    const carrierResults = await Promise.allSettled(
      connectorsToQuery.map(connector => this.queryCarrier(connector, input))
    );

    // Process results
    const quotes: RateQuote[] = [];
    const carrierResultsSummary: RateSearchOutput['carrierResults'] = [];

    for (let i = 0; i < carrierResults.length; i++) {
      const result = carrierResults[i];
      const connector = connectorsToQuery[i];
      const carrierName = connector.getCarrierName();

      if (result.status === 'fulfilled') {
        const carrierQuotes = result.value;
        quotes.push(...carrierQuotes);

        carrierResultsSummary.push({
          carrierName,
          status: 'success',
          resultCount: carrierQuotes.length,
        });
      } else {
        // Handle error
        const error = result.reason;
        carrierResultsSummary.push({
          carrierName,
          status: error.name === 'CarrierTimeoutException' ? 'timeout' : 'error',
          resultCount: 0,
          errorMessage: error.message,
        });
      }
    }

    // Save rate quotes to database
    if (quotes.length > 0) {
      await this.rateQuoteRepository.saveMany(quotes);
    }

    // Build output
    const output: RateSearchOutput = {
      quotes,
      searchId,
      searchedAt,
      totalResults: quotes.length,
      carrierResults: carrierResultsSummary,
    };

    // Cache results
    await this.cache.set(cacheKey, output, RateSearchService.CACHE_TTL_SECONDS);

    return output;
  }

  private async validatePorts(originCode: string, destinationCode: string): Promise<void> {
    const [origin, destination] = await Promise.all([
      this.portRepository.findByCode(originCode),
      this.portRepository.findByCode(destinationCode),
    ]);

    if (!origin) {
      throw new PortNotFoundException(originCode);
    }

    if (!destination) {
      throw new PortNotFoundException(destinationCode);
    }
  }

  private generateCacheKey(input: RateSearchInput): string {
    const parts = [
      'rate-search',
      input.origin,
      input.destination,
      input.containerType,
      input.mode,
      input.departureDate.toISOString().split('T')[0],
      input.quantity || 1,
      input.isHazmat ? 'hazmat' : 'standard',
    ];

    return parts.join(':');
  }

  private filterCarrierConnectors(carrierPreferences?: string[]): CarrierConnectorPort[] {
    if (!carrierPreferences || carrierPreferences.length === 0) {
      return this.carrierConnectors;
    }

    return this.carrierConnectors.filter(connector =>
      carrierPreferences.includes(connector.getCarrierCode())
    );
  }

  private async queryCarrier(
    connector: CarrierConnectorPort,
    input: RateSearchInput
  ): Promise<RateQuote[]> {
    return connector.searchRates({
      origin: input.origin,
      destination: input.destination,
      containerType: input.containerType,
      mode: input.mode,
      departureDate: input.departureDate,
      quantity: input.quantity,
      weight: input.weight,
      volume: input.volume,
      isHazmat: input.isHazmat,
      imoClass: input.imoClass,
    });
  }
}
