// 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 '@webcomponents/url';
import Constants from './constants';
import FauxtonAPI from '../../core/api';
import Helpers from '../../helpers';
import {get, post, put} from '../../core/ajax';
import base64 from 'base-64';

let newApiPromise = null;
export const supportNewApi = (forceCheck) => {
  if (!newApiPromise || forceCheck) {
    newApiPromise = new FauxtonAPI.Promise((resolve) => {
      const url = Helpers.getServerUrl('/_scheduler/jobs');
      get(url, {raw: true})
        .then(resp => {
          if (resp.status > 202) {
            return resolve(false);
          }
          resolve(true);
        });
    });
  }

  return newApiPromise;
};

export const encodeFullUrl = (fullUrl) => {
  if (!fullUrl) {return '';}
  const url = new URL(fullUrl);
  return `${url.origin}/${encodeURIComponent(url.pathname.slice(1))}`;
};

export const decodeFullUrl = (fullUrl) => {
  if (!fullUrl) {return '';}
  const url = new URL(fullUrl);
  return `${url.origin}/${decodeURIComponent(url.pathname.slice(1))}`;
};

export const getUsername = () => {
  return FauxtonAPI.session.user().name;
};

export const getAuthHeaders = (username, password) => {
  if (!username || !password) {
    return {};
  }
  return {
    'Authorization': 'Basic ' + base64.encode(username + ':' + password)
  };
};

export const getCredentialsFromUrl = (url) => {
  const index = url.lastIndexOf('@');
  if (index === -1) {
    return {
      username: '',
      password: ''
    };
  }

  const startIndex = url.startsWith("https") ? 8 : 7;
  const rawCreds = url.slice(startIndex, index);
  const colonIndex = rawCreds.indexOf(':');
  const username = rawCreds.slice(0, colonIndex);
  const password = rawCreds.slice(colonIndex + 1, rawCreds.length);

  return {
    username,
    password
  };
};

export const removeCredentialsFromUrl = (url) => {
  const index = url.lastIndexOf('@');
  if (index === -1) {
    return url;
  }

  const protocol = url.startsWith("https") ? "https://" : 'http://';
  const cleanUrl = url.slice(index + 1);
  return protocol + cleanUrl;
};

export const getSource = ({
  replicationSource,
  localSource,
  remoteSource,
  sourceAuthType,
  sourceAuth
},
{origin, pathname} = window.location) => {

  const source = {};
  if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
    const encodedLocalTarget = encodeURIComponent(localSource);

    const root = Helpers.getRootUrl({origin, pathname});
    source.url = `${root}${encodedLocalTarget}`;
  } else {
    source.url = encodeFullUrl(removeCredentialsFromUrl(remoteSource));
  }

  setCredentials(source, sourceAuthType, sourceAuth);
  return source;
};

export const getTarget = ({
  replicationTarget,
  localTarget,
  remoteTarget,
  targetAuthType,
  targetAuth
},
//this allows us to mock out window.location for our tests
{origin, pathname} = window.location) => {

  const target = {};
  if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
        replicationTarget === Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE) {
    target.url = encodeFullUrl(removeCredentialsFromUrl(remoteTarget));
  } else {
    const encodedLocalTarget = encodeURIComponent(localTarget);
    const root = Helpers.getRootUrl({origin, pathname});
    target.url = `${root}${encodedLocalTarget}`;
  }

  setCredentials(target, targetAuthType, targetAuth);
  return target;
};

const setCredentials = (target, authType, auth) => {
  if (!authType || authType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
    target.headers = {};
  } else if (authType === Constants.REPLICATION_AUTH_METHOD.BASIC) {
    target.headers = getAuthHeaders(auth.username, auth.password);
  } else {
    // Tries to set creds using one of the custom auth methods
    // The extension should provide:
    //   - 'setCredentials(target, auth)' method which sets the 'auth' credentials into 'target' which is the 'target'/'source' field of the replication doc.
    const authExtensions = FauxtonAPI.getExtensions('Replication:Auth');
    if (authExtensions) {
      authExtensions.filter(ext => ext.typeValue === authType).map(ext => {
        if (ext.setCredentials) {
          ext.setCredentials(target, auth);
        }
      });
    }
  }
};

export const createTarget = (replicationTarget) => {
  if (_.includes([
    Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
    Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE],
  replicationTarget)) {
    return true;
  }

  return false;
};

export const continuous = (replicationType) => {
  if (replicationType === Constants.REPLICATION_TYPE.CONTINUOUS) {
    return true;
  }

  return false;
};

export const addDocIdAndRev = (docId, _rev, doc) => {
  if (docId) {
    doc._id = docId;
  }

  if (_rev) {
    doc._rev = _rev;
  }

  return doc;
};

export const createReplicationDoc = ({
  replicationTarget,
  replicationSource,
  replicationType,
  replicationDocName,
  localTarget,
  localSource,
  remoteTarget,
  remoteSource,
  _rev,
  sourceAuthType,
  sourceAuth,
  targetAuthType,
  targetAuth,
  targetDatabasePartitioned
}) => {
  const username = getUsername();
  const replicationDoc = {
    user_ctx: {
      name: username,
      roles: ['_admin', '_reader', '_writer']
    },
    source: getSource({
      replicationSource,
      localSource,
      remoteSource,
      sourceAuthType,
      sourceAuth
    }),
    target: getTarget({
      replicationTarget,
      replicationSource,
      remoteTarget,
      localTarget,
      targetAuthType,
      targetAuth
    }),
    create_target: createTarget(replicationTarget),
    continuous: continuous(replicationType),
  };
  if (targetDatabasePartitioned) {
    replicationDoc.create_target_params = {
      partitioned: true
    };
  }
  return addDocIdAndRev(replicationDocName, _rev, replicationDoc);
};

export const removeSensitiveUrlInfo = (url) => {
  try {
    const urlObj = new URL(url);
    return `${urlObj.origin}/${decodeURIComponent(urlObj.pathname.slice(1))}`;
  } catch (e) {
    return url;
  }
};

export const getDocUrl = (doc) => {
  let url = doc;
  if (!doc) {
    return '';
  }

  if (typeof doc === "object") {
    url = doc.url;
  }
  return removeSensitiveUrlInfo(url);
};

export const parseReplicationDocs = (rows) => {
  return rows.map(row => row.doc).map(doc => {
    return {
      _id: doc._id,
      _rev: doc._rev,
      selected: false, //use this field for bulk delete in the ui
      source: getDocUrl(doc.source),
      target: getDocUrl(doc.target),
      createTarget: doc.create_target,
      continuous: doc.continuous === true ? true : false,
      status: doc._replication_state,
      errorMsg: doc._replication_state_reason ? doc._replication_state_reason : '',
      statusTime: new Date(doc._replication_state_time),
      startTime: new Date(doc._replication_start_time),
      url: `#/database/_replicator/${encodeURIComponent(doc._id)}`,
      raw: doc
    };
  });
};

export const convertState = (state) => {
  if (state.toLowerCase() === 'error' || state.toLowerCase() === 'crashing') {
    return 'retrying';
  }

  return state;
};

export const combineDocsAndScheduler = (docs, schedulerDocs) => {
  return docs.map(doc => {
    const schedule = schedulerDocs.find(s => s.doc_id === doc._id);
    if (!schedule) {
      return doc;
    }

    doc.status = convertState(schedule.state);
    if (schedule.start_time) {
      doc.startTime = new Date(schedule.start_time);
    }

    if (schedule.last_updated) {
      doc.stateTime = new Date(schedule.last_updated);
    }

    return doc;
  });
};

export const fetchReplicationDocs = (maxItems) => {
  return supportNewApi()
    .then(newApi => {
      // Increase limit by 1 to account for the design doc in the DB
      const url = Helpers.getServerUrl(`/_replicator/_all_docs?include_docs=true&limit=${maxItems + 1}`);
      const docsPromise = get(url)
        .then((res) => {
          if (res.error) {
            return [];
          }
          const listWithoutDDocs = res.rows.filter(row => row.id.indexOf("_design/") === -1);
          if (listWithoutDDocs.length > maxItems) {
            listWithoutDDocs.pop();
          }
          return parseReplicationDocs(listWithoutDDocs);
        });


      if (!newApi) {
        return docsPromise;
      }
      const schedulerPromise = fetchSchedulerDocs();
      return FauxtonAPI.Promise.join(docsPromise, schedulerPromise, (docs, schedulerDocs) => {
        return combineDocsAndScheduler(docs, schedulerDocs);
      })
        .catch(() => {
          return [];
        });
    });
};

export const fetchSchedulerDocs = () => {
  const url = Helpers.getServerUrl('/_scheduler/docs?include_docs=true');
  return get(url)
    .then((res) => {
      if (res.error) {
        return [];
      }

      return res.docs;
    });
};

export const checkReplicationDocID = (docId) => {
  return new Promise((resolve) => {
    const url = Helpers.getServerUrl(`/_replicator/${docId}`);
    get(url)
      .then(resp => {
        if (resp.error === "not_found") {
          resolve(false);
          return;
        }
        resolve(true);
      });
  });
};

export const parseReplicateInfo = (resp) => {
  return resp.jobs.filter(job => job.database === null).map(job => {
    return {
      _id: job.id,
      source: getDocUrl(job.source.slice(0, job.source.length - 1)),
      target: getDocUrl(job.target.slice(0, job.target.length - 1)),
      startTime: new Date(job.start_time),
      statusTime: new Date(job.last_updated),
      //making an asumption here that the first element is the latest
      status: convertState(job.history[0].type),
      errorMsg: '',
      selected: false,
      continuous: /continuous/.test(job.id),
      raw: job
    };
  });
};

export const fetchReplicateInfo = () => {
  return supportNewApi()
    .then(newApi => {
      if (!newApi) {
        return [];
      }

      const url = Helpers.getServerUrl('/_scheduler/jobs');
      return get(url)
        .then(resp => {
          return parseReplicateInfo(resp);
        });
    });
};

export const deleteReplicatesApi = (replicates) => {
  const promises = replicates.map(replicate => {
    const data = {
      replication_id: replicate._id,
      cancel: true
    };
    const url = Helpers.getServerUrl('/_replicate');
    return post(url, data);
  });

  return FauxtonAPI.Promise.all(promises);
};

export const createReplicatorDB = () => {
  const url = Helpers.getServerUrl('/_replicator');
  return put(url)
    .then(res => {
      if (!res.ok) {
        throw {reason: 'Failed to create the _replicator database.'};
      }
      return true;
    });
};
