const { ethers } = require('hardhat');
const { getContractAddressByNetworkName } = require('./contract-config');
const readFromApi = require('./util/read-from-api');

const network = process.env.HARDHAT_NETWORK;

let dao;
let work1;
let onboarding;
let account;
let validationPools;
let reputation;
let posts;
let proposalsContract;
let proposals;

const fetchReputation = async () => {
  reputation = await dao.balanceOf(account);
  console.log(`reputation: ${reputation}`);
};

const fetchPost = async (postIndex) => {
  const {
    id, sender, author, postId,
  } = await dao.posts(postIndex);
  const { content, embeddedData } = await readFromApi(postId);
  const post = {
    id,
    sender,
    author,
    postId,
    content,
    embeddedData,
  };
  posts[postIndex] = post;
  return post;
};

const fetchValidationPool = async (poolIndex) => {
  const {
    id, postIndex, sender, stakeCount, fee, duration, endTime, resolved, outcome,
  } = await dao.validationPools(poolIndex);
  const pool = {
    id, postIndex, sender, stakeCount, fee, duration, endTime, resolved, outcome,
  };
  pool.post = await fetchPost(pool.postIndex);
  validationPools[poolIndex] = pool;
  return pool;
};

const fetchValidationPools = async () => {
  const count = await dao.validationPoolCount();
  console.log(`validation pool count: ${count}`);
  const promises = [];
  validationPools = [];
  for (let i = 0; i < count; i += 1) {
    promises.push(fetchValidationPool(i));
  }
  await Promise.all(promises);
};

const initialize = async () => {
  const getContract = (name) => ethers.getContractAt(
    name,
    getContractAddressByNetworkName(network, name),
  );
  dao = await getContract('DAO');
  work1 = await getContract('Work1');
  onboarding = await getContract('Onboarding');
  proposalsContract = await getContract('Proposals');
  [account] = await ethers.getSigners();
  const address = await account.getAddress();
  console.log(`account: ${address}`);
  posts = [];
  await fetchReputation();
  await fetchValidationPools();
};

const poolIsActive = (pool) => {
  if (new Date() >= new Date(Number(pool.props.endTime) * 1000)) return false;
  if (pool.props.resolved) return false;
  return true;
};

const poolIsValidWorkContract = (pool) => {
  switch (pool.sender) {
    case getContractAddressByNetworkName(network, 'Work1'): {
      // If this is a valid work evidence
      // TODO: Can we decode from the post, a reference to the work request?
      // The work request does have its own postId, the work contract has that
      // under availabilityStakes
      const expectedContent = 'This is a work evidence post';
      return pool.post.content.startsWith(expectedContent);
    }
    case getContractAddressByNetworkName(network, 'Onboarding'): {
      const expectedContent = 'This is an onboarding work evidence post';
      return pool.post.content.startsWith(expectedContent);
    }
    default:
      return false;
  }
};

const poolIsProposal = (pool) => pool.sender === getContractAddressByNetworkName(network, 'Proposals');

const getPoolStatus = (pool) => {
  if (poolIsActive(pool)) return 'Active';
  if (!pool.props.resolved) return 'Ready to Evaluate';
  if (pool.props.outcome) return 'Accepted';
  return 'Rejected';
};

const stake = async (pool, amount, inFavor) => {
  console.log(`staking ${amount} ${inFavor ? 'in favor of' : 'against'} pool ${pool.id.toString()}`);
  await dao.stakeOnValidationPool(pool.id, amount, true);
  await fetchReputation();
};

const conditionalStake = async (pool, amountToStake) => {
  if (poolIsValidWorkContract(pool)) {
    await stake(pool, amountToStake, true);
  } else if (poolIsProposal(pool)) {
    // We leave these alone at the moment.
    // We could consider automatic followup staking,
    // as a convenience if you decide early to favor a proposal
  } else {
    await stake(pool, amountToStake, false);
  }
};

const conditionalStakeEach = async (pools, amountPerPool) => {
  const promises = [];
  pools.forEach(async (pool) => {
    promises.push(conditionalStake(pool, amountPerPool));
  });
  await Promise.all(promises);
};

const printPool = (pool) => {
  const dataStr = pool.post?.embeddedData ? `data: ${JSON.stringify(pool.post.embeddedData)},\n  ` : '';
  console.log(`pool ${pool.id.toString()}\n  `
    + `status: ${getPoolStatus(pool)},\n  `
    + `is valid work contract: ${poolIsValidWorkContract(pool)},\n  `
    + `is proposal: ${poolIsProposal(pool)},\n  `
    + `${dataStr}post content: ${pool.post?.content}`);
};

async function main() {
  await initialize();

  validationPools.forEach(printPool);

  // Stake half of available reputation on any active pools
  const activePools = validationPools.filter(poolIsActive);
  if (activePools.length && reputation > 0) {
    const amountPerPool = reputation / BigInt(2) / BigInt(activePools.length);
    await conditionalStakeEach(activePools, amountPerPool);
  }

  // Listen for new validation pools
  dao.on('ValidationPoolInitiated', async (poolIndex) => {
    const pool = await fetchValidationPool(poolIndex);
    printPool(pool);
    await fetchReputation();
    if (!reputation) return;
    const amountToStake = reputation / BigInt(2);
    await conditionalStake(pool, amountToStake);
  });

  dao.on('ValidationPoolResolved', async (poolIndex, votePasses) => {
    console.log(`pool ${poolIndex} resolved, status: ${votePasses ? 'accepted' : 'rejected'}`);
    fetchValidationPool(poolIndex);
    fetchReputation();
  });

  work1.on('AvailabilityStaked', async () => {
    fetchReputation();
  });

  onboarding.on('AvailabilityStaked', async () => {
    fetchReputation();
  });
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});