import { CsvRate } from '../entities/csv-rate.entity';
import { PortCode } from '../value-objects/port-code.vo';
import { ContainerType } from '../value-objects/container-type.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';
import { RateOfferGeneratorService } from './rate-offer-generator.service';

interface CsvRateConfig {
  companyName: string;
  csvFilePath: string;
  metadata?: {
    companyEmail?: string;
    [key: string]: any;
  };
}

export interface CsvRateConfigRepositoryPort {
  findActiveConfigs(): Promise<CsvRateConfig[]>;
}

export class CsvRateSearchService implements SearchCsvRatesPort {
  private readonly priceCalculator: CsvRatePriceCalculatorService;
  private readonly offerGenerator: RateOfferGeneratorService;

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

  async execute(input: CsvRateSearchInput): Promise<CsvRateSearchOutput> {
    const searchStartTime = new Date();
    const origin = PortCode.create(input.origin);
    const destination = PortCode.create(input.destination);

    const allRates = await this.loadAllRates();
    let matchingRates = this.filterByRoute(allRates, origin, destination);

    if (input.containerType) {
      const containerType = ContainerType.create(input.containerType);
      matchingRates = matchingRates.filter(rate => rate.containerType.equals(containerType));
    }

    if (input.filters) {
      matchingRates = this.applyAdvancedFilters(matchingRates, input.filters, input);
    }

    const results: CsvRateSearchResult[] = matchingRates.map(rate => {
      const priceBreakdown = this.priceCalculator.calculatePrice(rate, {
        volumeCBM: input.volumeCBM,
        weightKG: input.weightKG,
        hasDangerousGoods: input.hasDangerousGoods ?? false,
      });

      return {
        rate,
        priceBreakdown,
        source: 'CSV' as const,
        matchScore: this.calculateMatchScore(rate),
      };
    });

    results.sort(
      (a, b) => a.priceBreakdown.totalPriceForSorting - b.priceBreakdown.totalPriceForSorting
    );

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

  /**
   * Search with service level offers — returns 3 variants per rate (ECONOMIC / STANDARD / RAPID).
   * Price multipliers (0.85 / 1.0 / 1.2) are applied to totalPriceForSorting.
   */
  async executeWithOffers(input: CsvRateSearchInput): Promise<CsvRateSearchOutput> {
    const searchStartTime = new Date();
    const origin = PortCode.create(input.origin);
    const destination = PortCode.create(input.destination);

    const allRates = await this.loadAllRates();
    let matchingRates = this.filterByRoute(allRates, origin, destination);

    if (input.containerType) {
      const containerType = ContainerType.create(input.containerType);
      matchingRates = matchingRates.filter(rate => rate.containerType.equals(containerType));
    }

    if (input.filters) {
      matchingRates = this.applyAdvancedFilters(matchingRates, input.filters, input);
    }

    const eligibleRates = this.offerGenerator.filterEligibleRates(matchingRates);
    const allOffers = this.offerGenerator.generateOffersForRates(eligibleRates);

    const results: CsvRateSearchResult[] = allOffers.map(offer => {
      const priceBreakdown = this.priceCalculator.calculatePrice(offer.rate, {
        volumeCBM: input.volumeCBM,
        weightKG: input.weightKG,
        hasDangerousGoods: input.hasDangerousGoods ?? false,
      });

      const multiplier = offer.priceMultiplier;
      const adjustedBreakdown = {
        ...priceBreakdown,
        freightCharge: round2(priceBreakdown.freightCharge * multiplier),
        totalFreight: round2(priceBreakdown.totalFreight * multiplier),
        totalFob: round2(priceBreakdown.totalFob * multiplier),
        totalPriceForSorting: round2(priceBreakdown.totalPriceForSorting * multiplier),
      };

      return {
        rate: offer.rate,
        priceBreakdown: adjustedBreakdown,
        source: 'CSV' as const,
        matchScore: this.calculateMatchScore(offer.rate),
        serviceLevel: offer.serviceLevel,
        priceMultiplier: offer.priceMultiplier,
        originalTransitDays: offer.originalTransitDays,
        adjustedTransitDays: offer.adjustedTransitDays,
      };
    });

    let filteredResults = results;
    if (input.filters?.serviceLevels?.length) {
      filteredResults = results.filter(
        r => r.serviceLevel && input.filters!.serviceLevels!.includes(r.serviceLevel)
      );
    }

    filteredResults.sort(
      (a, b) => a.priceBreakdown.totalPriceForSorting - b.priceBreakdown.totalPriceForSorting
    );

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

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

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

  async getAvailableOrigins(): Promise<string[]> {
    const allRates = await this.loadAllRates();
    return [...new Set(allRates.map(r => r.originCode.getValue()))].sort();
  }

  async getAvailableDestinations(origin: string): Promise<string[]> {
    const allRates = await this.loadAllRates();
    const originCode = PortCode.create(origin);
    return [
      ...new Set(
        allRates.filter(r => r.originCode.equals(originCode)).map(r => r.destinationCode.getValue())
      ),
    ].sort();
  }

  async getAvailableRoutes(): Promise<Map<string, string[]>> {
    const allRates = await this.loadAllRates();
    const routeMap = new Map<string, Set<string>>();

    allRates.forEach(rate => {
      const origin = rate.originCode.getValue();
      const destination = rate.destinationCode.getValue();
      if (!routeMap.has(origin)) routeMap.set(origin, new Set());
      routeMap.get(origin)!.add(destination);
    });

    const result = new Map<string, string[]>();
    routeMap.forEach((destinations, origin) => {
      result.set(origin, [...destinations].sort());
    });
    return result;
  }

  private async loadAllRates(): Promise<CsvRate[]> {
    if (this.configRepository) {
      const configs = await this.configRepository.findActiveConfigs();

      if (configs.length > 0) {
        const results = await Promise.allSettled(
          configs.map(config => {
            const email = config.metadata?.companyEmail || 'bookings@example.com';
            return this.csvRateLoader.loadRatesFromCsv(
              config.csvFilePath,
              email,
              config.companyName
            );
          })
        );

        const failures = results.filter(r => r.status === 'rejected');
        if (failures.length > 0) {
          console.warn(`Failed to load ${failures.length} CSV files from database configs`);
        }

        return results
          .filter((r): r is PromiseFulfilledResult<CsvRate[]> => r.status === 'fulfilled')
          .flatMap(r => r.value);
      }

      // DB has no active configs — fall through to local CSV files
      console.warn('No active CSV rate configs in database, loading from local CSV files');
    }

    const files = await this.csvRateLoader.getAvailableCsvFiles();
    const results = await Promise.allSettled(
      files.map(file => this.csvRateLoader.loadRatesFromCsv(file, 'bookings@example.com'))
    );

    return results
      .filter((r): r is PromiseFulfilledResult<CsvRate[]> => r.status === 'fulfilled')
      .flatMap(r => r.value);
  }

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

  private applyAdvancedFilters(
    rates: CsvRate[],
    filters: RateSearchFilters,
    input: CsvRateSearchInput
  ): CsvRate[] {
    let filtered = rates;

    if (filters.companies?.length) {
      filtered = filtered.filter(rate => filters.companies!.includes(rate.companyName));
    }

    if (filters.onlyDirect) {
      filtered = filtered.filter(rate => rate.isDirectRoute());
    }

    if (filters.excludeNonDgRoutes) {
      filtered = filtered.filter(rate => rate.isDgAccepted());
    }

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

    if (filters.containerTypes?.length) {
      filtered = filtered.filter(rate =>
        filters.containerTypes!.includes(rate.containerType.getValue())
      );
    }

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

    if (filters.minPrice !== undefined || filters.maxPrice !== undefined) {
      filtered = filtered.filter(rate => {
        const bd = this.priceCalculator.calculatePrice(rate, {
          volumeCBM: input.volumeCBM,
          weightKG: input.weightKG,
          hasDangerousGoods: input.hasDangerousGoods ?? false,
        });
        if (filters.minPrice !== undefined && bd.totalPriceForSorting < filters.minPrice) {
          return false;
        }
        if (filters.maxPrice !== undefined && bd.totalPriceForSorting > filters.maxPrice) {
          return false;
        }
        return true;
      });
    }

    return filtered;
  }

  /**
   * Score (0–100) based on routing type, departure frequency, and rate validity.
   * Higher = better match.
   */
  private calculateMatchScore(rate: CsvRate): number {
    let score = 100;

    // Direct route bonus
    if (rate.isDirectRoute()) {
      score += 10;
    } else {
      score -= 5;
    }

    // Frequency bonus (Weekly = best)
    const freqScore = rate.getFrequencyScore(); // 1–4
    score += (freqScore - 2) * 5; // Weekly: +10, Bi-Weekly: +5, Bi-Monthly: 0, Monthly: -5

    // Validity penalty
    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));
  }
}

function round2(n: number): number {
  return Math.round(n * 100) / 100;
}
