import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Query,
  Request,
  UseGuards,
  Res,
  NotFoundException,
  InternalServerErrorException,
} from '@nestjs/common';
import { Response } from 'express';
import * as path from 'path';
import { Logger } from '@nestjs/common';
import { KnowledgeBaseService } from './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 { Public } from '../auth/public.decorator';
import { KnowledgeBase } from './knowledge-base.entity';
import { ChunkConfigService } from './chunk-config.service';
import { KnowledgeGroupService } from '../knowledge-group/knowledge-group.service';
import { I18nService } from '../i18n/i18n.service';
import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiQuery, ApiParam } from '@nestjs/swagger';

@ApiTags('Knowledge Base')
@Controller('knowledge-bases')
@UseGuards(CombinedAuthGuard, RolesGuard)
export class KnowledgeBaseController {
  private readonly logger = new Logger(KnowledgeBaseController.name);

  constructor(
    private readonly knowledgeBaseService: KnowledgeBaseService,
    private readonly chunkConfigService: ChunkConfigService,
    private readonly knowledgeGroupService: KnowledgeGroupService,
    private readonly i18nService: I18nService,
  ) { }

  @Get()
  @UseGuards(CombinedAuthGuard)
  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: '获取知识库文件列表', description: '获取当前租户的所有知识库文件' })
  @ApiResponse({ status: 200, description: '返回文件列表' })
  async findAll(@Request() req): Promise<KnowledgeBase[]> {
    return this.knowledgeBaseService.findAll(req.user.id, req.user.tenantId);
  }

  @Delete('clear')
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: '清空知识库', description: '删除当前租户的所有知识库文件' })
  @ApiResponse({ status: 200, description: '清空成功' })
  async clearAll(@Request() req): Promise<{ message: string }> {
    await this.knowledgeBaseService.clearAll(req.user.id, req.user.tenantId);
    return { message: this.i18nService.getMessage('kbCleared') };
  }

  @Post('search')
  async search(@Request() req, @Body() body: { query: string; topK?: number }) {
    return this.knowledgeBaseService.searchKnowledge(
      req.user.id,
      req.user.tenantId, // New
      body.query,
      body.topK || 5,
    );
  }

  @Post('rag-search')
  async ragSearch(
    @Request() req,
    @Body() body: { query: string; settings: any },
  ) {
    return this.knowledgeBaseService.ragSearch(
      req.user.id,
      req.user.tenantId, // New
      body.query,
      body.settings,
    );
  }

  @Delete(':id')
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  async deleteFile(
    @Request() req,
    @Param('id') fileId: string,
  ): Promise<{ message: string }> {
    await this.knowledgeBaseService.deleteFile(fileId, req.user.id, req.user.tenantId);
    return { message: this.i18nService.getMessage('fileDeleted') };
  }

  @Post(':id/retry')
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  async retryFile(
    @Request() req,
    @Param('id') fileId: string,
  ): Promise<KnowledgeBase> {
    return this.knowledgeBaseService.retryFailedFile(fileId, req.user.id, req.user.tenantId);
  }

  @Get(':id/chunks')
  async getFileChunks(
    @Request() req,
    @Param('id') fileId: string,
  ) {
    return this.knowledgeBaseService.getFileChunks(fileId, req.user.id, req.user.tenantId);
  }


  /**
   * Get chunk config limits (for frontend slider settings)
   * Query parameter: embeddingModelId - embedding model ID
   */
  @Get('chunk-config/limits')
  async getChunkConfigLimits(
    @Request() req,
    @Query('embeddingModelId') embeddingModelId: string,
  ) {
    if (!embeddingModelId) {
      return {
        maxChunkSize: parseInt(process.env.MAX_CHUNK_SIZE || '8191'),
        maxOverlapSize: parseInt(process.env.MAX_OVERLAP_SIZE || '2000'),
        minOverlapSize: 25,
        defaultChunkSize: 200,
        defaultOverlapSize: 40,
        modelInfo: {
          name: this.i18nService.getMessage('modelNotConfigured'),
          maxInputTokens: parseInt(process.env.MAX_CHUNK_SIZE || '8191'),
          maxBatchSize: 2048,
          expectedDimensions: parseInt(process.env.DEFAULT_VECTOR_DIMENSIONS || '2560'),
        },
      };
    }

    return await this.chunkConfigService.getFrontendLimits(
      embeddingModelId,
      req.user.tenantId,
    );
  }

  // File group management - requires admin permission
  @Post(':id/groups')
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  async addFileToGroups(
    @Param('id') fileId: string,
    @Body() body: { groupIds: string[] },
    @Request() req,
  ) {
    await this.knowledgeGroupService.addFilesToGroup(
      fileId,
      body.groupIds,
      req.user.id,
      req.user.tenantId,
    );
    return { message: this.i18nService.getMessage('groupSyncSuccess') };
  }

  @Delete(':id/groups/:groupId')
  @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
  async removeFileFromGroup(
    @Param('id') fileId: string,
    @Param('groupId') groupId: string,
    @Request() req,
  ) {
    await this.knowledgeGroupService.removeFileFromGroup(
      fileId,
      groupId,
      req.user.id,
      req.user.tenantId,
    );
    return { message: this.i18nService.getMessage('fileDeletedFromGroup') };
  }

  // PDF preview - public access
  @Public()
  @Get(':id/pdf')
  async getPDFPreview(
    @Param('id') fileId: string,
    @Query('token') token: string,
    @Res() res: Response,
  ) {
    try {
      if (!token) {
        throw new NotFoundException(this.i18nService.getMessage('accessDeniedNoToken'));
      }

      const jwt = await import('jsonwebtoken');
      const secret = process.env.JWT_SECRET;
      if (!secret) {
        throw new InternalServerErrorException(this.i18nService.getMessage('jwtSecretRequired'));
      }

      let decoded;
      try {
        decoded = jwt.verify(token, secret) as any;
      } catch {
        throw new NotFoundException(this.i18nService.getMessage('invalidToken'));
      }

      if (decoded.type !== 'pdf-access' || decoded.fileId !== fileId) {
        throw new NotFoundException(this.i18nService.getMessage('invalidToken'));
      }

      const pdfPath = await this.knowledgeBaseService.ensurePDFExists(
        fileId,
        decoded.userId,
        decoded.tenantId, // New
      );

      const fs = await import('fs');
      const path = await import('path');

      if (!fs.existsSync(pdfPath)) {
        throw new NotFoundException(this.i18nService.getMessage('pdfFileNotFound'));
      }

      const stat = fs.statSync(pdfPath);
      const fileName = path.basename(pdfPath);

      if (stat.size === 0) {
        this.logger.warn(`PDF file is empty: ${pdfPath}`);
        try {
          fs.unlinkSync(pdfPath); // Delete empty file
        } catch (e) { }
        throw new NotFoundException(this.i18nService.getMessage('pdfFileEmpty'));
      }

      res.setHeader('Content-Type', 'application/pdf');
      res.setHeader('Content-Length', stat.size);

      const stream = fs.createReadStream(pdfPath);
      stream.pipe(res);
    } catch (error) {
      if (error instanceof NotFoundException) {
        throw error;
      }
      this.logger.error(`PDF preview error: ${error.message}`);
      throw new NotFoundException(this.i18nService.getMessage('pdfConversionFailed'));
    }
  }

  // Get PDF preview URL
  @Get(':id/pdf-url')
  async getPDFUrl(
    @Param('id') fileId: string,
    @Query('force') force: string,
    @Request() req,
  ) {
    try {
      // Trigger PDF conversion
      await this.knowledgeBaseService.ensurePDFExists(fileId, req.user.id, req.user.tenantId, force === 'true');

      // Generate temporary access token
      const jwt = await import('jsonwebtoken');

      const secret = process.env.JWT_SECRET;
      if (!secret) {
        throw new InternalServerErrorException(this.i18nService.getMessage('jwtSecretRequired'));
      }

      const token = jwt.sign(
        { fileId, userId: req.user.id, tenantId: req.user.tenantId, type: 'pdf-access' },
        secret,
        { expiresIn: '1h' }
      );

      return {
        url: `/api/knowledge-bases/${fileId}/pdf?token=${token}`
      };
    } catch (error) {
      if (error.message.includes('LibreOffice')) {
        throw new InternalServerErrorException(this.i18nService.formatMessage('pdfServiceUnavailable', { message: error.message }));
      }
      throw new InternalServerErrorException(error.message);
    }
  }

  @Get(':id/pdf-status')
  async getPDFStatus(
    @Param('id') fileId: string,
    @Request() req,
  ) {
    return await this.knowledgeBaseService.getPDFStatus(fileId, req.user.id, req.user.tenantId);
  }

  // Get specific page of PDF as image
  @Get(':id/page/:index')
  async getPageImage(
    @Param('id') fileId: string,
    @Param('index') index: number,
    @Request() req,
    @Res() res: Response,
  ) {
    try {
      const imagePath = await this.knowledgeBaseService.getPageAsImage(
        fileId,
        Number(index),
        req.user.id,
        req.user.tenantId,
      );

      const fs = await import('fs');
      if (!fs.existsSync(imagePath)) {
        throw new NotFoundException(this.i18nService.getMessage('pageImageNotFound'));
      }

      res.sendFile(path.resolve(imagePath));
    } catch (error) {
      this.logger.error(`Failed to get PDF page image: ${error.message}`);
      throw new NotFoundException(this.i18nService.getMessage('pdfPageImageFailed'));
    }
  }
}
