// 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 base64 from 'base-64';
import React from 'react';
import Helpers from '../../../helpers';
import {json} from '../../../core/ajax';
import FauxtonAPI from '../../../core/api';
import {ReplicationSource} from './source';
import {ReplicationTarget} from './target';
import {ReplicationOptions} from './options';
import {ReplicationSubmit} from './submit';
import {ReplicationAuth} from './auth-options';
import Constants from '../constants';
import {ConflictModal} from './modals';
import {isEmpty} from 'lodash';

export default class NewReplicationController extends React.Component {
  constructor (props) {
    super(props);
    this.submit = this.submit.bind(this);
    this.checkAuth = this.checkAuth.bind(this);
    this.runReplicationChecks = this.runReplicationChecks.bind(this);
  }

  checkAuth () {
    this.props.hideConflictModal();
    const { replicationSource, replicationTarget,
      sourceAuthType, targetAuthType, sourceAuth, targetAuth } = this.props;

    const isLocalSource = replicationSource === Constants.REPLICATION_SOURCE.LOCAL;
    const isLocalTarget = replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE ||
      replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE;

    // Ask user to select an auth method for local source/target when one is not selected
    // and not on admin party
    if (!FauxtonAPI.session.isAdminParty()) {
      if (isLocalSource && sourceAuthType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
        FauxtonAPI.addNotification({
          msg: 'Missing credentials for local source database.',
          type: 'error',
          clear: true
        });
        return;
      }
      if (isLocalTarget && targetAuthType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
        FauxtonAPI.addNotification({
          msg: 'Missing credentials for local target database.',
          type: 'error',
          clear: true
        });
        return;
      }
    }

    this.checkLocalAccountCredentials(sourceAuthType, sourceAuth, 'source', isLocalSource).then(() => {
      this.checkLocalAccountCredentials(targetAuthType, targetAuth, 'target', isLocalTarget).then(() => {
        this.submit();
      }, () => {});
    }, () => {});
  }

  checkLocalAccountCredentials(authType, auth, label, isLocal) {
    // Skip check if it's a remote tb or not using BASIC auth
    if (authType !== Constants.REPLICATION_AUTH_METHOD.BASIC || !isLocal) {
      return FauxtonAPI.Promise.resolve(true);
    }

    if (!auth.username || !auth.password) {
      const err = `Missing ${label} credentials.`;
      FauxtonAPI.addNotification({
        msg: err,
        type: 'error',
        clear: true
      });
      return FauxtonAPI.Promise.reject(new Error(err));
    }

    return this.checkCredentials(auth.username, auth.password).then((resp) => {
      if (resp.error) {
        throw (resp);
      }
      return true;
    }).catch(err => {
      FauxtonAPI.addNotification({
        msg: `Your username or password for ${label} database is incorrect.`,
        type: 'error',
        clear: true
      });
      throw err;
    });
  }

  checkCredentials(username, password) {
    return json(Helpers.getServerUrl('/'), 'GET', {
      credentials: 'omit',
      headers: {
        'Authorization':'Basic ' + base64.encode(username + ':' + password)
      }
    });
  }

  checkReplicationDocID () {
    const {showConflictModal, replicationDocName, checkReplicationDocID} = this.props;
    checkReplicationDocID(replicationDocName).then(existingDoc => {
      if (existingDoc) {
        showConflictModal();
        return;
      }

      this.checkAuth();
    });
  }

  runReplicationChecks () {
    const {replicationDocName} = this.props;
    if (!this.checkSourceTargetDatabases()) {
      return;
    }
    if (replicationDocName) {
      this.checkReplicationDocID();
      return;
    }

    this.checkAuth();
  }

  checkSourceTargetDatabases () {
    const {
      remoteTarget,
      remoteSource,
      replicationSource,
      replicationTarget,
      localTarget,
      localSource,
      databases
    } = this.props;

    if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && _.includes(databases, localTarget)) {
      FauxtonAPI.addNotification({
        msg: 'The "' + localTarget + '" database already exists locally. Please enter another database name.',
        type: 'error',
        escape: false,
        clear: true
      });
      return false;
    }
    if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE ||
        replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE) {
      let error = '';
      if (/\s/.test(localTarget)) {
        error = 'The target database may not contain any spaces.';
      } else if (/^_/.test(localTarget)) {
        error = 'The target database may not start with an underscore.';
      }

      if (error) {
        FauxtonAPI.addNotification({
          msg: error,
          type: 'error',
          escape: false,
          clear: true
        });
        return false;
      }
    }

    //check if remote source/target URL is valid
    const isRemoteSource = replicationSource === Constants.REPLICATION_SOURCE.REMOTE;
    if (isRemoteSource && !isEmpty(remoteSource)) {
      let errorMessage = '';
      try {
        const url = new URL(remoteSource);
        if (url.pathname.slice(1) === '') {
          errorMessage = 'Invalid source database URL. Database name is missing.';
        }
      } catch (err) {
        errorMessage = 'Invalid source database URL.';
      }
      if (errorMessage) {
        FauxtonAPI.addNotification({
          msg: errorMessage,
          type: 'error',
          escape: false,
          clear: true
        });
        return false;
      }
    }
    const isRemoteTarget = replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
      replicationTarget === Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE;
    if (isRemoteTarget && !isEmpty(remoteTarget)) {
      let errorMessage = '';
      try {
        const url = new URL(remoteTarget);
        if (url.pathname.slice(1) === '') {
          errorMessage = 'Invalid target database URL. Database name is missing.';
        }
      } catch (err) {
        errorMessage = 'Invalid target database URL.';
      }
      if (errorMessage) {
        FauxtonAPI.addNotification({
          msg: errorMessage,
          type: 'error',
          escape: false,
          clear: true
        });
        return false;
      }
    }

    //check that source and target are not the same. They can trigger a false positive if they are ""
    if ((remoteTarget === remoteSource && !isEmpty(remoteTarget))
        || (localSource === localTarget && !isEmpty(localSource))) {
      FauxtonAPI.addNotification({
        msg: 'Cannot replicate a database to itself',
        type: 'error',
        escape: false,
        clear: true
      });

      return false;
    }

    return true;
  }

  submit () {
    const {
      replicationTarget,
      replicationSource,
      replicationType,
      replicationDocName,
      remoteTarget,
      remoteSource,
      localTarget,
      localSource,
      sourceAuthType,
      sourceAuth,
      targetAuthType,
      targetAuth,
      targetDatabasePartitioned,
      pageLimit
    } = this.props;

    let _rev;
    if (replicationDocName) {
      const doc = this.props.docs.find(doc => doc._id === replicationDocName);
      if (doc) {
        _rev = doc._rev;
      }
    }

    this.props.replicate({
      replicationTarget,
      replicationSource,
      replicationType,
      replicationDocName,
      localTarget,
      localSource,
      remoteTarget,
      remoteSource,
      _rev,
      sourceAuthType,
      sourceAuth,
      targetAuthType,
      targetAuth,
      targetDatabasePartitioned
    }, pageLimit);
  }

  confirmButtonEnabled () {
    const {
      remoteSource,
      localSourceKnown,
      replicationSource,
      replicationTarget,
      localTargetKnown,
      localTarget,
      submittedNoChange,
    } = this.props;

    if (submittedNoChange) {
      return false;
    }

    if (!replicationSource || !replicationTarget) {
      return false;
    }

    if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL && !localSourceKnown) {
      return false;
    }
    if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE && !localTargetKnown) {
      return false;
    }

    if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && !localTarget) {
      return false;
    }

    if (replicationSource === Constants.REPLICATION_SOURCE.REMOTE && remoteSource === "") {
      return false;
    }

    return true;
  }

  render () {
    const {
      replicationSource,
      replicationTarget,
      replicationType,
      replicationDocName,
      conflictModalVisible,
      databases,
      localSource,
      remoteSource,
      remoteTarget,
      localTarget,
      targetDatabasePartitioned,
      allowNewPartitionedLocalDbs,
      updateFormField,
      clearReplicationForm,
      sourceAuthType,
      sourceAuth,
      targetAuthType,
      targetAuth
    } = this.props;

    return (
      <div className="container px-3 py-3">
        <div className="row">
          <div className="col-12"><h3>Source</h3></div>
          <div className="col-12">
            <ReplicationSource
              replicationSource={replicationSource}
              localSource={localSource}
              databases={databases}
              remoteSource={remoteSource}
              onSourceSelect={updateFormField('replicationSource')}
              onRemoteSourceChange={updateFormField('remoteSource')}
              onLocalSourceChange={updateFormField('localSource')}
            />
          </div>
          <div className="col-12 mt-2">
            <ReplicationAuth
              credentials={sourceAuth}
              authType={sourceAuthType}
              onChangeAuthType={updateFormField('sourceAuthType')}
              onChangeAuth={updateFormField('sourceAuth')}
              authId={'replication-source-auth'}
            />
          </div>
        </div>

        <hr className="replication__seperator my-3" size="1"/>

        <div className="row">
          <div className="col-12"><h3>Target</h3></div>
          <div className="col-12">
            <ReplicationTarget
              replicationTarget={replicationTarget}
              onTargetChange={updateFormField('replicationTarget')}
              databases={databases}
              localTarget={localTarget}
              remoteTarget={remoteTarget}
              allowNewPartitionedLocalDbs={allowNewPartitionedLocalDbs}
              targetDatabasePartitioned={targetDatabasePartitioned}
              onRemoteTargetChange={updateFormField('remoteTarget')}
              onLocalTargetChange={updateFormField('localTarget')}
              onTargetDatabasePartitionedChange={updateFormField('targetDatabasePartitioned')}
            />
          </div>
          <div className="col-12 mt-2">
            <ReplicationAuth
              credentials={targetAuth}
              authType={targetAuthType}
              onChangeAuthType={updateFormField('targetAuthType')}
              onChangeAuth={updateFormField('targetAuth')}
              authId={'replication-target-auth'}
            />
          </div>
        </div>
        <hr className="replication__seperator my-3" size="1"/>

        <div className="row">
          <div className="col-12"><h3>Options</h3></div>
          <ReplicationOptions
            replicationType={replicationType}
            replicationDocName={replicationDocName}
            onDocChange={updateFormField('replicationDocName')}
            onTypeChange={updateFormField('replicationType')}
          />
        </div>

        <ReplicationSubmit
          disabled={!this.confirmButtonEnabled()}
          onClick={this.runReplicationChecks}
          onClear={clearReplicationForm}
        />
        <ConflictModal
          visible={conflictModalVisible}
          onClick={this.checkAuth}
          onClose={this.props.hideConflictModal}
          docId={replicationDocName}
        />
      </div>
    );
  }
}
