/*
 * last modified---
 * 	10-17-24 break out providing the token source approve() into a separate form
 * 	10-10-24 new
 *
 * purpose---
 * 	provide UI to establish Timelocks within the TimelockManager contract and
 * 	load previously minted tokens into them
 */

import React, { useState } from 'react';
import useEth from '../EthContext/useEth';
import Form from 'react-bootstrap/Form';
import LoadingButton from '../LoadingButton.jsx';
const BigNumber = require("bignumber.js");


// tell the user if they're not entitled to do timelock admin
function NoticeNotAdmin() {
	return (
		<>
			<br/><br/>
			<h3>Enter new $ENSHROUD Timelock specification:</h3>
			<br/>
			<p>
				⚠️ Your selected address is not listed as the admin in
				the <span className="code">TimelockManager</span> contract.
				Timelock admin disabled.
			</p>
			<br/>
		</>
	);
}

/* method to supply an entry form for an entirely new Timelock for a user
 * @param props.isManagerAuth true if user has admin permission in TimelockMgr
 */
function NewEnshTimelock(props) {
	// enable use of our contracts and wallet accounts
	const { state: { accounts, contracts, web3, chainConn } } = useEth();
	const enshTokenContract = contracts["EnshroudToken"];
	const timelockContract = contracts["TimelockManager"];
	const timelockAddr = timelockContract.options.address;
	const adminAcct = web3.utils.toChecksumAddress(accounts[0]);

	// current block
	const [currBlock, setCurrBlock] = useState(0);

	// get the current block
	const getCurrBlock = async () => {
		var blockNum = 0;
		await web3.eth.getBlock("latest")
			.then(block => { blockNum = block.number })
		setCurrBlock(blockNum);
	};
	getCurrBlock();

	/* Calculate the default block start and stop points.  We must have a 90d
	 * cliff (thus, startBlock at least 90d in future), and a 1y linear unlock.
	 * By default we'll use these intervals following deployment (genesis).
	 * The entry form below will however permit overrides, in case we start
	 * some later in the game than that.  (Note all starts must be in future.)
	 *
	 * On the Ganache test chain, which doesn't generate new blocks absent any
	 * transactions occurring, the calendar is irrelevant.  We will therefore
	 * provide an arbitrary override where the start block must be at least
	 * current + 10 blocks, and the release end must be 100 blocks after that.
	 * (In this case the tester should probably override the form defaults.)
	 */
	const BLOCKS_PER_DAY = 7150;
	const BLOCKS_PER_YEAR = 2609750;
	var DefaultStartBlock
		= Math.max(chainConn.chainConfig.protocolGenesis
					+ (90 * BLOCKS_PER_DAY), currBlock + 1);
	var DefaultEndBlock = DefaultStartBlock + BLOCKS_PER_YEAR;

	// special rule for local test chain
	if (chainConn.chainConfig.chainId === 1337
		|| chainConn.chainConfig.chain === "Ganache")
	{
		// override to reduce the default number of blocks to equal "1 year"
		DefaultStartBlock = currBlock + 10;
		DefaultEndBlock = DefaultStartBlock + 100;
	}

	// track state of form fields
	const [timelockForm, setTimelockForm] = useState({
		source: '',
		recipient: '',
		amount: 150000,
		startBlock: '',
		endBlock: '',
	});

	// process a timelock amount value change
	const handleTimelockAmtChange = e => {
		var inpVal = e.target.value.toString();
		if (inpVal === '') inpVal = "0.0";
		if (isNaN(inpVal)) inpVal = "0.0";
		var amt = new BigNumber(inpVal);
		setTimelockForm({...timelockForm, amount: amt.toString()});
	};

	// process a timelock recipient address value change
	const handleTimelockAddressChange = e => {
		var addr = e.target.value.toString();
		if (addr === '' || /^0x[0-9a-fA-F]+/.test(addr)) {
			setTimelockForm({...timelockForm, recipient: addr});
		}
	};

	// process a timelock source address value change
	const handleSourceAddressChange = e => {
		var addr = e.target.value.toString();
		if (addr === '' || /^0x[0-9a-fA-F]+/.test(addr)) {
			setTimelockForm({...timelockForm, source: addr});
		}
	};

	// process a start block value change
	const handleStartBlockChange = e => {
		var block = e.target.value.toString();
		if (block === '') block = DefaultStartBlock;
		if (isNaN(block)) block = DefaultStartBlock;
		setTimelockForm({...timelockForm, startBlock: block});
	};

	// process an end block value change
	const handleEndBlockChange = e => {
		var block = e.target.value.toString();
		if (block === '') block = DefaultEndBlock;
		if (isNaN(block)) block = DefaultEndBlock;
		setTimelockForm({...timelockForm, endBlock: block});
	};

	/* Submit a timelock establishment to the blockchain.  This requires two
	 * independent steps:
	 * 1) Perform an erc20.approve() from the timelockForm.source address to
	 *	  the TimelockManager contract.  This is done via <DoSourceApproval />.
	 * 2) Call TimelockManager.transferAndLock() from the admin account (here).
	 */
	const establishTimelock = async (resolve, reject) => {
		// sanity check form inputs
		if (timelockForm.amount <= 0) {
			let badAmt = new Error("Illegal timelock amount, \""
									+ timelockForm.amount + "\"");
			alert(badAmt.message);
			reject(badAmt);
			return false;
		}
		if (!web3.utils.isAddress(timelockForm.source)) {
			let badSrc = new Error("Illegal timelock source address, \""
									+ timelockForm.source + "\"");
			alert(badSrc.message);
			reject(badSrc);
			return false;
		}
		if (!web3.utils.isAddress(timelockForm.recipient)) {
			let badAddr = new Error("Illegal timelock recipient address, \""
									+ timelockForm.recipient + "\"");
			alert(badAddr.message);
			reject(badAddr);
			return false;
		}

		// provide a default start/end if none was entered
		if (timelockForm.startBlock === '') {
			timelockForm.startBlock = DefaultStartBlock;
		}
		if (timelockForm.endBlock === '') {
			timelockForm.endBlock = DefaultEndBlock;
		}

		// check block boundaries
		if (timelockForm.startBlock < (currBlock+2)) {
			let badStart = new Error("Starting block must be after the next "
									+ "pending block");
			alert(badStart.message);
			reject(badStart);
			return false;
		}
		if (timelockForm.endBlock <= timelockForm.startBlock) {
			let badEnd = new Error("Ending block must be after starting block");
			alert(badEnd.message);
			reject(badEnd);
			return false;
		}

		// scale amount up by 1e18 to wei
		const amt = web3.utils.toWei(`${timelockForm.amount}`);

		// build new record from form
		const timelockSetup = {
			admin: adminAcct,
			source: timelockForm.source,
			recipient: timelockForm.recipient,
			amount: amt,
			releaseStart: timelockForm.startBlock,
			releaseEnd: timelockForm.endBlock,
		};

		// determine whether recipient already has a timelock
		const fetchTimelock = async () => {
			let tLock = await timelockContract.methods.timelocks(
												timelockSetup.recipient).call(
												{ from: adminAcct });
			return tLock.remainingAmount;
		};
		var tLockAmt = await fetchTimelock();
		if (tLockAmt > 0) {
			let existing = new Error("Recipient " + timelockSetup.recipient
									+ " already has an existing Timelock");
			alert(existing.message);
			reject(existing);
			return false;
		}

		// determine approval amount for TimelockManager for the token source
		var apBalance = 0;
		const checkApprovalBalance = async () => {
			let apBal = await enshTokenContract.methods.allowance(
											timelockSetup.source, timelockAddr)
				.call({ from: timelockSetup.source} );
			apBalance = apBal;
		};
		await checkApprovalBalance();

		// make sure it's sufficient
		var approvedAmt = new BigNumber(apBalance);
		if (approvedAmt.lt(timelockSetup.amount)) {
			let noApprove = new Error("Token source is not approved for "
									+ "TimelockManager to transfer "
									+ web3.utils.fromWei(timelockSetup.amount)
									+ " $ENSHROUD tokens (only "
									+ web3.utils.fromWei(approvedAmt.toFixed())
									+ ")");
			alert(noApprove.message);
			reject(noApprove);
			return false;
		}

		// invoke transferAndLock method (must be admin)
		await timelockContract.methods.transferAndLock(
												timelockSetup.source,
												timelockSetup.recipient,
												timelockSetup.amount,
												timelockSetup.releaseStart,
												timelockSetup.releaseEnd)
				.send({ from: adminAcct })
			.then(tx => {
				alert("Timelock successfully established for recipient "
					+ timelockSetup.recipient + ", "
					+ web3.utils.fromWei(timelockSetup.amount) + " tokens");
				getCurrBlock();
			})
			.catch(err => {
				console.error("TimelockManager.transferAndLock() failed: "
							+ err.message);
				alert("Establishing timelock failed, reason: " + err.message);
				reject(err);
				return false;
			});

		resolve(true);
	};

	// form to enter new timelock record
	const timelockFormUI =
		<>
			<br/><br/>
			<h3>Enter new $ENSHROUD Timelock specification:</h3>
			<br/>
			<Form>
				<Form.Group className="mb-3" controlId="recipient">
					<Form.Label>Recipient address</Form.Label>
					<Form.Control type="text" placeholder="0x"
						value={timelockForm.recipient}
						onChange={handleTimelockAddressChange}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="source">
					<Form.Label>Token source address:</Form.Label>
					<Form.Control type="text" placeholder="0x"
						value={timelockForm.source}
						onChange={handleSourceAddressChange}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="amount">
					<Form.Label>Number of tokens (1e-18 scale):</Form.Label>
					<Form.Control type="text" placeholder="150000"
						value={timelockForm.amount}
						onChange={handleTimelockAmtChange}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="relStart">
					<Form.Label>Current Block</Form.Label>
					<Form.Control type="text" readOnly value={currBlock} />
				</Form.Group>
				<Form.Group className="mb-3" controlId="relStart">
					<Form.Label>
						Release Start Block (min. latest block + 2):
					</Form.Label>
					<Form.Control type="text" placeholder={DefaultStartBlock}
						value={timelockForm.startBlock}
						title={"minimum " + (currBlock+2)}
						onChange={handleStartBlockChange}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="relEnd">
					<Form.Label>Release End Block:</Form.Label>
					<Form.Control type="text" placeholder={DefaultEndBlock}
						value={timelockForm.endBlock}
						onChange={handleEndBlockChange}
					/>
				</Form.Group>
				<LoadingButton variant="success"
					buttonStyle="m-3"
					buttonText="Create Timelock"
					buttonTitle="Transfer the quantity of tokens from the source into a new Timelock for the recipient"
					buttonIcon="images/bag-plus-fill.svg"
					netMethod={(resolve, reject) => establishTimelock(resolve, reject)}
				/>
			</Form>
		</>;

	// render new timelock form
	return (
		<div id="newTimelock">
		{
			!props.isManagerAuth ? <NoticeNotAdmin /> : timelockFormUI
		}
		</div>
	);
}

/* provide UI for a source of tokens to be transferred into a Timelock to make
 * an erc20.approve() call, in advance of the admin creating the Timelock
 * @param props.isManagerAuth true if user has admin permission in TimelockMgr
 */
function DoSourceApproval(props) {
	// enable use of our contracts and wallet accounts
	const { state: { accounts, contracts, web3 } } = useEth();
	const timelockMgrContract = contracts["TimelockManager"];
	const enshTokenContract = contracts["EnshroudToken"];
	const enshTokenAddr = enshTokenContract.options.address;
	const timelockAddr = timelockMgrContract.options.address;

	// address making the approval (must be the selected one; must have gas)
	const sourceAddress = web3.utils.toChecksumAddress(accounts[0]);

	// the amount being approved (must match the Timelock's intended amount)
	const [approvalAmt, setApprovalAmt] = useState(new BigNumber(0));

	// the current approval amount
	const [currentAmt, setCurrentAmt] = useState(0);

	// process an approval amount value change
	const handleApprovalAmtChange = e => {
		var inpVal = e.target.value.toString();
		if (inpVal === '') inpVal = "0.0";
		if (isNaN(inpVal)) inpVal = "0.0";
		setApprovalAmt(new BigNumber(inpVal));
	};

	// determine current approval amount for TimelockManager for token source
	const checkApprovalBalance = async () => {
		await enshTokenContract.methods.allowance(sourceAddress, timelockAddr)
				.call({ from: sourceAddress })
			.then(apBal => {
				setCurrentAmt(apBal);
			});
	};
	checkApprovalBalance();

	const doApproval = async (resolve, reject) => {
		// sanity check form input
		if (approvalAmt.lte(0)) {
			let badAmt = new Error("Illegal approval amount, "
									+ approvalAmt.toString())
			alert(badAmt.message);
			reject(badAmt);
			return false;
		}

		// obtain approval from EnshroudToken contract for TimelockManager
		var retErr = undefined;
		const obtainSpendApproval = async (approvAmt) => {
			var approved = true;
			await enshTokenContract.methods.approve(timelockAddr, approvAmt)
					.send({ from: sourceAddress })
				.then(receipt => {
				/*
					console.debug("Approval of " + approvalAmt.toString()
								+ " $ENSHROUD for TimelockManager succeeded");
				 */
				})
				.catch(err => {
					console.error("Approval of " + approvalAmt.toString()
								+ " $ENSHROUD for TimelockManager failed, "
								+ "due to:\n" + err.message);
					alert("Approval for transfer of " + approvalAmt.toString()
						+ " $ENSHROUD to TimelockManager contract was denied: "
						+ err.message);
					approved = false;
					retErr = err;
				});
			return approved;
		};

		// obtain approval for transfer from source, as required
		var approvedAmt = new BigNumber(currentAmt);
		var appr = true;
		// scale up by 1e18 to wei
		const weiAmt = new BigNumber(web3.utils.toWei(approvalAmt.toFixed()));
		if (approvedAmt.lt(weiAmt)) {
			appr = await obtainSpendApproval(weiAmt.toFixed());
			if (!appr) {
				reject(retErr);
				return false;
			}
			setApprovalAmt(new BigNumber(0));
			setCurrentAmt(weiAmt.toFixed());
		}
		else {
			alert("Already approved for " + web3.utils.fromWei(currentAmt)
				+ ", nothing to do");
		}
		resolve(true);
	};

	// form to enter new timelock record
	const dispApproval = approvalAmt.eq(0) ? "" : approvalAmt.toString();

	const approvalFormUI =
		<>
			<hr/>
			<br/><br/>
			<h3>Specify $ENSHROUD Timelock Approval Amount:</h3>
			<br/>
			<p className="text-lead">
				This form is used to approve the Enshroud TimelockManager
				contract (<i>{timelockAddr}</i>) to transfer an amount of
				$ENSHROUD ERC20 tokens (<i>{enshTokenAddr}</i>) from your
				address.
				<br/><br/>
				This is done in conjunction with the TimelockManager admin's
				creation of the Timelock record, and must be performed in
				advance.
			</p>
			<Form>
				<Form.Group className="mb-3" controlId="source">
					<Form.Label>Token source address (yours):</Form.Label>
					<Form.Control type="text" readOnly value={sourceAddress} />
				</Form.Group>
				<Form.Group className="mb-3" controlId="source">
					<Form.Label>Current Approval Amount:</Form.Label>
					<Form.Control type="text" readOnly
						value={web3.utils.fromWei(currentAmt.toString())} />
				</Form.Group>
				<Form.Group className="mb-3" controlId="amount">
					<Form.Label>
						Number of tokens to Approve (1e-18 scale):
					</Form.Label>
					<Form.Control type="text" value={dispApproval}
						onChange={handleApprovalAmtChange} placeholder="0"
					/>
				</Form.Group>
				<LoadingButton variant="success"
					buttonStyle="m-3"
					buttonText="Approve Transfer"
					buttonTitle="Approve transfer of the quantity of tokens into the TimelockManager contract"
					buttonIcon="images/plus-circle.svg"
					netMethod={(resolve, reject) => doApproval(resolve, reject)}
				/>
			</Form>
		</>;

	// placeholder blurb for <DoSourceApproval/>
	const approvalPlaceholder =
		<>
			<br/><br/>
			<hr/>
			<br/>
			<h4>Placeholder for Approval Form</h4>
			<p className="text-lead">
				If your selected address were not the Timelock contract admin,
				a form to approve() a token transfer to the contract would
				appear here.
			</p>
		</>;

	// render new approval form
	return (
		<div id="newApproval">
		{
			!props.isManagerAuth ? approvalFormUI : approvalPlaceholder
		}
		</div>
	);
}

/* provide opportunity for entry of new Timelock records
 * (shown only if current account has admin privileges)
 * @param props.setManagerAuth method to set whether user is timelock admin
 * @param props.isManagerAuth true if user has admin permission in TimelockMgr
 */
function DoEnshroudTimelocks(props) {
	// enable use of our contracts and wallet accounts
	const { state: { accounts, contracts } } = useEth();
	const timelockMgrContract = contracts["TimelockManager"];

	// determine whether this user (accounts[0]) is allowed to admin Timelocks
	const authToAdmin = async () => {
		// obtain value of public variable 'admin'
		const admin = await timelockMgrContract.methods.admin()
												.call({ from: accounts[0] });
		props.setManagerAuth(admin === accounts[0]);
	};
	authToAdmin();

	// do actual rendering
	return (
		<div className="enshTimelocks">
			{ /* provide form to enter a new timelock */ }
			<NewEnshTimelock isManagerAuth={props.isManagerAuth} />

			{ /* provide form to specify approval for TimelockManager */ }
			<DoSourceApproval isManagerAuth={props.isManagerAuth} />
			<br/><br/>
		</div>
	);
}

export default DoEnshroudTimelocks;
