import {
  BadRequestException,
  Body,
  Controller,
  Post,
  Request,
  UploadedFile,
  UseGuards,
  UseInterceptors,
  Logger,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { UploadService } from './upload.service';
import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';
import { CombinedAuthGuard } from '../auth/combined-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';
import { UserRole } from '../user/user-role.enum';
import { errorMessages } from '../i18n/messages';

import {
  DEFAULT_CHUNK_SIZE,
  MIN_CHUNK_SIZE,
  MAX_CHUNK_SIZE,
  DEFAULT_CHUNK_OVERLAP,
  DEFAULT_MAX_OVERLAP_RATIO,
  MAX_FILE_SIZE,
  DEFAULT_LANGUAGE
} from '../common/constants';
import { I18nService } from '../i18n/i18n.service';
import { isAllowedByExtension, IMAGE_MIME_TYPES } from '../common/file-support.constants';

export interface UploadConfigDto {
  chunkSize?: string;
  chunkOverlap?: string;
  embeddingModelId?: string;
  mode?: 'fast' | 'precise'; // Processing mode
  groupIds?: string; // JSON string of group IDs
}

@Controller('upload')
@UseGuards(CombinedAuthGuard, RolesGuard)
export class UploadController {
  private readonly logger = new Logger(UploadController.name);

  constructor(
    private readonly uploadService: UploadService,
    private readonly knowledgeBaseService: KnowledgeBaseService,
    private readonly i18nService: I18nService,
  ) { }

  @Post()
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  @UseInterceptors(
    FileInterceptor('file', {
      fileFilter: (req, file, cb) => {
        // Check by image MIME type or extension
        const isAllowed = IMAGE_MIME_TYPES.includes(file.mimetype) ||
          isAllowedByExtension(file.originalname);

        if (isAllowed) {
          cb(null, true);
        } else {
          cb(
            new BadRequestException(
              (errorMessages[DEFAULT_LANGUAGE]?.uploadTypeUnsupported || errorMessages['ja'].uploadTypeUnsupported).replace('{type}', file?.mimetype ?? 'unknown')
            ),
            false,
          );
        }
      },
    }),
  )
  async uploadFile(
    @UploadedFile() file: Express.Multer.File,
    @Request() req,
    @Body() config: UploadConfigDto,
  ) {
    if (!file) {
      throw new BadRequestException(this.i18nService.getMessage('uploadNoFile'));
    }

    // Validate file size（frontend limit + backend validation）
    const maxFileSize = parseInt(
      process.env.MAX_FILE_SIZE || String(MAX_FILE_SIZE),
    ); // 100MB
    if (file.size > maxFileSize) {
      throw new BadRequestException(
        this.i18nService.formatMessage('uploadSizeExceeded', { size: this.formatBytes(file.size), max: this.formatBytes(maxFileSize) }),
      );
    }

    // Validate embedding model config
    if (!config.embeddingModelId) {
      throw new BadRequestException(this.i18nService.getMessage('uploadModelRequired'));
    }

    this.logger.log(
      `User ${req.user.id} uploaded file: ${file.originalname} (${this.formatBytes(file.size)})`,
    );

    const fileInfo = await this.uploadService.processUploadedFile(file);

    // Parse config parameters and set safe default values
    const indexingConfig = {
      chunkSize: config.chunkSize ? parseInt(config.chunkSize) : DEFAULT_CHUNK_SIZE,
      chunkOverlap: config.chunkOverlap ? parseInt(config.chunkOverlap) : DEFAULT_CHUNK_OVERLAP,
      embeddingModelId: config.embeddingModelId || null,
      groupIds: config.groupIds ? JSON.parse(config.groupIds) : [],
    };

    // Ensure overlap <= 50% of chunk size
    if (indexingConfig.chunkOverlap > indexingConfig.chunkSize * DEFAULT_MAX_OVERLAP_RATIO) {
      indexingConfig.chunkOverlap = Math.floor(indexingConfig.chunkSize * DEFAULT_MAX_OVERLAP_RATIO);
      this.logger.warn(
        this.i18nService.formatMessage('overlapAdjusted', { newSize: indexingConfig.chunkOverlap }),
      );
    }

    // Save to database and trigger indexing（async）
    const kb = await this.knowledgeBaseService.createAndIndex(
      fileInfo,
      req.user.id,
      req.user.tenantId, // Ensure tenantId is passed down
      {
        ...indexingConfig,
        mode: config.mode || 'fast',
      } as any, // Bypass strict type check for now or cast to correct type
    );

    return {
      message: this.i18nService.getMessage('uploadSuccess'),
      id: kb.id,
      filename: fileInfo.filename,
      originalname: fileInfo.originalname,
      status: kb.status,
      mode: config.mode || 'fast',
      config: indexingConfig,
      estimatedChunks: Math.ceil(file.size / (indexingConfig.chunkSize * 4)), // Estimated chunk count
    };
  }

  @Post('text')
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  async uploadText(
    @Request() req,
    @Body() body: {
      content: string;
      title: string;
      chunkSize?: string;
      chunkOverlap?: string;
      embeddingModelId?: string;
      mode?: 'fast' | 'precise';
    }
  ) {
    if (!body.content || !body.title) {
      throw new BadRequestException(this.i18nService.getMessage('contentAndTitleRequired'));
    }

    const fs = await import('fs');
    const path = await import('path');
    const uploadPath = process.env.UPLOAD_FILE_PATH || './uploads';

    if (!fs.existsSync(uploadPath)) {
      fs.mkdirSync(uploadPath, { recursive: true });
    }

    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    const filename = `note-${uniqueSuffix}.md`;
    const filePath = path.join(uploadPath, filename);

    // Save content to file (mocking a file upload)
    fs.writeFileSync(filePath, body.content, 'utf8');

    const fileSize = Buffer.byteLength(body.content, 'utf8');

    // Mimic Multer file info
    const fileInfo = {
      filename: filename,
      originalname: body.title.endsWith('.md') ? body.title : `${body.title}.md`,
      size: fileSize,
      mimetype: 'text/markdown',
      path: filePath
    };

    // Validating Config
    if (!body.embeddingModelId) {
      throw new BadRequestException(this.i18nService.getMessage('uploadModelRequired'));
    }

    const indexingConfig = {
      chunkSize: body.chunkSize ? parseInt(body.chunkSize) : DEFAULT_CHUNK_SIZE,
      chunkOverlap: body.chunkOverlap ? parseInt(body.chunkOverlap) : DEFAULT_CHUNK_OVERLAP,
      embeddingModelId: body.embeddingModelId || null,
    };

    if (indexingConfig.chunkOverlap > indexingConfig.chunkSize * DEFAULT_MAX_OVERLAP_RATIO) {
      indexingConfig.chunkOverlap = Math.floor(indexingConfig.chunkSize * DEFAULT_MAX_OVERLAP_RATIO);
    }

    const kb = await this.knowledgeBaseService.createAndIndex(
      fileInfo,
      req.user.id,
      req.user.tenantId,
      {
        ...indexingConfig,
        mode: body.mode || 'fast',
      } as any
    );

    return {
      message: this.i18nService.getMessage('uploadTextSuccess'),
      id: kb.id,
      filename: fileInfo.filename,
      originalname: fileInfo.originalname,
      status: kb.status,
      config: indexingConfig
    };
  }

  private formatBytes(bytes: number): string {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }
}
