import {
  Controller,
  Post,
  Get,
  Body,
  Query,
  HttpCode,
  HttpStatus,
  Logger,
  UsePipes,
  ValidationPipe,
  UseGuards,
  Inject,
} from '@nestjs/common';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBadRequestResponse,
  ApiInternalServerErrorResponse,
  ApiBearerAuth,
  ApiQuery,
} from '@nestjs/swagger';
import { RateSearchRequestDto, RateSearchResponseDto } from '../dto';
import { RateQuoteMapper } from '../mappers';
import { RateSearchService } from '@domain/services/rate-search.service';
import { CsvRateSearchService } from '@domain/services/csv-rate-search.service';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
import { CsvRateSearchDto, CsvRateSearchResponseDto } from '../dto/csv-rate-search.dto';
import {
  AvailableCompaniesDto,
  FilterOptionsDto,
  AvailableOriginsDto,
  AvailableDestinationsDto,
  RoutePortInfoDto,
} from '../dto/csv-rate-upload.dto';
import { CsvRateMapper } from '../mappers/csv-rate.mapper';
import { PortRepository, PORT_REPOSITORY } from '@domain/ports/out/port.repository';

@ApiTags('Rates')
@Controller('rates')
@ApiBearerAuth()
export class RatesController {
  private readonly logger = new Logger(RatesController.name);

  constructor(
    private readonly rateSearchService: RateSearchService,
    private readonly csvRateSearchService: CsvRateSearchService,
    private readonly csvRateMapper: CsvRateMapper,
    @Inject(PORT_REPOSITORY) private readonly portRepository: PortRepository
  ) {}

  @Post('search')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Search shipping rates',
    description:
      'Search for available shipping rates from multiple carriers. Results are cached for 15 minutes. Requires authentication.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Rate search completed successfully',
    type: RateSearchResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiBadRequestResponse({
    description: 'Invalid request parameters',
    schema: {
      example: {
        statusCode: 400,
        message: ['Origin must be a valid 5-character UN/LOCODE (e.g., NLRTM)'],
        error: 'Bad Request',
      },
    },
  })
  @ApiInternalServerErrorResponse({
    description: 'Internal server error',
  })
  async searchRates(
    @Body() dto: RateSearchRequestDto,
    @CurrentUser() user: UserPayload
  ): Promise<RateSearchResponseDto> {
    const startTime = Date.now();
    this.logger.log(
      `[User: ${user.email}] Searching rates: ${dto.origin} → ${dto.destination}, ${dto.containerType}`
    );

    try {
      // Convert DTO to domain input
      const searchInput = {
        origin: dto.origin,
        destination: dto.destination,
        containerType: dto.containerType,
        mode: dto.mode,
        departureDate: new Date(dto.departureDate),
        quantity: dto.quantity,
        weight: dto.weight,
        volume: dto.volume,
        isHazmat: dto.isHazmat,
        imoClass: dto.imoClass,
      };

      // Execute search
      const result = await this.rateSearchService.execute(searchInput);

      // Convert domain entities to DTOs
      const quoteDtos = RateQuoteMapper.toDtoArray(result.quotes);

      const responseTimeMs = Date.now() - startTime;
      this.logger.log(`Rate search completed: ${quoteDtos.length} quotes, ${responseTimeMs}ms`);

      return {
        quotes: quoteDtos,
        count: quoteDtos.length,
        origin: dto.origin,
        destination: dto.destination,
        departureDate: dto.departureDate,
        containerType: dto.containerType,
        mode: dto.mode,
        fromCache: false, // TODO: Implement cache detection
        responseTimeMs,
      };
    } catch (error: any) {
      this.logger.error(`Rate search failed: ${error?.message || 'Unknown error'}`, error?.stack);
      throw error;
    }
  }

  /**
   * Search CSV-based rates with advanced filters
   */
  @Post('search-csv')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Search CSV-based rates with advanced filters',
    description:
      'Search for rates from CSV-loaded carriers (SSC, ECU, TCC, NVO) with advanced filtering options including volume, weight, pallets, price range, transit time, and more.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'CSV rate search completed successfully',
    type: CsvRateSearchResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiBadRequestResponse({
    description: 'Invalid request parameters',
  })
  async searchCsvRates(
    @Body() dto: CsvRateSearchDto,
    @CurrentUser() user: UserPayload
  ): Promise<CsvRateSearchResponseDto> {
    const startTime = Date.now();
    this.logger.log(
      `[User: ${user.email}] Searching CSV rates: ${dto.origin} → ${dto.destination}, ${dto.volumeCBM} CBM, ${dto.weightKG} kg`
    );

    try {
      const searchInput = {
        origin: dto.origin,
        destination: dto.destination,
        volumeCBM: dto.volumeCBM,
        weightKG: dto.weightKG,
        containerType: dto.containerType,
        hasDangerousGoods: dto.hasDangerousGoods ?? false,
        filters: this.csvRateMapper.mapFiltersDtoToDomain(dto.filters),
      };

      const result = await this.csvRateSearchService.execute(searchInput);

      // Map domain output to response DTO
      const response = this.csvRateMapper.mapSearchOutputToResponseDto(result);

      const responseTimeMs = Date.now() - startTime;
      this.logger.log(
        `CSV rate search completed: ${response.totalResults} results, ${responseTimeMs}ms`
      );

      return response;
    } catch (error: any) {
      this.logger.error(
        `CSV rate search failed: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }

  /**
   * Search CSV-based rates with service level offers (RAPID, STANDARD, ECONOMIC)
   */
  @Post('search-csv-offers')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Search CSV-based rates with service level offers',
    description:
      'Search for rates from CSV-loaded carriers and generate 3 service level offers for each matching rate: RAPID (20% more expensive, 30% faster), STANDARD (base price and transit), ECONOMIC (15% cheaper, 50% slower). Results are sorted by price (cheapest first).',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'CSV rate search with offers completed successfully',
    type: CsvRateSearchResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiBadRequestResponse({
    description: 'Invalid request parameters',
  })
  async searchCsvRatesWithOffers(
    @Body() dto: CsvRateSearchDto,
    @CurrentUser() user: UserPayload
  ): Promise<CsvRateSearchResponseDto> {
    const startTime = Date.now();
    this.logger.log(
      `[User: ${user.email}] Searching CSV rates with offers: ${dto.origin} → ${dto.destination}, ${dto.volumeCBM} CBM, ${dto.weightKG} kg`
    );

    try {
      const searchInput = {
        origin: dto.origin,
        destination: dto.destination,
        volumeCBM: dto.volumeCBM,
        weightKG: dto.weightKG,
        containerType: dto.containerType,
        hasDangerousGoods: dto.hasDangerousGoods ?? false,
        filters: this.csvRateMapper.mapFiltersDtoToDomain(dto.filters),
      };

      const result = await this.csvRateSearchService.executeWithOffers(searchInput);

      // Map domain output to response DTO
      const response = this.csvRateMapper.mapSearchOutputToResponseDto(result);

      const responseTimeMs = Date.now() - startTime;
      this.logger.log(
        `CSV rate search with offers completed: ${response.totalResults} results (including 3 offers per rate), ${responseTimeMs}ms`
      );

      return response;
    } catch (error: any) {
      this.logger.error(
        `CSV rate search with offers failed: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }

  /**
   * Get available origin ports from CSV rates
   * Returns only ports that have routes defined in CSV files
   */
  @Get('available-routes/origins')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get available origin ports from CSV rates',
    description:
      'Returns list of origin ports that have shipping routes defined in CSV rate files. Use this to populate origin port selection dropdown.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'List of available origin ports with details',
    type: AvailableOriginsDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  async getAvailableOrigins(): Promise<AvailableOriginsDto> {
    this.logger.log('Fetching available origin ports from CSV rates');

    try {
      // Get unique origin port codes from CSV rates
      const originCodes = await this.csvRateSearchService.getAvailableOrigins();

      // Fetch port details from database
      const ports = await this.portRepository.findByCodes(originCodes);

      // Map to response DTO with port details
      const origins: RoutePortInfoDto[] = originCodes.map(code => {
        const port = ports.find(p => p.code === code);
        if (port) {
          return {
            code: port.code,
            name: port.name,
            city: port.city,
            country: port.country,
            countryName: port.countryName,
            displayName: port.getDisplayName(),
            latitude: port.coordinates.latitude,
            longitude: port.coordinates.longitude,
          };
        }
        // Fallback if port not found in database
        return {
          code,
          name: code,
          city: '',
          country: code.substring(0, 2),
          countryName: '',
          displayName: code,
        };
      });

      // Sort by display name
      origins.sort((a, b) => a.displayName.localeCompare(b.displayName));

      return {
        origins,
        total: origins.length,
      };
    } catch (error: any) {
      this.logger.error(
        `Failed to fetch available origins: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }

  /**
   * Get available destination ports for a given origin
   * Returns only destinations that have routes from the specified origin in CSV files
   */
  @Get('available-routes/destinations')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get available destination ports for a given origin',
    description:
      'Returns list of destination ports that have shipping routes from the specified origin port in CSV rate files. Use this to populate destination port selection dropdown after origin is selected.',
  })
  @ApiQuery({
    name: 'origin',
    required: true,
    description: 'Origin port code (UN/LOCODE format, e.g., NLRTM)',
    example: 'NLRTM',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'List of available destination ports with details',
    type: AvailableDestinationsDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiBadRequestResponse({
    description: 'Origin port code is required',
  })
  async getAvailableDestinations(
    @Query('origin') origin: string
  ): Promise<AvailableDestinationsDto> {
    this.logger.log(`Fetching available destinations for origin: ${origin}`);

    if (!origin) {
      throw new Error('Origin port code is required');
    }

    try {
      // Get destination port codes for this origin from CSV rates
      const destinationCodes = await this.csvRateSearchService.getAvailableDestinations(origin);

      // Fetch port details from database
      const ports = await this.portRepository.findByCodes(destinationCodes);

      // Map to response DTO with port details
      const destinations: RoutePortInfoDto[] = destinationCodes.map(code => {
        const port = ports.find(p => p.code === code);
        if (port) {
          return {
            code: port.code,
            name: port.name,
            city: port.city,
            country: port.country,
            countryName: port.countryName,
            displayName: port.getDisplayName(),
            latitude: port.coordinates.latitude,
            longitude: port.coordinates.longitude,
          };
        }
        // Fallback if port not found in database
        return {
          code,
          name: code,
          city: '',
          country: code.substring(0, 2),
          countryName: '',
          displayName: code,
        };
      });

      // Sort by display name
      destinations.sort((a, b) => a.displayName.localeCompare(b.displayName));

      return {
        origin: origin.toUpperCase(),
        destinations,
        total: destinations.length,
      };
    } catch (error: any) {
      this.logger.error(
        `Failed to fetch available destinations: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }

  /**
   * Get available companies
   */
  @Get('companies')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get available carrier companies',
    description: 'Returns list of all available carrier companies in the CSV rate system.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'List of available companies',
    type: AvailableCompaniesDto,
  })
  async getCompanies(): Promise<AvailableCompaniesDto> {
    this.logger.log('Fetching available companies');

    try {
      const companies = await this.csvRateSearchService.getAvailableCompanies();

      return {
        companies,
        total: companies.length,
      };
    } catch (error: any) {
      this.logger.error(
        `Failed to fetch companies: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }

  /**
   * Get filter options
   */
  @Get('filters/options')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get available filter options',
    description:
      'Returns available options for all filters (companies, container types, currencies).',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Available filter options',
    type: FilterOptionsDto,
  })
  async getFilterOptions(): Promise<FilterOptionsDto> {
    this.logger.log('Fetching filter options');

    try {
      const [companies, containerTypes] = await Promise.all([
        this.csvRateSearchService.getAvailableCompanies(),
        this.csvRateSearchService.getAvailableContainerTypes(),
      ]);

      return {
        companies,
        containerTypes,
        currencies: ['USD', 'EUR'],
      };
    } catch (error: any) {
      this.logger.error(
        `Failed to fetch filter options: ${error?.message || 'Unknown error'}`,
        error?.stack
      );
      throw error;
    }
  }
}
