import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '@chatapp/database';
import { ConfigService } from '@nestjs/config';
import * as Minio from 'minio';
import * as crypto from 'crypto';

@Injectable()
export class FilesService {
  private minioClient: Minio.Client;
  private bucket: string;

  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: ConfigService,
  ) {
    this.bucket = this.configService.get<string>('MINIO_BUCKET') || 'chatapp-files';
    this.minioClient = new Minio.Client({
      endPoint: this.configService.get<string>('MINIO_ENDPOINT') || 'localhost',
      port: parseInt(this.configService.get<string>('MINIO_PORT') || '9000', 10),
      useSSL: this.configService.get<string>('MINIO_USE_SSL') === 'true',
      accessKey: this.configService.get<string>('MINIO_ACCESS_KEY') || 'chatapp',
      secretKey: this.configService.get<string>('MINIO_SECRET_KEY') || '',
    });
  }

  async ensureBucket(): Promise<void> {
    const exists = await this.minioClient.bucketExists(this.bucket);
    if (!exists) {
      await this.minioClient.makeBucket(this.bucket);
    }
  }

  async uploadFile(
    file: Express.Multer.File,
    userId: string,
    workspaceId: string,
  ) {
    await this.ensureBucket();

    const ext = file.originalname.split('.').pop() || '';
    const fileHash = crypto.randomBytes(16).toString('hex');
    const storageKey = `${workspaceId}/${userId}/${fileHash}.${ext}`;

    await this.minioClient.putObject(
      this.bucket,
      storageKey,
      file.buffer,
      file.size,
      { 'Content-Type': file.mimetype },
    );

    // Determine file type category
    let fileType = 'file';
    if (file.mimetype.startsWith('image/')) fileType = 'image';
    else if (file.mimetype.startsWith('video/')) fileType = 'video';
    else if (file.mimetype.startsWith('audio/')) fileType = 'audio';

    const record = await this.prisma.file.create({
      data: {
        workspaceId,
        uploaderId: userId,
        filename: file.originalname,
        fileType,
        fileExtension: ext || null,
        mimeType: file.mimetype,
        sizeBytes: BigInt(file.size),
        storageKey,
        storageBucket: this.bucket,
        storageProvider: 's3',
      },
    });

    return {
      id: record.id,
      filename: record.filename,
      fileType: record.fileType,
      mimeType: record.mimeType,
      size: file.size,
      url: `/files/${record.id}/download`,
    };
  }

  async findById(id: string, wsId?: string) {
    const where: any = { id, isDeleted: false };
    if (wsId) where.workspaceId = wsId;
    const file = await this.prisma.file.findUnique({ where });
    if (!file) throw new NotFoundException('File not found');
    return file;
  }

  async getDownloadStream(fileId: string, wsId: string) {
    const file = await this.findById(fileId, wsId);
    return {
      stream: this.minioClient.getObject(file.storageBucket, file.storageKey),
      filename: file.filename,
      mimeType: file.mimeType,
    };
  }

  async shareFile(fileId: string, channelId: string | null, messageId: string | null, sharedBy: string) {
    return this.prisma.fileShare.create({
      data: {
        fileId,
        channelId,
        messageId,
        sharedBy,
      },
    });
  }

  async delete(id: string, userId: string, wsId: string) {
    const file = await this.findById(id, wsId);
    if (file.uploaderId !== userId) {
      throw new BadRequestException('Only the uploader can delete this file');
    }

    // Soft delete in DB
    await this.prisma.file.update({
      where: { id },
      data: { isDeleted: true, deletedAt: new Date() },
    });

    // Remove from MinIO
    try {
      await this.minioClient.removeObject(file.storageBucket, file.storageKey);
    } catch {
      // Ignore minio errors on delete — the DB record is the source of truth
    }

    return { deleted: true };
  }

  async listByWorkspace(workspaceId: string, page = 1, limit = 20, channelId?: string) {
    const skip = (page - 1) * limit;
    const where: any = { workspaceId, isDeleted: false };

    if (channelId) {
      where.shares = { some: { channelId } };
    }

    const [files, total] = await Promise.all([
      this.prisma.file.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
      }),
      this.prisma.file.count({ where }),
    ]);

    return {
      files: files.map((f) => ({
        id: f.id,
        filename: f.filename,
        fileType: f.fileType,
        mimeType: f.mimeType,
        size: Number(f.sizeBytes),
        url: `/files/${f.id}/download`,
        uploadedAt: f.createdAt,
      })),
      total,
      page,
      limit,
    };
  }

  async getSignedUrl(fileId: string, wsId: string) {
    const file = await this.findById(fileId, wsId);

    // Generate a presigned URL valid for 1 hour
    const url = await this.minioClient.presignedGetObject(
      file.storageBucket,
      file.storageKey,
      3600,
    );

    return {
      id: file.id,
      filename: file.filename,
      url,
      expires_in: 3600,
    };
  }
}