import { Injectable, Logger, Optional } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { parse } from 'csv-parse/sync';
import * as fs from 'fs/promises';
import * as path from 'path';
import { CsvRateLoaderPort } from '@domain/ports/out/csv-rate-loader.port';
import { CsvRate } from '@domain/entities/csv-rate.entity';
import { PortCode } from '@domain/value-objects/port-code.vo';
import { ContainerType } from '@domain/value-objects/container-type.vo';
import { Money } from '@domain/value-objects/money.vo';
import { Surcharge, SurchargeType, SurchargeCollection } from '@domain/value-objects/surcharge.vo';
import { DateRange } from '@domain/value-objects/date-range.vo';
import { S3StorageAdapter } from '@infrastructure/storage/s3-storage.adapter';
import { TypeOrmCsvRateConfigRepository } from '@infrastructure/persistence/typeorm/repositories/typeorm-csv-rate-config.repository';

/**
 * CSV Row Interface
 * Maps to CSV file structure
 */
interface CsvRow {
  companyName: string;
  origin: string;
  destination: string;
  containerType: string;
  minVolumeCBM: string;
  maxVolumeCBM: string;
  minWeightKG: string;
  maxWeightKG: string;
  palletCount: string;
  pricePerCBM: string;
  pricePerKG: string;
  basePriceUSD: string;
  basePriceEUR: string;
  currency: string;
  hasSurcharges: string;
  surchargeBAF?: string;
  surchargeCAF?: string;
  surchargeDetails?: string;
  transitDays: string;
  validFrom: string;
  validUntil: string;
}

/**
 * CSV Rate Loader Adapter
 *
 * Infrastructure adapter for loading shipping rates from CSV files.
 * Implements CsvRateLoaderPort interface.
 *
 * Features:
 * - CSV parsing with validation
 * - Mapping CSV rows to domain entities
 * - Error handling and logging
 * - File system operations
 */
@Injectable()
export class CsvRateLoaderAdapter implements CsvRateLoaderPort {
  private readonly logger = new Logger(CsvRateLoaderAdapter.name);
  private readonly csvDirectory: string;

  // Company name to CSV file mapping
  private readonly companyFileMapping: Map<string, string> = new Map([
    ['SSC Consolidation', 'ssc-consolidation.csv'],
    ['ECU Worldwide', 'ecu-worldwide.csv'],
    ['TCC Logistics', 'tcc-logistics.csv'],
    ['NVO Consolidation', 'nvo-consolidation.csv'],
  ]);

  constructor(
    @Optional() private readonly s3Storage?: S3StorageAdapter,
    @Optional() private readonly configService?: ConfigService,
    @Optional() private readonly csvConfigRepository?: TypeOrmCsvRateConfigRepository
  ) {
    // CSV files are stored in infrastructure/storage/csv-storage/rates/
    // Use absolute path based on project root (works in both dev and production)
    // In production, process.cwd() points to the backend app directory
    // In development with nest start --watch, it also points to the backend directory
    this.csvDirectory = path.join(
      process.cwd(),
      'src',
      'infrastructure',
      'storage',
      'csv-storage',
      'rates'
    );
    this.logger.log(`CSV directory initialized: ${this.csvDirectory}`);

    if (this.s3Storage && this.configService) {
      this.logger.log('✅ MinIO/S3 storage support enabled for CSV files');
    }
  }

  async loadRatesFromCsv(filePath: string, companyEmail: string, companyNameOverride?: string): Promise<CsvRate[]> {
    this.logger.log(`Loading rates from CSV: ${filePath} (email: ${companyEmail}, company: ${companyNameOverride || 'from CSV'})`);

    try {
      let fileContent: string;

      // Try to load from MinIO first if configured
      if (this.s3Storage && this.configService && this.csvConfigRepository && companyNameOverride) {
        try {
          const config = await this.csvConfigRepository.findByCompanyName(companyNameOverride);
          const minioObjectKey = config?.metadata?.minioObjectKey as string | undefined;

          if (minioObjectKey) {
            const bucket = this.configService.get<string>('AWS_S3_BUCKET', 'xpeditis-csv-rates');
            this.logger.log(`📥 Loading CSV from MinIO: ${bucket}/${minioObjectKey}`);

            const buffer = await this.s3Storage.download({ bucket, key: minioObjectKey });
            fileContent = buffer.toString('utf-8');
            this.logger.log(`✅ Successfully loaded CSV from MinIO`);
          } else {
            // Fallback to local file
            throw new Error('No MinIO object key found, using local file');
          }
        } catch (minioError: any) {
          this.logger.warn(`⚠️  Failed to load from MinIO: ${minioError.message}. Falling back to local file.`);
          // Fallback to local file system
          const fullPath = path.isAbsolute(filePath)
            ? filePath
            : path.join(this.csvDirectory, filePath);
          fileContent = await fs.readFile(fullPath, 'utf-8');
        }
      } else {
        // Read from local file system
        const fullPath = path.isAbsolute(filePath)
          ? filePath
          : path.join(this.csvDirectory, filePath);
        fileContent = await fs.readFile(fullPath, 'utf-8');
      }

      // Parse CSV
      const records: CsvRow[] = parse(fileContent, {
        columns: true,
        skip_empty_lines: true,
        trim: true,
      });

      this.logger.log(`Parsed ${records.length} rows from ${filePath}`);

      // Validate structure
      this.validateCsvStructure(records);

      // Map to domain entities
      const rates = records.map((record, index) => {
        try {
          return this.mapToCsvRate(record, companyEmail, companyNameOverride);
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : String(error);
          this.logger.error(`Error mapping row ${index + 1} in ${filePath}: ${errorMessage}`);
          throw new Error(`Invalid data in row ${index + 1} of ${filePath}: ${errorMessage}`);
        }
      });

      this.logger.log(`Successfully loaded ${rates.length} rates from ${filePath}`);
      return rates;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Failed to load CSV file ${filePath}: ${errorMessage}`);
      throw new Error(`CSV loading failed for ${filePath}: ${errorMessage}`);
    }
  }

  async loadRatesByCompany(companyName: string): Promise<CsvRate[]> {
    const fileName = this.companyFileMapping.get(companyName);

    if (!fileName) {
      this.logger.warn(`No CSV file configured for company: ${companyName}`);
      return [];
    }

    // Use placeholder email since we don't have access to config repository here
    const placeholderEmail = `info@${companyName.toLowerCase().replace(/\s+/g, '-')}.com`;
    return this.loadRatesFromCsv(fileName, placeholderEmail);
  }

  async validateCsvFile(
    filePath: string
  ): Promise<{ valid: boolean; errors: string[]; rowCount?: number }> {
    const errors: string[] = [];

    try {
      const fullPath = path.isAbsolute(filePath)
        ? filePath
        : path.join(this.csvDirectory, filePath);

      // Check if file exists
      try {
        await fs.access(fullPath);
      } catch {
        errors.push(`File not found: ${filePath}`);
        return { valid: false, errors };
      }

      // Read and parse
      const fileContent = await fs.readFile(fullPath, 'utf-8');
      const records: CsvRow[] = parse(fileContent, {
        columns: true,
        skip_empty_lines: true,
        trim: true,
      });

      if (records.length === 0) {
        errors.push('CSV file is empty');
        return { valid: false, errors, rowCount: 0 };
      }

      // Validate structure
      try {
        this.validateCsvStructure(records);
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        errors.push(errorMessage);
      }

      // Validate each row (use dummy email for validation)
      records.forEach((record, index) => {
        try {
          this.mapToCsvRate(record, 'validation@example.com');
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : String(error);
          errors.push(`Row ${index + 1}: ${errorMessage}`);
        }
      });

      return {
        valid: errors.length === 0,
        errors,
        rowCount: records.length,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      errors.push(`Validation failed: ${errorMessage}`);
      return { valid: false, errors };
    }
  }

  async getAvailableCsvFiles(): Promise<string[]> {
    try {
      // If MinIO/S3 is configured, list files from there
      if (this.s3Storage && this.configService && this.csvConfigRepository) {
        try {
          const configs = await this.csvConfigRepository.findAll();
          const minioFiles = configs
            .filter(config => config.metadata?.minioObjectKey)
            .map(config => config.metadata?.minioObjectKey as string);

          if (minioFiles.length > 0) {
            this.logger.log(`📂 Found ${minioFiles.length} CSV files in MinIO`);
            return minioFiles;
          } else {
            this.logger.warn('⚠️  No CSV files configured in MinIO, falling back to local files');
          }
        } catch (minioError: any) {
          this.logger.warn(`⚠️  Failed to list MinIO files: ${minioError.message}. Falling back to local files.`);
        }
      }

      // Fallback: list from local file system
      try {
        await fs.access(this.csvDirectory);
      } catch {
        this.logger.warn(`CSV directory does not exist: ${this.csvDirectory}`);
        return [];
      }

      const files = await fs.readdir(this.csvDirectory);
      return files.filter(file => file.endsWith('.csv'));
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`Failed to list CSV files: ${errorMessage}`);
      return [];
    }
  }

  /**
   * Validate that CSV has all required columns
   */
  private validateCsvStructure(records: CsvRow[]): void {
    const requiredColumns = [
      'companyName',
      'origin',
      'destination',
      'containerType',
      'minVolumeCBM',
      'maxVolumeCBM',
      'minWeightKG',
      'maxWeightKG',
      'palletCount',
      'pricePerCBM',
      'pricePerKG',
      'basePriceUSD',
      'basePriceEUR',
      'currency',
      'hasSurcharges',
      'transitDays',
      'validFrom',
      'validUntil',
    ];

    if (records.length === 0) {
      throw new Error('CSV file is empty');
    }

    const firstRecord = records[0];
    const missingColumns = requiredColumns.filter(col => !(col in firstRecord));

    if (missingColumns.length > 0) {
      throw new Error(`Missing required columns: ${missingColumns.join(', ')}`);
    }
  }

  /**
   * Map CSV row to CsvRate domain entity
   */
  private mapToCsvRate(record: CsvRow, companyEmail: string, companyNameOverride?: string): CsvRate {
    // Parse surcharges
    const surcharges = this.parseSurcharges(record);

    // Create DateRange
    const validFrom = new Date(record.validFrom);
    const validUntil = new Date(record.validUntil);
    const validity = DateRange.create(validFrom, validUntil, true);

    // Use override company name if provided, otherwise use the one from CSV
    const companyName = companyNameOverride || record.companyName.trim();

    // Create CsvRate
    return new CsvRate(
      companyName,
      companyEmail,
      PortCode.create(record.origin),
      PortCode.create(record.destination),
      ContainerType.create(record.containerType),
      {
        minCBM: parseFloat(record.minVolumeCBM),
        maxCBM: parseFloat(record.maxVolumeCBM),
      },
      {
        minKG: parseFloat(record.minWeightKG),
        maxKG: parseFloat(record.maxWeightKG),
      },
      parseInt(record.palletCount, 10),
      {
        pricePerCBM: parseFloat(record.pricePerCBM),
        pricePerKG: parseFloat(record.pricePerKG),
        basePriceUSD: Money.create(parseFloat(record.basePriceUSD), 'USD'),
        basePriceEUR: Money.create(parseFloat(record.basePriceEUR), 'EUR'),
      },
      record.currency.toUpperCase(),
      new SurchargeCollection(surcharges),
      parseInt(record.transitDays, 10),
      validity
    );
  }

  /**
   * Parse surcharges from CSV row
   */
  private parseSurcharges(record: CsvRow): Surcharge[] {
    const hasSurcharges = record.hasSurcharges.toLowerCase() === 'true';

    if (!hasSurcharges) {
      return [];
    }

    const surcharges: Surcharge[] = [];
    const currency = record.currency.toUpperCase();

    // BAF (Bunker Adjustment Factor)
    if (record.surchargeBAF && parseFloat(record.surchargeBAF) > 0) {
      surcharges.push(
        new Surcharge(
          SurchargeType.BAF,
          Money.create(parseFloat(record.surchargeBAF), currency),
          'Bunker Adjustment Factor'
        )
      );
    }

    // CAF (Currency Adjustment Factor)
    if (record.surchargeCAF && parseFloat(record.surchargeCAF) > 0) {
      surcharges.push(
        new Surcharge(
          SurchargeType.CAF,
          Money.create(parseFloat(record.surchargeCAF), currency),
          'Currency Adjustment Factor'
        )
      );
    }

    return surcharges;
  }
}
