import { BadRequestException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { createReadStream, existsSync, promises as fs } from 'fs';
import { copyFile, mkdir, stat, unlink, writeFile } from 'fs/promises';
import * as path from 'path';
import { basename, join, sep } from 'path';
import { Stream } from 'stream';
import { AbstractProvider } from './abstract.provider';

export class LocalProvider extends AbstractProvider {
  constructor(
    private setting: Record<string, string>,
    private jwt: JwtService
  ) {
    super();
    this.initValidation();
  }

  async initValidation() {
    if (!this.setting['storage-local-path']) {
      throw new BadRequestException(
        `You must set the storage-local-path in the setting.`,
      );
    }

    if (!existsSync(this.setting['storage-local-path'])) {
      await this.createFolderRecursive(this.setting['storage-local-path']);
    }
  }

  async createFolderRecursive(path: string) {
    const folders = join(path).split(sep);
    let currentPath = '';
    for (const folder of folders) {
      currentPath = join(currentPath, folder);
      if (!existsSync(currentPath)) {
        await mkdir(currentPath, { recursive: true });
      }
    }
  }

  private resolveFilepath(filepath: string): string {
    return path.isAbsolute(filepath) ? filepath : path.resolve(filepath);
  }

  async uploadFromString(
    destination: string,
    filename: string,
    fileContent: string,
    mimetype?: string,
  ): Promise<any> {
    const storagePath = join(this.setting['storage-local-path'], destination);

    if (!storagePath) {
      throw new BadRequestException(
        `You must set the storage-local-path in the setting.`,
      );
    }

    if (!existsSync(storagePath)) {
      await this.createFolderRecursive(storagePath);
    }

    if (!existsSync(storagePath)) {
      throw new BadRequestException(
        `The storage path does not exist: ${storagePath}`,
      );
    }

    const filePath = join(storagePath, this.getFilename(filename));

    await writeFile(filePath, fileContent);

    return filePath;
  }

  async upload(destination: string, file: MulterFile): Promise<any> {
    const storagePath = join(this.setting['storage-local-path'], destination);

    if (!storagePath) {
      throw new BadRequestException(
        `You must set the storage-local-path in the setting.`,
      );
    }

    if (!existsSync(storagePath)) {
      await this.createFolderRecursive(storagePath);
    }

    if (!existsSync(storagePath)) {
      throw new BadRequestException(
        `The storage path does not exist: ${storagePath}`,
      );
    }
    const filePath = join(storagePath, this.getFilename(file.originalname));

    await writeFile(filePath, file.buffer);

    return filePath;
  }

  async uploadFromPath(
    destination: string,
    filePath: string,
    options?: { filename?: string; mimetype?: string },
  ): Promise<any> {
    const sourcePath = this.resolveFilepath(filePath);
    if (!existsSync(sourcePath)) {
      throw new BadRequestException(`File not found: ${sourcePath}`);
    }

    const sourceStats = await stat(sourcePath);
    if (!sourceStats.isFile()) {
      throw new BadRequestException(`Invalid file path: ${sourcePath}`);
    }

    const storagePath = join(this.setting['storage-local-path'], destination);

    if (!existsSync(storagePath)) {
      await this.createFolderRecursive(storagePath);
    }

    const targetFilename = this.getFilename(options?.filename ?? basename(sourcePath));
    const targetPath = join(storagePath, targetFilename);
    await copyFile(sourcePath, targetPath);

    return targetPath;
  }

  async delete(filepath: string): Promise<any> {
    const fullPath = this.resolveFilepath(filepath);

    if (!existsSync(fullPath)) {
      throw new BadRequestException(`File not found: ${fullPath}`);
    }

    return unlink(fullPath);
  }

  async readStream(filepath: string): Promise<Stream> {
    const fullPath = this.resolveFilepath(filepath);

    if (!existsSync(fullPath)) {
      throw new BadRequestException(`File not found: ${fullPath}`);
    }

    return createReadStream(fullPath);
  }

  async metaData(filepath: string): Promise<any> {
    const fullPath = this.resolveFilepath(filepath);

    if (!existsSync(fullPath)) {
      throw new BadRequestException(`File not found: ${fullPath}`);
    }

    return {
      size: (await this.buffer(fullPath)).length,
    };
  }

  async buffer(filepath: string): Promise<Buffer> {
    const fullPath = this.resolveFilepath(filepath);

    if (!existsSync(fullPath)) {
      throw new BadRequestException(`File not found: ${fullPath}`);
    }

    return fs.readFile(fullPath);
  }

  async tempURL(filepath: string, expires = 3600): Promise<any> {
    try {
      const token = this.jwt.sign(
        { filepath },
        {
          secret: process.env.JWT_SECRET,
          expiresIn: expires,
        },
      );

      const host = process.env.HOST || 'localhost';
      const port = process.env.PORT || 3100;
      
      return `http://${host}:${port}/file/download/${token}`;
    } catch (error) {
      throw new BadRequestException(error);
    }
  }
}
