import { CsvRate } from '../entities/csv-rate.entity';
import { PortCode } from '../value-objects/port-code.vo';
import { ContainerType } from '../value-objects/container-type.vo';
import { Volume } from '../value-objects/volume.vo';
import { Money } from '../value-objects/money.vo';
import {
  SearchCsvRatesPort,
  CsvRateSearchInput,
  CsvRateSearchOutput,
  CsvRateSearchResult,
  RateSearchFilters,
} from '@domain/ports/in/search-csv-rates.port';
import { CsvRateLoaderPort } from '@domain/ports/out/csv-rate-loader.port';
import { CsvRatePriceCalculatorService } from './csv-rate-price-calculator.service';

/**
 * Config Metadata Interface (to avoid circular dependency)
 */
interface CsvRateConfig {
  companyName: string;
  csvFilePath: string;
  metadata?: {
    companyEmail?: string;
    [key: string]: any;
  };
}

/**
 * Config Repository Port (simplified interface)
 */
export interface CsvRateConfigRepositoryPort {
  findActiveConfigs(): Promise<CsvRateConfig[]>;
}

/**
 * CSV Rate Search Service
 *
 * Domain service implementing CSV rate search use case.
 * Applies business rules for matching rates and filtering.
 *
 * Pure domain logic - no framework dependencies.
 */
export class CsvRateSearchService implements SearchCsvRatesPort {
  private readonly priceCalculator: CsvRatePriceCalculatorService;

  constructor(
    private readonly csvRateLoader: CsvRateLoaderPort,
    private readonly configRepository?: CsvRateConfigRepositoryPort
  ) {
    this.priceCalculator = new CsvRatePriceCalculatorService();
  }

  async execute(input: CsvRateSearchInput): Promise<CsvRateSearchOutput> {
    const searchStartTime = new Date();

    // Parse and validate input
    const origin = PortCode.create(input.origin);
    const destination = PortCode.create(input.destination);
    const volume = new Volume(input.volumeCBM, input.weightKG);
    const palletCount = input.palletCount ?? 0;

    // Load all CSV rates
    const allRates = await this.loadAllRates();

    // Apply route and volume matching
    let matchingRates = this.filterByRoute(allRates, origin, destination);
    matchingRates = this.filterByVolume(matchingRates, volume);
    matchingRates = this.filterByPalletCount(matchingRates, palletCount);

    // Apply container type filter if specified
    if (input.containerType) {
      const containerType = ContainerType.create(input.containerType);
      matchingRates = matchingRates.filter(rate => rate.containerType.equals(containerType));
    }

    // Apply advanced filters
    if (input.filters) {
      matchingRates = this.applyAdvancedFilters(matchingRates, input.filters, volume);
    }

    // Calculate prices and create results
    const results: CsvRateSearchResult[] = matchingRates.map(rate => {
      // Calculate detailed price breakdown
      const priceBreakdown = this.priceCalculator.calculatePrice(rate, {
        volumeCBM: input.volumeCBM,
        weightKG: input.weightKG,
        palletCount: input.palletCount ?? 0,
        hasDangerousGoods: input.hasDangerousGoods ?? false,
        requiresSpecialHandling: input.requiresSpecialHandling ?? false,
        requiresTailgate: input.requiresTailgate ?? false,
        requiresStraps: input.requiresStraps ?? false,
        requiresThermalCover: input.requiresThermalCover ?? false,
        hasRegulatedProducts: input.hasRegulatedProducts ?? false,
        requiresAppointment: input.requiresAppointment ?? false,
      });

      return {
        rate,
        calculatedPrice: {
          usd: priceBreakdown.totalPrice,
          eur: priceBreakdown.totalPrice, // TODO: Add currency conversion
          primaryCurrency: priceBreakdown.currency,
        },
        priceBreakdown,
        source: 'CSV' as const,
        matchScore: this.calculateMatchScore(rate, input),
      };
    });

    // Sort by total price (ascending)
    results.sort((a, b) => a.priceBreakdown.totalPrice - b.priceBreakdown.totalPrice);

    return {
      results,
      totalResults: results.length,
      searchedFiles: await this.csvRateLoader.getAvailableCsvFiles(),
      searchedAt: searchStartTime,
      appliedFilters: input.filters || {},
    };
  }

  async getAvailableCompanies(): Promise<string[]> {
    const allRates = await this.loadAllRates();
    const companies = new Set(allRates.map(rate => rate.companyName));
    return Array.from(companies).sort();
  }

  async getAvailableContainerTypes(): Promise<string[]> {
    const allRates = await this.loadAllRates();
    const types = new Set(allRates.map(rate => rate.containerType.getValue()));
    return Array.from(types).sort();
  }

  /**
   * Load all rates from all CSV files
   */
  private async loadAllRates(): Promise<CsvRate[]> {
    // If config repository is available, load rates with emails and company names from configs
    if (this.configRepository) {
      const configs = await this.configRepository.findActiveConfigs();
      const ratePromises = configs.map(config => {
        const email = config.metadata?.companyEmail || 'bookings@example.com';
        // Pass company name from config to override CSV column value
        return this.csvRateLoader.loadRatesFromCsv(config.csvFilePath, email, config.companyName);
      });

      // Use allSettled to handle missing files gracefully
      const results = await Promise.allSettled(ratePromises);
      const rateArrays = results
        .filter((result): result is PromiseFulfilledResult<CsvRate[]> => result.status === 'fulfilled')
        .map(result => result.value);

      // Log any failed file loads
      const failures = results.filter(result => result.status === 'rejected');
      if (failures.length > 0) {
        console.warn(`Failed to load ${failures.length} CSV files:`,
          failures.map((f, idx) => `${configs[idx]?.csvFilePath}: ${(f as PromiseRejectedResult).reason}`));
      }

      return rateArrays.flat();
    }

    // Fallback: load files without email (use default)
    const files = await this.csvRateLoader.getAvailableCsvFiles();
    const ratePromises = files.map(file =>
      this.csvRateLoader.loadRatesFromCsv(file, 'bookings@example.com')
    );

    // Use allSettled here too for consistency
    const results = await Promise.allSettled(ratePromises);
    const rateArrays = results
      .filter((result): result is PromiseFulfilledResult<CsvRate[]> => result.status === 'fulfilled')
      .map(result => result.value);

    return rateArrays.flat();
  }

  /**
   * Filter rates by route (origin/destination)
   */
  private filterByRoute(rates: CsvRate[], origin: PortCode, destination: PortCode): CsvRate[] {
    return rates.filter(rate => rate.matchesRoute(origin, destination));
  }

  /**
   * Filter rates by volume/weight range
   */
  private filterByVolume(rates: CsvRate[], volume: Volume): CsvRate[] {
    return rates.filter(rate => rate.matchesVolume(volume));
  }

  /**
   * Filter rates by pallet count
   */
  private filterByPalletCount(rates: CsvRate[], palletCount: number): CsvRate[] {
    return rates.filter(rate => rate.matchesPalletCount(palletCount));
  }

  /**
   * Apply advanced filters to rate list
   */
  private applyAdvancedFilters(
    rates: CsvRate[],
    filters: RateSearchFilters,
    volume: Volume
  ): CsvRate[] {
    let filtered = rates;

    // Company filter
    if (filters.companies && filters.companies.length > 0) {
      filtered = filtered.filter(rate => filters.companies!.includes(rate.companyName));
    }

    // Volume CBM filter
    if (filters.minVolumeCBM !== undefined) {
      filtered = filtered.filter(rate => rate.volumeRange.maxCBM >= filters.minVolumeCBM!);
    }
    if (filters.maxVolumeCBM !== undefined) {
      filtered = filtered.filter(rate => rate.volumeRange.minCBM <= filters.maxVolumeCBM!);
    }

    // Weight KG filter
    if (filters.minWeightKG !== undefined) {
      filtered = filtered.filter(rate => rate.weightRange.maxKG >= filters.minWeightKG!);
    }
    if (filters.maxWeightKG !== undefined) {
      filtered = filtered.filter(rate => rate.weightRange.minKG <= filters.maxWeightKG!);
    }

    // Pallet count filter
    if (filters.palletCount !== undefined) {
      filtered = filtered.filter(rate => rate.matchesPalletCount(filters.palletCount!));
    }

    // Price filter (calculate price first)
    if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
      const currency = filters.currency || 'USD';
      filtered = filtered.filter(rate => {
        const price = rate.getPriceInCurrency(volume, currency);
        const amount = price.getAmount();

        if (filters.minPrice !== undefined && amount < filters.minPrice) {
          return false;
        }
        if (filters.maxPrice !== undefined && amount > filters.maxPrice) {
          return false;
        }
        return true;
      });
    }

    // Transit days filter
    if (filters.minTransitDays !== undefined) {
      filtered = filtered.filter(rate => rate.transitDays >= filters.minTransitDays!);
    }
    if (filters.maxTransitDays !== undefined) {
      filtered = filtered.filter(rate => rate.transitDays <= filters.maxTransitDays!);
    }

    // Container type filter
    if (filters.containerTypes && filters.containerTypes.length > 0) {
      filtered = filtered.filter(rate =>
        filters.containerTypes!.includes(rate.containerType.getValue())
      );
    }

    // All-in prices only filter
    if (filters.onlyAllInPrices) {
      filtered = filtered.filter(rate => rate.isAllInPrice());
    }

    // Departure date / validity filter
    if (filters.departureDate) {
      filtered = filtered.filter(rate => rate.isValidForDate(filters.departureDate!));
    }

    return filtered;
  }

  /**
   * Calculate match score (0-100) based on how well rate matches input
   * Higher score = better match
   */
  private calculateMatchScore(rate: CsvRate, input: CsvRateSearchInput): number {
    let score = 100;

    // Reduce score if volume/weight is near boundaries
    const volumeUtilization =
      (input.volumeCBM - rate.volumeRange.minCBM) /
      (rate.volumeRange.maxCBM - rate.volumeRange.minCBM);
    if (volumeUtilization < 0.2 || volumeUtilization > 0.8) {
      score -= 10; // Near boundaries
    }

    // Reduce score if pallet count doesn't match exactly
    if (rate.palletCount !== 0 && input.palletCount !== rate.palletCount) {
      score -= 5;
    }

    // Increase score for all-in prices (simpler for customers)
    if (rate.isAllInPrice()) {
      score += 5;
    }

    // Reduce score for rates expiring soon
    const daysUntilExpiry = Math.floor(
      (rate.validity.getEndDate().getTime() - Date.now()) / (1000 * 60 * 60 * 24)
    );
    if (daysUntilExpiry < 7) {
      score -= 10;
    } else if (daysUntilExpiry < 30) {
      score -= 5;
    }

    return Math.max(0, Math.min(100, score));
  }
}
