import Upload from 'react-native-background-upload';
import { castArray } from 'lodash';
import post from '../utils/post';
import request from '../utils/request';
import { getToken } from '../utils/auth';
import config from '../utils/config';

export class Operations {
  constructor() {
    this.operations = [];
  }
  addCmd = (cmd, ...args) => {
    this.operations.push({ cmd, args });
    return this;
  };
  setName = (name) => {
    return this.addCmd('setName', name);
  };
  setRight = (right) => {
    return this.addCmd('setRight', right);
  };
  addTags = (...tags) => {
    return this.addCmd('addTags', ...tags);
  };
  removeTags = (...tags) => {
    return this.addCmd('removeTags', ...tags);
  };
  clearTags = () => {
    return this.addCmd('clearTags');
  };
  setTags = (...tags) => {
    return this.addCmd('setTags', ...tags);
  };
  addOwners = (...owners) => {
    return this.addCmd('addOwners', ...owners);
  };
  removeOwners = (...owners) => {
    return this.addCmd('removeOwners', ...owners);
  };
  clearOwners = () => {
    return this.addCmd('clearOwners');
  };
  setOwners = (...owners) => {
    return this.addCmd('setOwners', ...owners);
  };
  addOwnerGroups = (...groups) => {
    return this.addCmd('addOwnerGroups', ...groups);
  };
  removeOwnerGroups = (...groups) => {
    return this.addCmd('removeOwnerGroups', ...groups);
  };
  clearOwnerGroups = () => {
    return this.addCmd('clearOwnerGroups');
  };
  setOwnerGroups = (...groups) => {
    return this.addCmd('setOwnerGroups', ...groups);
  };
  use = (usage) => {
    return this.addCmd('use', usage);
  };
  unuse = (usage) => {
    return this.addCmd('unuse', usage);
  };
}

export const createOperations = () => {
  return new Operations();
};

export async function editResource(uri, operations) {
  return post(`${config.apiContextPath}/api/resource/user/${encodeURIComponent(uri)}/meta`, { operations: operations.operations });
}

export async function getResourceInfo(uri) {
  return request(`${config.apiContextPath}/api/resource/user/${encodeURIComponent(uri)}/meta`);
}

export async function queryResourceByUsage(usage, pst = -1, psz = -1) {
  return request(`${config.apiContextPath}/api/resource/user/query`, { usage, pst, psz });
}

export async function queryResourceByAllTags(tags, pst = -1, psz = -1) {
  return request(`${config.apiContextPath}/api/resource/user/query`, { tags: castArray(tags), pst, psz });
}

export async function queryResourceByAnyTags(tags, pst = -1, psz = -1) {
  return request(`${config.apiContextPath}/api/resource/user/query`, { tags: castArray(tags), andOr: 'or', pst, psz });
}

export async function queryResourceByUsageAndAllTags(usage, tags, pst = -1, psz = -1) {
  return request(`${config.apiContextPath}/api/resource/user/query`, { usage, tags: castArray(tags), pst, psz });
}

export async function queryResourceByUsageAndAnyTags(usage, tags, pst = -1, psz = -1) {
  return request(`${config.apiContextPath}/api/resource/user/query`, { usage, tags: castArray(tags), andOr: 'or', pst, psz });
}

/**
 * @callback UploadErrorCallback
 * @param {string} uploadId 上传id，用于标识上传任务
 * @param {Error} error 错误异常
 */

/**
 * @callback UploadCompleteCallback
 * @param {string} uploadId 上传id，用于标识上传任务
 * @param {string} uri 上传完成后，服务端返回的内部uri，这个uri可以用于后续定位上传的资源
 */

/**
 * @callback UploadProgressCallback
 * @param {string} uploadId 上传id，用于标识上传任务
 * @param {number} progress 上传进度，0-100的整数
 */

/**
 * 上传文件，支持后台上传
 * @param path 文件绝对路径，类似file:///xxx格式
 * @param {UploadErrorCallback} [error] 错误回调，上传失败会调用此回调，并传入错误异常
 * @param {UploadCompleteCallback} [complete] 成功回调，上传成功会调用此回调，并传入服务端资源内部uri
 * @param {UploadProgressCallback} [progress] 上传进度回调，可以获得进度
 * @param {string} [uri] 服务端资源内部uri，可用于覆盖已有资源
 * @return {Promise<string>} uploadId 上传id，用于引用上传任务
 */
export async function uploadResource(path, error = () => {}, complete = () => {}, progress = () => {}, uri) {
  const { exists, mimeType } = await Upload.getFileInfo(path.substring(7));
  if (exists) {
    const token = await getToken();
    let url;
    if (uri) {
      url = `${config.apiContextPath}/resource/${token}/${encodeURIComponent(uri)}`;
    } else {
      url = `${config.apiContextPath}/resource/${token}`;
    }
    const uploadId = await Upload.startUpload({
      url,
      path,
      method: 'POST',
      field: 'file',
      type: 'multipart',
      headers: {
        'content-type': mimeType,
      },
    });
    let completeHandle;
    const progressHandle = Upload.addListener('progress', uploadId, (data) => {
      progress(uploadId, data.progress);
    });
    const errorHandle = Upload.addListener('error', uploadId, (data) => {
      try {
        error(uploadId, data.error);
      } finally {
        progressHandle.remove();
        completeHandle.remove();
        errorHandle.remove();
      }
    });
    completeHandle = Upload.addListener('completed', uploadId, (event) => {
      try {
        const { responseCode, responseBody } = event;
        if (responseCode >= 200 && responseCode < 300) {
          try {
            const response = JSON.parse(responseBody);
            if (response.errorCode != null && response.errorCode === 0) {
              complete(uploadId, response.data[0]);
            } else if (response.errorCode != null) {
              const err = new Error(response.message);
              err.data = response;
              error(uploadId, err);
            } else {
              error(uploadId, new Error(`Invalid response: ${responseBody}.`));
            }
          } catch (err) {
            error(uploadId, new Error(`Invalid response: ${responseBody}.`));
          }
        } else {
          try {
            const response = JSON.parse(responseBody);
            if (response.message) {
              const err = new Error(response.message);
              err.data = response;
              error(uploadId, err);
            } else if (response.data) {
              const err = new Error(response.data);
              err.data = response;
              error(uploadId, err);
            } else {
              error(uploadId, new Error(responseBody));
            }
          } catch (err) {
            error(uploadId, new Error(responseBody));
          }
        }
      } finally {
        progressHandle.remove();
        completeHandle.remove();
        errorHandle.remove();
      }
    });
    return uploadId;
  }
}
