import {UploadStrategy, UploadStrategyConfig} from './strategy/upload-strategy';
import {UploadedFile} from '@ui/utils/files/uploaded-file';
import {Disk} from './backend-metadata';
import {S3MultipartUpload} from './strategy/s3-multipart-upload';
import {S3Upload} from './strategy/s3-upload';
import {TusUpload} from './strategy/tus-upload';
import {AxiosUpload} from './strategy/axios-upload';
import {FileUpload, FileUploadState} from './file-upload-store';
import {validateFile} from '@ui/utils/files/validate-file';
import {getBootstrapData} from '@ui/bootstrap-data/bootstrap-data-store';
import {toast} from '@ui/toast/toast';
import {ProgressTimeout} from './progress-timeout';
import {message} from '@ui/i18n/message';

export async function startUploading(
  upload: FileUpload,
  state: FileUploadState,
): Promise<UploadStrategy | null> {
  const settings = getBootstrapData().settings;
  const options = upload.options;
  const file = upload.file;

  // validate file, if validation fails, error the upload and bail
  if (options?.restrictions) {
    const errorMessage = validateFile(file, options.restrictions);
    if (errorMessage) {
      state.updateFileUpload(file.id, {
        errorMessage,
        status: 'failed',
        request: undefined,
        timer: undefined,
      });

      if (options.showToastOnRestrictionFail) {
        toast.danger(errorMessage);
      }

      state.runQueue();
      return null;
    }
  }

  // prepare config for file upload strategy
  const timer = new ProgressTimeout();
  const config: UploadStrategyConfig = {
    metadata: {
      ...options?.metadata,
      relativePath: file.relativePath,
      disk: options?.metadata?.disk || Disk.uploads,
      parentId: options?.metadata?.parentId || '',
    },
    chunkSize: settings.uploads.chunk_size,
    baseUrl: settings.base_url,
    onError: errorMessage => {
      state.updateFileUpload(file.id, {
        errorMessage,
        status: 'failed',
      });
      state.runQueue();
      timer.done();
      options?.onError?.(errorMessage, file);
      if (options.showToastOnBackendError && errorMessage) {
        toast.danger(errorMessage);
      }
    },
    onSuccess: entry => {
      state.updateFileUpload(file.id, {
        status: 'completed',
        entry,
      });
      state.runQueue();
      timer.done();
      options?.onSuccess?.(entry, file);
    },
    onProgress: ({bytesUploaded, bytesTotal}) => {
      const percentage = (bytesUploaded / bytesTotal) * 100;
      state.updateFileUpload(file.id, {
        percentage,
        bytesUploaded,
      });
      timer.progress();
      options?.onProgress?.({bytesUploaded, bytesTotal});
    },
  };

  // choose and create upload strategy, based on file size and settings
  const strategy = chooseUploadStrategy(file, config);
  const request = await strategy.create(file, config);

  // add handler for when upload times out (no progress for 30+ seconds)
  timer.timeoutHandler = () => {
    request.abort();
    state.updateFileUpload(file.id, {
      status: 'failed',
      errorMessage: message('Upload timed out'),
    });
    state.runQueue();
  };

  state.updateFileUpload(file.id, {
    status: 'inProgress',
    request,
  });
  request.start();

  return request;
}

const OneMB = 1024 * 1024;
const FourMB = 4 * OneMB;
const HundredMB = 100 * OneMB;

const chooseUploadStrategy = (
  file: UploadedFile,
  config: UploadStrategyConfig,
) => {
  const settings = getBootstrapData().settings;
  const disk = config.metadata?.disk || Disk.uploads;
  const driver =
    disk === Disk.uploads
      ? settings.uploads.uploads_driver
      : settings.uploads.public_driver;

  if (driver?.endsWith('s3') && settings.uploads.s3_direct_upload) {
    return file.size >= HundredMB ? S3MultipartUpload : S3Upload;
  } else {
    // 4MB = Axios, otherwise Tus
    return file.size >= FourMB && !settings.uploads.disable_tus
      ? TusUpload
      : AxiosUpload;
  }
};
