/*
 * Copyright (c) 2018 by Filestack.
 * Some rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { Session } from '../client';
import { Hosts } from './../../config';
import { ExtensionsMap } from './extensions';
import { fileTypeFromBuffer } from 'file-type';
import isutf8 from 'isutf8';

/**
 * Resolve cdn url based on handle type
 *
 * @private
 * @param session session object
 * @param handle file handle (hash, src://alias, url)
 */
export const resolveCdnUrl = (session: Session, handle: string): string => {
  const cdnURL = session.urls.cdnUrl;

  if (handle && (handle.indexOf('src:') === 0 || handle.indexOf('http') === 0)) {
    if (!session.apikey) {
      throw new Error('Api key is required when storage alias is provided');
    }

    // apikey is required for alias or external sources call
    return `${cdnURL}/${session.apikey}`;
  }

  return cdnURL;
};

/**
 * Resolve all urls with provided cnames
 *
 * @private
 * @param urls
 * @param cname
 */
export const resolveHost = (urls: Hosts, cname: string): Hosts => {
  if (!cname) {
    return urls;
  }

  const hosts = /filestackapi.com|filestackcontent.com/i;

  Object.keys(urls).forEach(key => {
    urls[key] = urls[key].replace(hosts, cname);
  });

  return urls;
};

/**
 * Removes empty options from object
 *
 * @private
 * @param obj
 */
export const removeEmpty = (obj: any) => {
  const newObj = { ...obj };
  Object.keys(newObj).forEach(k => !newObj[k] && typeof newObj[k] !== 'boolean' && delete newObj[k]);
  return newObj;
};

/**
 * Returns unique time
 */
let last;
export const uniqueTime = () => {
  const time = Date.now();
  last = time === last ? time + 1 : time;
  return last;
};

/**
 * Generates random string with provided length
 *
 * @param len
 */
export const uniqueId = (len: number = 10): string => {
  return new Array(len).join().replace(/(.|$)/g, () => ((Math.random() * 36) | 0).toString(36)[Math.random() < 0.5 ? 'toString' : 'toUpperCase']());
};

/**
 * Check if input is a svg
 *
 * @param {Uint8Array | Buffer} file
 * @returns {string} - mimetype
 */
export const getMimetype = async (file: Uint8Array | Buffer, name?: string): Promise<string> => {
  let type;

  try {
    type = await fileTypeFromBuffer(file as Uint8Array);
  } catch (e) {
    console.warn('An exception occurred while processing the buffer:', e.message);
  }

  const excludedMimetypes = ['text/plain', 'application/octet-stream', 'application/x-ms', 'application/x-msi', 'application/zip', 'audio/x-m4a'];

  if (type && excludedMimetypes.indexOf(type.mime) === -1) {
    return type.mime;
  }

  if (name && name.indexOf('.') > -1) {
    const mime = extensionToMime(name);

    if (mime) {
      return mime;
    }
  }

  try {
    if (isutf8(file)) {
      return 'text/plain';
    }
  } catch (e) {
    /* istanbul ignore next */
    console.warn('Additional mimetype checks (text/plain) are currently not supported for browsers');
  }
  // this is only fallback, omit it in coverage
  /* istanbul ignore next */

  // if we cant find types by extensions and we have magic bytes fallback to it
  if (type) {
    return type.mime;
  }

  return 'application/octet-stream';
};

/**
 * Change extension to according mimetype using ext=>mimetype map
 *
 * @param ext - string
 * @return string|boolean
 */
export const extensionToMime = (ext: string) => {
  if (!ext || ext.length === 0) {
    return;
  }

  if (ext.split('/').length === 2) {
    return ext;
  }

  if (ext.indexOf('.') > -1) {
    ext = ext.split('.').pop();
  }

  ext = ext.toLocaleLowerCase();

  const keys = Object.keys(ExtensionsMap);
  const mapLen = keys.length;

  for (let i = 0; i < mapLen; i++) {
    if (ExtensionsMap[keys[i]].indexOf(ext) > -1) {
      return keys[i];
    }
  }

  return;
};

/**
 * Sanitizer Options
 */
export type SanitizeOptions =
  | boolean
  | {
      exclude?: string[];
      replacement?: string;
    };

/**
 * Sanitize file name
 *
 * @param name
 * @param {bool} options  - enable,disable sanitizer, default enabled
 * @param {string} options.replacement - replacement for sanitized chars defaults to "-"
 * @param {string[]} options.exclude - array with excluded chars default - `['\', '{', '}','|', '%', '`', '"', "'", '~', '[', ']', '#', '|', '^', '<', '>']`
 */
export const sanitizeName = (name: string, options: SanitizeOptions = true): string => {
  if (typeof options === 'boolean' && !options) {
    return name;
  }

  let ext;

  const replacement = typeof options !== 'boolean' && options.replacement ? options.replacement : '-';
  const exclude = typeof options !== 'boolean' && options.exclude ? options.exclude : ['\\', '{', '}', '|', '%', '`', '"', "'", '~', '[', ']', '#', '|', '^', '<', '>'];

  if (!name || name.length === 0) {
    return 'undefined';
  }

  const fileParts = name.split('.');

  if (fileParts.length > 1) {
    ext = fileParts.pop();
  }

  return `${fileParts
    .join('.')
    .split('')
    .map(char => (exclude.indexOf(char) > -1 ? replacement : char))
    .join('')}${ext ? '.' + ext : ''}`;
};

/**
 * Filter object to given fields
 *
 * @param toFilter
 * @param requiredFields
 */
export const filterObject = (toFilter, requiredFields: string[]) => {
  if (!requiredFields || requiredFields.length === 0) {
    return toFilter;
  }

  if (Object.keys(toFilter).length === 0) {
    return toFilter;
  }

  return Object.keys(toFilter)
    .filter(f => requiredFields.indexOf(f) > -1)
    .reduce((obj, key) => ({ ...obj, [key]: toFilter[key] }), {});
};

/**
 * Deep cleanup object from functions
 *
 * @param obj
 */
export const cleanUpCallbacks = (obj: any) => {
  if (!obj || Object.keys(obj).length === 0) {
    return obj;
  }

  Object.keys(obj).forEach(k => {
    if (typeof obj[k] === 'function') {
      obj[k] = undefined;
    }

    if (obj[k] === Object(obj[k])) {
      obj[k] = cleanUpCallbacks(obj[k]);
    }
  });

  return obj;
};

export * from './index.node';
