import { useEffect, useRef, useState } from 'react';
import { useSDK } from '@metamask/sdk-react';
import { Web3 } from 'web3';
import Button from 'react-bootstrap/Button';

import DAOArtifact from './assets/DAO.json';
// import work1Artifact from './assets/Work1.json';

const contracts = {
  '0x539': { // Hardhat
    DAO: '0x8d914D38dD301FC4606f5aa9fEcF8A76389020d3',
    Work1: '0x050C420Cc4995B41217Eba1B54B82Fd5687e9139',
  },
  '0xaa36a7': { // Sepolia
    DAO: '0x8F00038542C87A5eAf18d5938B7723bF2A04A4e4',
    Work1: '0x42b79f8d8408c36aD4347ab72f826684440a7a8F',
  },
};

function App() {
  const {
    sdk, connected, provider, chainId, account, balance,
  } = useSDK();

  const [DAO, setDAO] = useState();
  // const [work1, setWork1] = useState();
  // const [work1Price, setWork1Price] = useState();
  const [balanceEther, setBalanceEther] = useState();
  //  const [reputation, setReputation] = useState();
  const reputation = useRef();
  const [totalReputation, setTotalReputation] = useState();
  const [posts, setPosts] = useState([]);
  const [validationPools, setValidationPools] = useState([]);
  const stakedPools = useRef([]);

  // const watchReputationToken = useCallback(async () => {
  //   await provider.request({
  //     method: 'wallet_watchAsset',
  //     params: {
  //       type: 'ERC20',
  //       options: {
  //         address: DAOAddress,
  //       },
  //     },
  //   });
  // }, [provider]);

  const getStatus = (pool) => {
    if (pool.resolved) {
      return pool.outcome ? 'Accepted' : 'Rejected';
    }
    const endDate = new Date(Number(pool.endTime) * 1000);
    return new Date() < endDate ? 'In Progress' : 'Ready to Evaluate';
  };

  useEffect(() => {
    if (!provider || !chainId || !account) return;
    if (!contracts[chainId]) return;
    const web3 = new Web3(provider);
    const DAOContract = new web3.eth.Contract(DAOArtifact.abi, contracts[chainId].DAO);
    // const work1Contract = new web3.eth.Contract(work1Artifact.abi, contracts[chainId].Work1);

    // const fetchPrice = async () => {
    // };

    const fetchReputation = async () => {
      reputation.current = Number(await DAOContract.methods.balanceOf(account).call());
      setTotalReputation(await DAOContract.methods.totalSupply().call());
    };

    const fetchPosts = async () => {
      const count = await DAOContract.methods.postCount().call();
      const promises = [];
      for (let i = 0; i < count; i += 1) {
        promises.push(DAOContract.methods.posts(i).call());
      }
      const fetchedPosts = await Promise.all(promises);
      setPosts(fetchedPosts);
    };

    const stake = async (poolIndex, amount, inFavor) => {
      console.log(`Attempting to stake ${amount} ${inFavor ? 'for' : 'against'} pool ${poolIndex}`);
      await DAOContract.methods.stake(poolIndex, amount, inFavor).send({
        from: account,
        gas: 1000000,
      });

      // Since this is the result we expect from the server, we preemptively set it here.
      // We can let this value be negative -- this would just mean we'll be getting
      // at least one error from the server, and a corrected reputation.
      reputation.current = Number(reputation.current) - Number(stake);
    };

    const stakeAllInFavor = (poolIndex) => stake(poolIndex, reputation.current, true);

    const fetchValidationPools = async () => {
      // TODO: Pagination
      // TODO: Memoization
      // TODO: Caching
      const count = await DAOContract.methods.validationPoolCount().call();
      const promises = [];
      for (let i = 0; i < count; i += 1) {
        promises.push(DAOContract.methods.validationPools(i).call());
      }
      const pools = (await Promise.all(promises)).map((p) => {
        const pool = p;
        pool.status = getStatus(pool);
        const timeRemaining = new Date(Number(pool.endTime) * 1000) - new Date();
        if (timeRemaining > 0 && !pool.resolved && reputation.current
          && !stakedPools.current.includes(pool.id)) {
        // Naievely stake all reputation that this validation pool is valid.
        // This is the greediest possible strategy.
        // Staking reputation transfers it, thus it's important we update our reputation
        // locally before hearing back from the server -- since blockchains are slow.
        // Note that this means refresing the page will re-send any pending staking operations.
          stakeAllInFavor(pool.id);
          stakedPools.current = stakedPools.current.concat(pool.id);
        }

        // TODO: When remaing time expires, we want to update the status for this pool
        // if (timeRemaining > 0) {
        //   setTimeout(() => {
        //     pool.status = getStatus(pool);
        //     setValidationPools((currentPools) => {
        //       const newPools = currentPools;
        //       newPools[pool.id] = pool;
        //       return newPools;
        //     });
        //     console.log(`attepted to update pool status: ${pool.status}`);
        //   }, timeRemaining);
        // }

        return pool;
      });
      setValidationPools(pools);
    };

    // fetchPrice();
    fetchReputation();
    fetchPosts();
    fetchValidationPools();
    // setWork1(work1Contract);
    setDAO(DAOContract);

    DAOContract.events.PostAdded({ fromBlock: 'latest' }).on('data', (event) => {
      console.log('event: post added', event);
      fetchPosts();
    });

    DAOContract.events.ValidationPoolInitiated({ fromBlock: 'latest' }).on('data', (event) => {
      console.log('event: validation pool initiated', event);
      fetchValidationPools();
    });

    DAOContract.events.ValidationPoolResolved({ fromBlock: 'latest' }).on('data', (event) => {
      console.log('event: validation pool resolved', event);
      fetchReputation();
      fetchValidationPools();
    });
  }, [provider, account, chainId, reputation]);

  useEffect(() => {
    if (!provider || balance === undefined) return;
    const web3 = new Web3(provider);
    setBalanceEther(web3.utils.fromWei(balance, 'ether'));
  }, [provider, balance]);

  const connect = async () => {
    try {
      await sdk?.connect();
    } catch (err) {
      console.warn('failed to connect..', err);
    }
  };

  const disconnect = async () => {
    try {
      sdk?.terminate();
    } catch (err) {
      console.warn('failed to disconnect..', err);
    }
  };

  const addPost = async () => {
    await DAO.methods.addPost(account).send({
      from: account,
      gas: 1000000,
    });
  };

  const initiateValidationPool = async (postIndex, poolDuration) => {
    await DAO.methods.initiateValidationPool(postIndex, poolDuration ?? 3600).send({
      from: account,
      gas: 1000000,
      value: 100,
    });
  };

  const evaluateOutcome = async (poolIndex) => {
    await DAO.methods.evaluateOutcome(poolIndex).send({
      from: account,
      gas: 1000000,
    });
  };

  // const stakeAvailability = async () => { }

  // const requestWork = async () => {
  //   work1.events.WorkAssigned(() => {
  //     console.log('event callback');
  //   });
  //   await work1.methods.requestWork().send({
  //     from: account,
  //     gas: 1000000,
  //   });
  // };

  return (
    <>
      {!connected && <Button onClick={() => connect()}>Connect</Button>}

      {connected && (
        <>
          <div>
            {!contracts[chainId] && (
            <div>
              Please switch MetaMask to Sepolia testnet!
            </div>
            )}
            <div>
              {chainId && `Chain ID: ${chainId}`}
            </div>
            <div>
              {`Account: ${account}`}
            </div>
            <div>
              {`Balance: ${balanceEther} ETH`}
            </div>
            <div>
              {`Your REP: ${reputation.current}`}
            </div>
            <div>
              {`Total REP: ${totalReputation}`}
            </div>
            <Button onClick={() => disconnect()}>Disconnect</Button>
          </div>
          <div>
            {`Posts count: ${posts.length}`}
          </div>
          <div>
            <table className="table">
              <thead>
                <tr>
                  <th>ID</th>
                  <th>Author</th>
                  <th>Actions</th>
                </tr>
              </thead>
              <tbody>
                {posts.map((post) => (
                  <tr key={post.id}>
                    <td>{post.id.toString()}</td>
                    <td>{post.author}</td>
                    <td>
                      Initiate Validation Pool
                      {' '}
                      <Button onClick={() => initiateValidationPool(post.id, 60)}>
                        1 Min.
                      </Button>
                      {' '}
                      <Button onClick={() => initiateValidationPool(post.id, 3600)}>
                        1 Hr.
                      </Button>
                      {' '}
                      <Button onClick={() => initiateValidationPool(post.id, 86400)}>
                        1 Day
                      </Button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <div>
            <Button onClick={() => addPost()}>Add Post</Button>
          </div>
          <div>
            {`Validation Pool Count: ${validationPools.length}`}
          </div>
          <div>
            <table className="table">
              <thead>
                <tr>
                  <th>ID</th>
                  <th>Post ID</th>
                  <th>Fee</th>
                  <th>Duration</th>
                  <th>End Time</th>
                  <th>
                    Stake
                    <br />
                    Count
                  </th>
                  <th>Status</th>
                  <th>Actions</th>
                </tr>
              </thead>
              <tbody>
                {validationPools.map((pool) => (
                  <tr key={pool.id}>
                    <td>{pool.id.toString()}</td>
                    <td>{pool.postIndex.toString()}</td>
                    <td>{pool.fee.toString()}</td>
                    <td>{pool.duration.toString()}</td>
                    <td>{new Date(Number(pool.endTime) * 1000).toLocaleString()}</td>
                    <td>{pool.stakeCount.toString()}</td>
                    <td>{pool.status}</td>
                    <td>
                      {!pool.resolved && (
                        <Button onClick={() => evaluateOutcome(pool.id)}>
                          Evaluate Outcome
                        </Button>
                      )}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <div>
            { /* `Work1 Price: ${work1Price} ETH` */ }
          </div>
          <div>
            { /* <Button onClick={() => requestWork()}>Request Work</Button> */ }
          </div>
        </>
      )}
    </>
  );
}

export default App;
