import Web3 from 'web3';
import Contract from 'web3-eth-contract';
import IPFS from 'ipfs';
import { contracts } from 'concordia-contracts';
import { FORUM_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import getWeb3ProviderUrl from 'concordia-shared/src/utils/web3';
import { createOrbitInstance, getPeerDatabases, openKVDBs } from './utils/orbitUtils';
import startAPI from './app';
import downloadContractArtifacts from './utils/drizzleUtils';
import getIpfsOptions from './options/ipfsOptions';

process.on('unhandledRejection', (error) => {
  // This happens when attempting to initialize without any available Swarm addresses (e.g. Rendezvous)
  if (error.code === 'ERR_NO_VALID_ADDRESSES') {
    console.error('unhandledRejection', error.message);
    process.exit(1);
  }

  // Don't swallow other errors
  console.error(error);
  throw error;
});

const getDeployedContract = async (web3) => {
  let forumContractPromise;

  if (process.env.USE_EXTERNAL_CONTRACTS_PROVIDER) {
    console.log('Downloading contracts.');
    forumContractPromise = downloadContractArtifacts()
      .then((remoteContracts) => remoteContracts
        .find((remoteContract) => remoteContract.contractName === FORUM_CONTRACT));
  } else {
    forumContractPromise = Promise.resolve(contracts.find((contract) => contract.contractName === FORUM_CONTRACT));
  }

  return forumContractPromise
    .then((forumContract) => web3.eth.net.getId()
      .then((networkId) => forumContract.networks[networkId].address)
      .then((contractAddress) => {
        Contract.setProvider(getWeb3ProviderUrl());
        const contract = new Contract(forumContract.abi, contractAddress);

        return { contract, contractAddress };
      }));
};

// Open & replicate databases of existing users
const openExistingUsersDatabases = async (contract, orbit) => contract.methods.getUserAddresses().call()
  .then((userAddresses) => getPeerDatabases(orbit, userAddresses))
  .then((peerDBs) => openKVDBs(orbit, peerDBs));

const handleWeb3LogEvent = (web3, eventJsonInterface, orbit) => (error, result) => {
  if (!error) {
    const eventObj = web3.eth.abi.decodeLog(
      eventJsonInterface.inputs,
      result.data,
      result.topics.slice(1),
    );
    const userAddress = eventObj[1];
    console.log('User signed up:', userAddress);
    getPeerDatabases(orbit, [userAddress])
      .then((peerDBs) => openKVDBs(orbit, peerDBs));
  }
};

const main = async () => {
  console.log('Initializing...');
  const web3 = new Web3(new Web3.providers.WebsocketProvider(getWeb3ProviderUrl()));

  getDeployedContract(web3)
    .then(({ contract, contractAddress }) => getIpfsOptions()
      .then((ipfsOptions) => IPFS.create(ipfsOptions))
      .then((ipfs) => createOrbitInstance(ipfs, contractAddress))
      .then((orbit) => openExistingUsersDatabases(contract, orbit)
        .then(() => {
          // Listen for new users and subscribe to their databases
          const eventJsonInterface = web3.utils._.find(
            // eslint-disable-next-line no-underscore-dangle
            contract._jsonInterface,
            (obj) => obj.name === 'UserSignedUp' && obj.type === 'event',
          );

          web3.eth.subscribe('logs', {
            address: contractAddress,
            topics: [eventJsonInterface.signature],
          }, handleWeb3LogEvent(web3, eventJsonInterface, orbit));

          startAPI(orbit);
        })));
};

main();