import { v4 as uuidv4 } from 'uuid';
import Axios from 'axios';
import { baseURLApi } from '../../config';

function extractExtensionFrom(filename) {
  if (!filename) {
    return null;
  }

  const regex = /(?:\.([^.]+))?$/;
  return regex.exec(filename)[1];
}

export default class FileUploader {
  static validate(file, schema) {
    if (!schema) {
      return;
    }

    if (schema.image) {
      if (!file.type.startsWith('image')) {
        throw new Error('You must upload an image');
      }
    }

    if (schema.size && file.size > schema.size) {
      throw new Error('File is too big.');
    }

    const extension = extractExtensionFrom(file.name);

    if (schema.formats && !schema.formats.includes(extension)) {
      throw new Error('Invalid format');
    }
  }

  static async upload(path, file, schema) {
    try {
      this.validate(file, schema);
    } catch (error) {
      return Promise.reject(error);
    }

    const extension = extractExtensionFrom(file.name);
    const id = uuidv4();
    const filename = `${id}.${extension}`;
    const privateUrl = `${path}/${filename}`;

    const publicUrl = await this.uploadToServer(file, path, filename);

    return {
      id: id,
      name: file.name,
      sizeInBytes: file.size,
      privateUrl,
      publicUrl,
      new: true,
    };
  }

  static async uploadChunked(path, file, schema, options = {}) {
    try {
      this.validate(file, schema);
    } catch (error) {
      return Promise.reject(error);
    }

    const chunkSize =
      Number(options.chunkSize) > 0
        ? Number(options.chunkSize)
        : 5 * 1024 * 1024;
    const maxRetries =
      Number(options.maxRetries) > 0 ? Number(options.maxRetries) : 3;
    const onProgress =
      typeof options.onProgress === 'function' ? options.onProgress : null;
    const onStatus =
      typeof options.onStatus === 'function' ? options.onStatus : null;
    const signal = options.signal || null;
    const extension = extractExtensionFrom(file.name);
    const id = uuidv4();
    const filename = extension ? `${id}.${extension}` : id;
    const privateUrl = `${path}/${filename}`;
    const totalChunks = Math.max(1, Math.ceil(file.size / chunkSize));

    if (signal?.aborted) {
      throw new Error('Upload aborted');
    }

    const initResponse = await Axios.post(
      '/file/upload-sessions/init',
      {
        folder: path,
        filename,
        size: file.size,
        contentType: file.type || '',
        totalChunks,
      },
      { signal },
    );

    const sessionId = initResponse?.data?.sessionId;

    if (!sessionId) {
      throw new Error('Upload session was not created');
    }

    for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
      if (signal?.aborted) {
        throw new Error('Upload aborted');
      }

      const start = chunkIndex * chunkSize;
      const end = Math.min(file.size, start + chunkSize);
      const chunk = file.slice(start, end);

      if (onStatus) {
        onStatus('uploading', {
          chunkIndex,
          totalChunks,
        });
      }

      let retry = 0;

      while (retry <= maxRetries) {
        try {
          await Axios.put(
            `/file/upload-sessions/${sessionId}/chunks/${chunkIndex}`,
            chunk,
            {
              headers: {
                'Content-Type': 'application/octet-stream',
              },
              signal,
            },
          );
          break;
        } catch (error) {
          if (signal?.aborted) {
            throw new Error('Upload aborted');
          }
          retry += 1;
          if (retry > maxRetries) {
            throw error;
          }
          await new Promise((resolve) => setTimeout(resolve, 500 * retry));
        }
      }

      if (onProgress) {
        const percent = Math.round(((chunkIndex + 1) / totalChunks) * 100);
        onProgress(percent, {
          chunkIndex: chunkIndex + 1,
          totalChunks,
        });
      }
    }

    if (signal?.aborted) {
      throw new Error('Upload aborted');
    }

    if (onStatus) {
      onStatus('finalizing', null);
    }

    const finalizeResponse = await Axios.post(
      `/file/upload-sessions/${sessionId}/finalize`,
      null,
      { signal },
    );
    const responsePublicUrl = finalizeResponse?.data?.url;
    const publicUrl = responsePublicUrl
      ? responsePublicUrl.startsWith('http')
        ? responsePublicUrl
        : `${baseURLApi}${responsePublicUrl}`
      : `${baseURLApi}/file/download?privateUrl=${encodeURIComponent(privateUrl)}`;

    return {
      id,
      name: file.name,
      sizeInBytes: file.size,
      privateUrl,
      publicUrl,
      new: true,
    };
  }

  static async uploadToServer(file, path, filename) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('filename', filename);
    const uri = `/file/upload/${path}`;
    await Axios.post(uri, formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
      },
    });

    const privateUrl = `${path}/${filename}`;

    console.log(
      'process.env.NODE_ENV in uploadToServer function',
      process.env.NODE_ENV,
    );
    console.log('baseURLApi in uploadToServer function', baseURLApi);

    return `${baseURLApi}/file/download?privateUrl=${privateUrl}`;
  }
}
