import {
  Controller,
  Post,
  Get,
  Delete,
  Param,
  Body,
  UseGuards,
  UseInterceptors,
  UploadedFile,
  HttpCode,
  HttpStatus,
  Logger,
  BadRequestException,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBearerAuth,
  ApiConsumes,
  ApiBody,
} from '@nestjs/swagger';
import { diskStorage } from 'multer';
import { extname } from 'path';
import { JwtAuthGuard } from '../../guards/jwt-auth.guard';
import { RolesGuard } from '../../guards/roles.guard';
import { Roles } from '../../decorators/roles.decorator';
import { CurrentUser, UserPayload } from '../../decorators/current-user.decorator';
import { CsvRateLoaderAdapter } from '@infrastructure/carriers/csv-loader/csv-rate-loader.adapter';
import { CsvConverterService } from '@infrastructure/carriers/csv-loader/csv-converter.service';
import { TypeOrmCsvRateConfigRepository } from '@infrastructure/persistence/typeorm/repositories/typeorm-csv-rate-config.repository';
import {
  CsvRateUploadDto,
  CsvRateUploadResponseDto,
  CsvRateConfigDto,
  CsvFileValidationDto,
} from '../../dto/csv-rate-upload.dto';
import { CsvRateMapper } from '../../mappers/csv-rate.mapper';
import { S3StorageAdapter } from '@infrastructure/storage/s3-storage.adapter';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs';
import * as path from 'path';

/**
 * Get CSV upload directory path (works in both dev and Docker)
 */
function getCsvUploadPath(): string {
  // In Docker, working directory is /app, so we use /app/src/...
  // In local dev, process.cwd() points to the project root
  const workDir = process.cwd();

  // If we're in /app (Docker), use /app/src/infrastructure/...
  if (workDir === '/app') {
    return '/app/src/infrastructure/storage/csv-storage/rates';
  }

  // Otherwise (local dev), use relative path from project root
  return path.join(workDir, 'apps/backend/src/infrastructure/storage/csv-storage/rates');
}

/**
 * CSV Rates Admin Controller
 *
 * ADMIN-ONLY endpoints for managing CSV rate files
 * Protected by JWT + Roles guard
 */
@ApiTags('Admin - CSV Rates')
@Controller('admin/csv-rates')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN') // ⚠️ ONLY ADMIN can access these endpoints
export class CsvRatesAdminController {
  private readonly logger = new Logger(CsvRatesAdminController.name);
  private readonly csvUploadPath: string;

  constructor(
    private readonly csvLoader: CsvRateLoaderAdapter,
    private readonly csvConverter: CsvConverterService,
    private readonly csvConfigRepository: TypeOrmCsvRateConfigRepository,
    private readonly csvRateMapper: CsvRateMapper,
    private readonly s3Storage: S3StorageAdapter,
    private readonly configService: ConfigService
  ) {
    this.csvUploadPath = getCsvUploadPath();
    this.logger.log(`📁 CSV upload path: ${this.csvUploadPath}`);
  }

  /**
   * Upload CSV rate file (ADMIN only)
   */
  @Post('upload')
  @HttpCode(HttpStatus.CREATED)
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: getCsvUploadPath(),
        filename: (req, file, cb) => {
          // Use timestamp + random string to avoid conflicts
          // We'll rename it later once we have the company name from req.body
          const timestamp = Date.now();
          const randomStr = Math.random().toString(36).substring(7);
          const tempFilename = `temp-${timestamp}-${randomStr}.csv`;
          cb(null, tempFilename);
        },
      }),
      fileFilter: (req, file, cb) => {
        // Only allow CSV files
        if (extname(file.originalname).toLowerCase() !== '.csv') {
          return cb(new BadRequestException('Only CSV files are allowed'), false);
        }
        cb(null, true);
      },
      limits: {
        fileSize: 10 * 1024 * 1024, // 10MB max
      },
    })
  )
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Upload CSV rate file (ADMIN only)',
    description:
      'Upload a CSV file containing shipping rates for a carrier company. File must be valid CSV format with required columns. Maximum file size: 10MB.',
  })
  @ApiBody({
    schema: {
      type: 'object',
      required: ['companyName', 'companyEmail', 'file'],
      properties: {
        companyName: {
          type: 'string',
          description: 'Carrier company name',
          example: 'SSC Consolidation',
        },
        companyEmail: {
          type: 'string',
          format: 'email',
          description: 'Email address for booking requests',
          example: 'bookings@sscconsolidation.com',
        },
        file: {
          type: 'string',
          format: 'binary',
          description: 'CSV file to upload',
        },
      },
    },
  })
  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'CSV file uploaded and validated successfully',
    type: CsvRateUploadResponseDto,
  })
  @ApiResponse({
    status: 400,
    description: 'Invalid file format or validation failed',
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - Admin role required',
  })
  async uploadCsv(
    @UploadedFile() file: Express.Multer.File,
    @Body() dto: CsvRateUploadDto,
    @CurrentUser() user: UserPayload
  ): Promise<CsvRateUploadResponseDto> {
    this.logger.log(`[Admin: ${user.email}] Uploading CSV for company: ${dto.companyName}`);

    if (!file) {
      throw new BadRequestException('File is required');
    }

    try {
      // Generate final filename based on company name
      const sanitizedCompanyName = dto.companyName
        .toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[^a-z0-9-]/g, '');
      const finalFilename = `${sanitizedCompanyName}.csv`;

      // Auto-convert CSV if needed (FOB FRET → Standard format)
      const conversionResult = await this.csvConverter.autoConvert(file.path, dto.companyName);
      const filePathToValidate = conversionResult.convertedPath;

      if (conversionResult.wasConverted) {
        this.logger.log(
          `Converted ${conversionResult.rowsConverted} rows from FOB FRET format to standard format`
        );
      }

      // Validate CSV file structure using the converted path
      const validation = await this.csvLoader.validateCsvFile(filePathToValidate);

      if (!validation.valid) {
        this.logger.error(
          `CSV validation failed for ${dto.companyName}: ${validation.errors.join(', ')}`
        );
        throw new BadRequestException({
          message: 'CSV validation failed',
          errors: validation.errors,
        });
      }

      // Load rates to verify parsing using the converted path
      // Pass company name from form to override CSV column value
      const rates = await this.csvLoader.loadRatesFromCsv(
        filePathToValidate,
        dto.companyEmail,
        dto.companyName
      );
      const ratesCount = rates.length;

      this.logger.log(`Successfully parsed ${ratesCount} rates from ${file.filename}`);

      // Rename file to final name (company-name.csv)
      const finalPath = path.join(path.dirname(filePathToValidate), finalFilename);

      // Delete old file if exists
      if (fs.existsSync(finalPath)) {
        fs.unlinkSync(finalPath);
        this.logger.log(`Deleted old file: ${finalFilename}`);
      }

      // Rename temp file to final name
      fs.renameSync(filePathToValidate, finalPath);
      this.logger.log(`Renamed ${file.filename} to ${finalFilename}`);

      // Upload CSV file to MinIO/S3
      let minioObjectKey: string | null = null;
      try {
        const csvBuffer = fs.readFileSync(finalPath);
        const bucket = this.configService.get<string>('AWS_S3_BUCKET', 'xpeditis-csv-rates');
        const objectKey = `csv-rates/${finalFilename}`;

        await this.s3Storage.upload({
          bucket,
          key: objectKey,
          body: csvBuffer,
          contentType: 'text/csv',
          metadata: {
            companyName: dto.companyName,
            companyEmail: dto.companyEmail,
            uploadedBy: user.email,
            uploadedAt: new Date().toISOString(),
          },
        });

        minioObjectKey = objectKey;
        this.logger.log(`✅ CSV file uploaded to MinIO: ${bucket}/${objectKey}`);
      } catch (error: any) {
        this.logger.error(
          `⚠️  Failed to upload CSV to MinIO (will continue with local storage): ${error.message}`
        );
        // Don't fail the entire operation if MinIO upload fails
        // The file is still available locally
      }

      // Check if config exists for this company
      const existingConfig = await this.csvConfigRepository.findByCompanyName(dto.companyName);

      if (existingConfig) {
        // Update existing configuration
        await this.csvConfigRepository.update(existingConfig.id, {
          csvFilePath: finalFilename,
          uploadedAt: new Date(),
          uploadedBy: user.id,
          rowCount: ratesCount,
          lastValidatedAt: new Date(),
          metadata: {
            ...existingConfig.metadata,
            companyEmail: dto.companyEmail, // Store email in metadata
            minioObjectKey, // Store MinIO object key
            lastUpload: {
              timestamp: new Date().toISOString(),
              by: user.email,
              ratesCount,
            },
          },
        });

        this.logger.log(`Updated CSV config for company: ${dto.companyName}`);
      } else {
        // Create new configuration
        await this.csvConfigRepository.create({
          companyName: dto.companyName,
          csvFilePath: finalFilename,
          type: 'CSV_ONLY',
          hasApi: false,
          apiConnector: null,
          isActive: true,
          uploadedAt: new Date(),
          uploadedBy: user.id,
          rowCount: ratesCount,
          lastValidatedAt: new Date(),
          metadata: {
            uploadedBy: user.email,
            description: `${dto.companyName} shipping rates`,
            companyEmail: dto.companyEmail, // Store email in metadata
            minioObjectKey, // Store MinIO object key
          },
        });

        this.logger.log(`Created new CSV config for company: ${dto.companyName}`);
      }

      return {
        success: true,
        ratesCount,
        csvFilePath: finalFilename,
        companyName: dto.companyName,
        uploadedAt: new Date(),
      };
    } catch (error: any) {
      this.logger.error(`CSV upload failed: ${error?.message || 'Unknown error'}`, error?.stack);
      throw error;
    }
  }

  /**
   * Get all CSV rate configurations
   */
  @Get('config')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get all CSV rate configurations (ADMIN only)',
    description: 'Returns list of all CSV rate configurations with upload details.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'List of CSV rate configurations',
    type: [CsvRateConfigDto],
  })
  async getAllConfigs(): Promise<CsvRateConfigDto[]> {
    this.logger.log('Fetching all CSV rate configs (admin)');

    const configs = await this.csvConfigRepository.findAll();
    return this.csvRateMapper.mapConfigEntitiesToDtos(configs);
  }

  /**
   * Get configuration for specific company
   */
  @Get('config/:companyName')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get CSV configuration for specific company (ADMIN only)',
    description: 'Returns CSV rate configuration details for a specific carrier.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'CSV rate configuration',
    type: CsvRateConfigDto,
  })
  @ApiResponse({
    status: 404,
    description: 'Company configuration not found',
  })
  async getConfigByCompany(@Param('companyName') companyName: string): Promise<CsvRateConfigDto> {
    this.logger.log(`Fetching CSV config for company: ${companyName}`);

    const config = await this.csvConfigRepository.findByCompanyName(companyName);

    if (!config) {
      throw new BadRequestException(`No CSV configuration found for company: ${companyName}`);
    }

    return this.csvRateMapper.mapConfigEntityToDto(config);
  }

  /**
   * Validate CSV file
   */
  @Post('validate/:companyName')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Validate CSV file for company (ADMIN only)',
    description:
      'Validates the CSV file structure and data for a specific company without uploading.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Validation result',
    type: CsvFileValidationDto,
  })
  async validateCsvFile(@Param('companyName') companyName: string): Promise<CsvFileValidationDto> {
    this.logger.log(`Validating CSV file for company: ${companyName}`);

    const config = await this.csvConfigRepository.findByCompanyName(companyName);

    if (!config) {
      throw new BadRequestException(`No CSV configuration found for company: ${companyName}`);
    }

    const result = await this.csvLoader.validateCsvFile(config.csvFilePath);

    // Update validation timestamp
    if (result.valid && result.rowCount) {
      await this.csvConfigRepository.updateValidationInfo(companyName, result.rowCount, result);
    }

    return result;
  }

  /**
   * Delete CSV rate configuration
   */
  @Delete('config/:companyName')
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({
    summary: 'Delete CSV rate configuration (ADMIN only)',
    description:
      'Deletes the CSV rate configuration for a company. Note: This does not delete the actual CSV file.',
  })
  @ApiResponse({
    status: HttpStatus.NO_CONTENT,
    description: 'Configuration deleted successfully',
  })
  @ApiResponse({
    status: 404,
    description: 'Company configuration not found',
  })
  async deleteConfig(
    @Param('companyName') companyName: string,
    @CurrentUser() user: UserPayload
  ): Promise<void> {
    this.logger.warn(`[Admin: ${user.email}] Deleting CSV config for company: ${companyName}`);

    await this.csvConfigRepository.delete(companyName);

    this.logger.log(`Deleted CSV config for company: ${companyName}`);
  }

  /**
   * List all CSV files (Frontend compatibility endpoint)
   * Maps to GET /files for compatibility with frontend API client
   */
  @Get('files')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'List all CSV files (ADMIN only)',
    description:
      'Returns list of all uploaded CSV files with metadata. Alias for /config endpoint.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'List of CSV files',
    schema: {
      type: 'object',
      properties: {
        files: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              filename: { type: 'string', example: 'ssc-consolidation.csv' },
              size: { type: 'number', example: 2048 },
              uploadedAt: { type: 'string', format: 'date-time' },
              rowCount: { type: 'number', example: 150 },
            },
          },
        },
      },
    },
  })
  async listFiles(): Promise<{ files: any[] }> {
    this.logger.log('Fetching all CSV files (frontend compatibility)');

    const configs = await this.csvConfigRepository.findAll();

    // Map configs to file info format expected by frontend
    const files = configs.map(config => {
      const filePath = path.join(
        process.cwd(),
        'apps/backend/src/infrastructure/storage/csv-storage/rates',
        config.csvFilePath
      );

      let fileSize = 0;
      try {
        const stats = fs.statSync(filePath);
        fileSize = stats.size;
      } catch (error) {
        this.logger.warn(`Could not get file size for ${config.csvFilePath}`);
      }

      return {
        filename: config.csvFilePath,
        size: fileSize,
        uploadedAt: config.uploadedAt.toISOString(),
        rowCount: config.rowCount,
        companyEmail: config.metadata?.companyEmail ?? null,
      };
    });

    return { files };
  }

  /**
   * Delete CSV file (Frontend compatibility endpoint)
   * Maps to DELETE /files/:filename
   */
  @Delete('files/:filename')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Delete CSV file by filename (ADMIN only)',
    description: 'Deletes a CSV file and its configuration from the system.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'File deleted successfully',
    schema: {
      type: 'object',
      properties: {
        success: { type: 'boolean', example: true },
        message: { type: 'string', example: 'File deleted successfully' },
      },
    },
  })
  @ApiResponse({
    status: 404,
    description: 'File not found',
  })
  async deleteFile(
    @Param('filename') filename: string,
    @CurrentUser() user: UserPayload
  ): Promise<{ success: boolean; message: string }> {
    this.logger.warn(`[Admin: ${user.email}] Deleting CSV file: ${filename}`);

    // Find config by file path
    const configs = await this.csvConfigRepository.findAll();
    const config = configs.find(c => c.csvFilePath === filename);

    if (!config) {
      throw new BadRequestException(`No configuration found for file: ${filename}`);
    }

    // Delete the file from filesystem
    const filePath = path.join(
      process.cwd(),
      'apps/backend/src/infrastructure/storage/csv-storage/rates',
      filename
    );

    try {
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
        this.logger.log(`Deleted file: ${filePath}`);
      }
    } catch (error: any) {
      this.logger.error(`Failed to delete file ${filePath}: ${error.message}`);
    }

    // Delete from MinIO/S3 if it exists there
    const minioObjectKey = config.metadata?.minioObjectKey as string | undefined;
    if (minioObjectKey) {
      try {
        const bucket = this.configService.get<string>('AWS_S3_BUCKET', 'xpeditis-csv-rates');
        await this.s3Storage.delete({ bucket, key: minioObjectKey });
        this.logger.log(`✅ Deleted file from MinIO: ${bucket}/${minioObjectKey}`);
      } catch (error: any) {
        this.logger.error(`⚠️  Failed to delete file from MinIO: ${error.message}`);
        // Don't fail the operation if MinIO deletion fails
      }
    }

    // Delete the configuration
    await this.csvConfigRepository.delete(config.companyName);

    this.logger.log(`Deleted CSV config and file for: ${config.companyName}`);

    return {
      success: true,
      message: `File ${filename} deleted successfully`,
    };
  }
}
