import {
  Controller,
  Post,
  Get,
  Delete,
  Put,
  Body,
  Param,
  Res,
  Query,
  UseGuards,
  UseInterceptors,
  UploadedFile,
  HttpCode,
  HttpStatus,
  Logger,
  BadRequestException,
  NotFoundException,
  ForbiddenException,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBearerAuth,
  ApiParam,
  ApiConsumes,
} from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { DocumentsService } from './documents.service';
import { UploadDocumentDto } from './dto/upload-document.dto';
import { DocumentResponseDto } from './dto/document-response.dto';
import { DocumentVersionResponseDto } from './dto/document-version-response.dto';
import { DownloadUrlResponseDto } from './dto/download-url-response.dto';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { CorrelationId } from '../../common/decorators/correlation-id.decorator';
import { JwtPayload } from '../../common/interfaces/request-context.interface';

@ApiTags('Documents')
@Controller()
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'))
export class DocumentsController {
  private readonly logger = new Logger(DocumentsController.name);

  constructor(private readonly documentsService: DocumentsService) {}

  @Post('documents/upload')
  @UseInterceptors(
    FileInterceptor('file', {
      limits: {
        fileSize: 10 * 1024 * 1024,
      },
    }),
  )
  @HttpCode(HttpStatus.CREATED)
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Upload document (alternative endpoint)',
    description: 'Upload a new document for a license request using requestId in body',
  })
  @ApiResponse({
    status: 201,
    description: 'Document uploaded successfully',
    type: DocumentResponseDto,
  })
  @ApiResponse({
    status: 400,
    description: 'Invalid file or request data',
  })
  @ApiResponse({
    status: 404,
    description: 'Request not found',
  })
  async uploadDocumentAlt(
    @UploadedFile() file: Express.Multer.File,
    @Body()
    uploadDto: UploadDocumentDto & {
      requestId: string;
      documentType: string;
      description?: string;
    },
    @CurrentUser() user: any,
    @CorrelationId() correlationId: string,
  ): Promise<any> {
    this.logger.debug(
      `[${correlationId}] Uploading document for request: ${uploadDto.requestId}, docType: ${uploadDto.documentType}`,
    );

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

    if (!uploadDto.requestId) {
      throw new BadRequestException('requestId is required');
    }

    if (!uploadDto.documentType) {
      throw new BadRequestException('documentType is required');
    }

    const document = await this.documentsService.upload(
      uploadDto.requestId,
      file,
      { docType: uploadDto.documentType, description: uploadDto.description },
      user.sub,
    );

    const storagePath = `requests/${uploadDto.requestId}/${uploadDto.documentType}`;
    return {
      id: document.id,
      fileName: document.originalFilename,
      docType: document.docType,
      documentType: document.docType,
      fileType: file.mimetype,
      mimeType: file.mimetype,
      size: file.size,
      fileSize: file.size,
      fileHash: document.currentHash,
      hash: document.currentHash,
      requestId: uploadDto.requestId,
      version: document.currentVersion,
      uploadedBy: user.sub,
      minioPath: storagePath,
      storagePath: storagePath,
      description: uploadDto.description || null,
      createdAt: document.createdAt,
      uploadedAt: document.createdAt,
      scanned: true,
      blockchainTxHash: null,
    };
  }

  @Post('requests/:requestId/documents')
  @UseInterceptors(
    FileInterceptor('file', {
      limits: {
        fileSize: 10 * 1024 * 1024,
      },
    }),
  )
  @HttpCode(HttpStatus.CREATED)
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Upload document',
    description: 'Upload a new document for a license request',
  })
  @ApiParam({
    name: 'requestId',
    description: 'License request ID (UUID)',
  })
  @ApiResponse({
    status: 201,
    description: 'Document uploaded successfully',
    type: DocumentResponseDto,
  })
  @ApiResponse({
    status: 400,
    description: 'Invalid file or request data',
  })
  @ApiResponse({
    status: 404,
    description: 'Request not found',
  })
  async uploadDocument(
    @Param('requestId') requestId: string,
    @UploadedFile() file: Express.Multer.File,
    @Body() uploadDto: UploadDocumentDto,
    @CurrentUser() user: JwtPayload,
    @CorrelationId() correlationId: string,
  ): Promise<DocumentResponseDto> {
    this.logger.debug(
      `[${correlationId}] Uploading document for request: ${requestId}, docType: ${uploadDto.docType}`,
    );

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

    const document = await this.documentsService.upload(requestId, file, uploadDto, user.sub);
    return this.mapToResponseDto(document);
  }

  @Get('documents/:documentId')
  @ApiOperation({
    summary: 'Get document metadata',
    description: 'Retrieve metadata for a document',
  })
  @ApiParam({
    name: 'documentId',
    description: 'Document ID (UUID)',
  })
  @ApiResponse({
    status: 200,
    description: 'Document metadata',
    type: DocumentResponseDto,
  })
  @ApiResponse({
    status: 404,
    description: 'Document not found',
  })
  async getDocument(
    @Param('documentId') documentId: string,
    @CorrelationId() correlationId: string,
  ): Promise<DocumentResponseDto> {
    this.logger.debug(`[${correlationId}] Fetching document: ${documentId}`);

    const document = await this.documentsService.findById(documentId);
    return this.mapToResponseDto(document);
  }

  @Get('documents/:documentId/download')
  @ApiOperation({
    summary: 'Download document',
    description: 'Download a document file directly',
  })
  @ApiParam({
    name: 'documentId',
    description: 'Document ID (UUID)',
  })
  @ApiResponse({
    status: 200,
    description: 'Document file downloaded',
  })
  @ApiResponse({
    status: 404,
    description: 'Document not found',
  })
  @ApiResponse({
    status: 403,
    description: 'Access denied',
  })
  async downloadDocument(
    @Param('documentId') documentId: string,
    @Query('version') version: string,
    @Query('inline') inline: string,
    @CurrentUser() user: JwtPayload,
    @CorrelationId() correlationId: string,
    @Res() res: any,
  ): Promise<void> {
    this.logger.debug(
      `[${correlationId}] Downloading document: ${documentId}, version: ${version}, inline: ${inline}`,
    );

    // Validate documentId format
    if (!documentId || documentId === 'undefined' || documentId === 'null') {
      throw new BadRequestException('Invalid document ID');
    }

    // Check authorization
    let canAccess: boolean;
    try {
      canAccess = await this.documentsService.checkUserCanAccessDocument(user.sub, documentId);
    } catch (error: any) {
      if (error instanceof NotFoundException) {
        throw error;
      }
      this.logger.error(`[${correlationId}] Error checking document access: ${error.message}`);
      throw error;
    }

    if (!canAccess) {
      throw new ForbiddenException({
        code: 'AUTH_008',
        message: 'You do not have permission to download this document',
      });
    }

    const versionNum = version ? parseInt(version, 10) : undefined;

    // Validate version number if provided
    if (version && (isNaN(versionNum) || versionNum < 1)) {
      throw new BadRequestException('Invalid version number');
    }

    let fileContent: {
      buffer: Buffer;
      mimeType: string;
      fileName: string;
      fileSize: number;
      hash: string;
    };
    try {
      fileContent = await this.documentsService.getFileContent(documentId, versionNum, user.sub);
    } catch (error: any) {
      this.logger.error(
        `[${correlationId}] Error retrieving file content for document ${documentId}: ${error.message}`,
      );
      throw error;
    }

    const { buffer, mimeType, fileName, fileSize, hash } = fileContent;

    // Sanitize filename for Content-Disposition header
    const sanitizedFileName = fileName.replace(/[^\w\s.-]/g, '_').replace(/\s+/g, '_');

    const disposition = inline === 'true' ? 'inline' : 'attachment';

    res.set({
      'Content-Type': mimeType || 'application/octet-stream',
      'Content-Length': fileSize || buffer.length,
      'Content-Disposition': `${disposition}; filename="${sanitizedFileName}"`,
      'X-File-Hash': hash || '',
      'Cache-Control': 'private, max-age=3600',
      'X-Content-Type-Options': 'nosniff',
      'X-Frame-Options': 'DENY',
    });

    res.send(buffer);
  }

  @Get('documents/:documentId/versions')
  @ApiOperation({
    summary: 'List document versions',
    description: 'Get all versions of a document',
  })
  @ApiParam({
    name: 'documentId',
    description: 'Document ID (UUID)',
  })
  @ApiResponse({
    status: 200,
    description: 'Document versions',
    type: [DocumentVersionResponseDto],
  })
  @ApiResponse({
    status: 404,
    description: 'Document not found',
  })
  async getVersions(
    @Param('documentId') documentId: string,
    @CorrelationId() correlationId: string,
  ): Promise<DocumentVersionResponseDto[]> {
    this.logger.debug(`[${correlationId}] Fetching versions for document: ${documentId}`);

    const versions = await this.documentsService.getVersions(documentId);
    return versions.map(v => ({
      id: v.id,
      documentId: v.documentId,
      version: v.version,
      hash: v.hash,
      minioPath: v.minioPath,
      fileSize: v.fileSize,
      mimeType: v.mimeType,
      uploadedBy: v.uploadedBy,
      blockchainTxHash: v.blockchainTxHash,
      createdAt: v.createdAt,
    }));
  }

  @Put('requests/:requestId/documents/:documentId')
  @UseInterceptors(
    FileInterceptor('file', {
      limits: {
        fileSize: 10 * 1024 * 1024,
      },
    }),
  )
  @HttpCode(HttpStatus.OK)
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Upload new document version',
    description: 'Upload a new version of an existing document',
  })
  @ApiParam({
    name: 'requestId',
    description: 'License request ID (UUID)',
  })
  @ApiParam({
    name: 'documentId',
    description: 'Document ID (UUID)',
  })
  @ApiResponse({
    status: 200,
    description: 'New version uploaded successfully',
    type: DocumentResponseDto,
  })
  @ApiResponse({
    status: 400,
    description: 'Invalid file or request data',
  })
  @ApiResponse({
    status: 404,
    description: 'Request or document not found',
  })
  async uploadVersion(
    @Param('requestId') requestId: string,
    @Param('documentId') documentId: string,
    @UploadedFile() file: Express.Multer.File,
    @CurrentUser() user: JwtPayload,
    @CorrelationId() correlationId: string,
  ): Promise<DocumentResponseDto> {
    this.logger.debug(`[${correlationId}] Uploading new version for document: ${documentId}`);

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

    const document = await this.documentsService.uploadNewVersion(
      requestId,
      documentId,
      file,
      user.sub,
    );
    return this.mapToResponseDto(document);
  }

  @Delete('documents/:documentId')
  @HttpCode(HttpStatus.NO_CONTENT)
  @ApiOperation({
    summary: 'Soft delete document',
    description: 'Soft delete a document (marks as inactive)',
  })
  @ApiParam({
    name: 'documentId',
    description: 'Document ID (UUID)',
  })
  @ApiResponse({
    status: 204,
    description: 'Document deleted successfully',
  })
  @ApiResponse({
    status: 404,
    description: 'Document not found',
  })
  async deleteDocument(
    @Param('documentId') documentId: string,
    @CorrelationId() correlationId: string,
  ): Promise<void> {
    this.logger.debug(`[${correlationId}] Deleting document: ${documentId}`);

    await this.documentsService.softDelete(documentId);
  }

  @Get('requests/:requestId/documents')
  @ApiOperation({
    summary: 'List request documents',
    description: 'Get all documents for a license request',
  })
  @ApiParam({
    name: 'requestId',
    description: 'License request ID (UUID)',
  })
  @ApiResponse({
    status: 200,
    description: 'Request documents',
    type: [DocumentResponseDto],
  })
  async getRequestDocuments(
    @Param('requestId') requestId: string,
    @CorrelationId() correlationId: string,
  ): Promise<DocumentResponseDto[]> {
    this.logger.debug(`[${correlationId}] Fetching documents for request: ${requestId}`);

    const documents = await this.documentsService.findByRequestId(requestId);
    return documents.map(d => this.mapToResponseDto(d));
  }

  private mapToResponseDto(document: any): DocumentResponseDto {
    // Get file size and mimeType from the latest version if available
    let fileSize: number | undefined;
    let mimeType: string | undefined;

    if (document.versions && document.versions.length > 0) {
      // Sort versions by version number descending and get the latest
      const latestVersion = document.versions.reduce(
        (latest: any, v: any) => (v.version > (latest?.version || 0) ? v : latest),
        null,
      );
      if (latestVersion) {
        fileSize = parseInt(latestVersion.fileSize, 10) || undefined;
        mimeType = latestVersion.mimeType;
      }
    }

    return {
      id: document.id,
      requestId: document.requestId,
      docType: document.docType,
      originalFilename: document.originalFilename,
      currentVersion: document.currentVersion,
      currentHash: document.currentHash,
      fileHash: document.currentHash,
      fileSize,
      mimeType,
      minioBucket: document.minioBucket,
      isActive: document.isActive,
      downloadCount: document.downloadCount || 0,
      lastDownloadedAt: document.lastDownloadedAt,
      createdAt: document.createdAt,
      updatedAt: document.updatedAt,
    };
  }
}
