/*
 * last modified---
 * 	08-17-26 add text indicating paid API key required to see relevant events
 * 	01-05-24 accommodate changes to GreyListAdd events
 * 	12-28-23 new
 *
 * purpose---
 * 	provide UI to examine past greylisting of eNFT IDs, and to un-greylist them
 * 	given admin permission to do so
 */

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


// tell the user if they're not entitled to do un-greylisting
function NoticeNotAdmin() {
	return (
		<>
			<br/><br/>
			<h3>Enter eNFT ID to un-greylist:</h3>
			<br/>
			<p>
				⚠️ Cannot find your address listed as an admin in
				the <span className="code">EnshroudProtocol</span> contract.
				Greylist removal disabled.
			</p>
		</>
	);
}

/* render a greylisting entry as a table row
 * @param props.greylisting the greylisting record for a given eNFT ID
 */
function GreylistRenderer(props) {
	const greylistRec = props.greylisting;
	const unGL = greylistRec.ungreylistBlock !== 'n/a';

	// make a short version of the eId
	const eIdShort = greylistRec.id.substring(0, 5) + '...'
					+ greylistRec.id.substring(59);
	const title = unGL ? greylistRec.id : eIdShort;
	const full = unGL ? eIdShort : greylistRec.id;
	let reason = unGL ? "(not greylisted)" : greylistRec.reason;
	reason = reason.replace("\n", "; ");

	// render object
	return (
		<tr align="center" valign="middle">
			<td>{greylistRec.block}</td>
			<td className="text-break" title={title}>{full}</td>
			<td>{greylistRec.audId}</td>
			<td className="text-break">{reason}</td>
			<td title={unGL ? "ID was un-greylisted in this block" : "ID remains greylisted"}>
				{greylistRec.ungreylistBlock}
			</td>
		</tr>
	);
}

/* method to render the table of existing greylisting records
 * @param props.greylistings the list of previous greylist records from events
 */
function GreylistingTable(props) {
	let gIdx = 1;
	const nf = new Intl.NumberFormat("en-US", {minimumIntegerDigits: 3});
	const hdr = "header" + nf.format(gIdx);

	// render table
	return (
		<Table striped bordered hover responsive variant="dark">
			<caption className="caption-top">
				Currently Greylisted IDs:
			</caption>
			<thead>
				<tr align="center" key={hdr}>
					<th scope="col"
						title="Block number in which this ID was greylisted">
						Greylist Block
					</th>
					<th scope="col">eNFT ID</th>
					<th scope="col"
						title="ID of Auditor node that performed greylisting">
						Auditor Id
					</th>
					<th scope="col"
						title="Reason supplied by Auditor for greylisting ID">
						Reason
					</th>
					<th scope="col"
						title="Latest block number in which this ID was un-greylisted (by 3 admins), or n/a if still greylisted">
						Whitelist Block
					</th>
				</tr>
			</thead>
			<tbody>
				{props.greylistings.map((greylistRec) =>
					<GreylistRenderer key={gIdx++}
						greylisting={greylistRec}
					/>
				)}
			</tbody>
		</Table>
	);
}

/* method to supply an entry form for an admin to ungreylist/whitelist an ID
 * @param props.greylistings list of greylisting entries built from events
 * @param props.isGreylistEnabled true if user has authorized admin perms
 * @param props.recordUnGreylist method to record a single greylist remove event
 */
function UnGreylistId(props) {
	// enable use of our contracts and wallet accounts
	const { state: { accounts, contracts, web3 } } = useEth();
	const enshProtocolContract = contracts["EnshroudProtocol"];
	const userAcct = web3.utils.toChecksumAddress(accounts[0]);

	// track state of eNFT Id field
	const [enftId, setEnftId] = useState('');

	/* utility method to obtain the greylisting reason from the
	 * idToAuditorGreylist mapping for a given ID
	 * @param eId the eNFT ID
	 * @return the reason, or empty string if found
	 */
	const isIdGreylisted = async (eId) => {
		const reason
			= await enshProtocolContract.methods.idToAuditorGreylist(eId)
				.call({ from: userAcct })
			.catch(err => {
				console.error("Error fetching greylisting reason, " + err);
			});
		return reason !== '';
	};

	// process an eNFT Id value change
	const handleEnftIdChange = e => {
		if (/[0-9a-fA-F]*/.test(e.target.value)) {
			setEnftId(e.target.value);
		}
	};

	// submit a request to remove greylist status
	const submitRemoveGreylist = async () => {
		// check perms
		if (!props.isGreylistEnabled) {
			alert("You do not appear to have admin permissions, cannot "
					+ "remove greylisting");
			return;
		}

		// check ID was entered
		if (enftId === '' || enftId.toString().length !== 64) {
			alert("The eNFT ID entered does not appear valid, should be 64 "
				+ "hex digits");
			return;
		}

		// make sure we've downloaded this greylisted ID
		const greylistRec = props.greylistings.find(elt => elt.id === enftId);
		if (greylistRec === undefined) {
			alert("The entered eNFT Id, " + enftId
					+ ", has apparently not been downloaded; use Fetch button");
			return;
		}

		// sanity check input Id -- must be currently greylisted
		const eId = "0x" + enftId;
		const idIsGrey = await isIdGreylisted(eId);
		if (!idIsGrey) {
			alert("The entered eNFT Id, " + enftId + ", is not greylisted");
			return;
		}

		// invoke contract method (must be admin)
		var glBlock = '';
		await enshProtocolContract.methods.removeGreylistStatus(eId)
				.send({ from: userAcct })
			.then(receipt => {
				// record block from transaction receipt
				glBlock = receipt.blockNumber;
				alert("eNFT ID " + enftId + " successfully whitelisted by you");
			})
			.catch(err => {
				console.error("Error: code " + err.code + ", " + err.message);
				alert("Error: code " + err.code + ", " + err.message);
				return;
			});

		// see whether it took effect or needs counter-sigs
		const idStillGrey = await isIdGreylisted(eId);
		if (idStillGrey) {
			alert("eNFT ID " + enftId + " still requires whitelisting by "
					+ "additional admins");
		} else {
			alert("eNFT ID " + enftId + " has now been whitelisted by "
					+ "3 admins, and is now usable as an input");
			// update the ungrelisted block in the downloaded records
			greylistRec.ungreylistBlock = glBlock;
			greylistRec.reason = '';
			props.recordUnGreylist(greylistRec);
		}
	};

	// form to remove greylisting for an Id
	const ungreylistForm =
		<>
			<br/><br/>
			<h3>Enter eNFT ID to un-greylist:</h3>
			<br/>
			<Form>
				<Form.Group className="mb-3" controlId="enftId">
					<Form.Label>eNFT ID</Form.Label>
					<Form.Control type="text"
						value={enftId} onChange={handleEnftIdChange}
					/>
				</Form.Group>
				<Button variant="success" className="m-3"
					title="Un-greylist the entered eNFT ID"
					onClick={submitRemoveGreylist}
				>
					Remove Greylisting
				</Button>
			</Form>
		</>;
	
	// render greylist removal form
	return (
		<div id="removeGL">
		{
			!props.isGreylistEnabled ? <NoticeNotAdmin /> : ungreylistForm
		}
		</div>
	);
}


/* display past eNFT ID greylisting records, and provide opportunity
 * for un-greylisting them (shown only if current account has admin privileges)
 * @param props.greylistings list of greylisting entries built from events
 * @param props.addGreylistRecs method to record downloaded greylisting events
 * @param props.recordUnGreylist method to record a single greylist remove event
 * @param props.isGreylistEnabled whether user has admin privs to un-greylist
 * @param props.setGreylistAdmin method to record whether user is greylist admin
 */
function DoEnshroudGreylisting(props) {
	const greylistings = props.greylistings;

	// enable use of our contracts and wallet accounts
	const { state: { accounts, contracts, web3, chainConn } } = useEth();
	var startBlock = chainConn.chainConfig.protocolGenesis;
	if (startBlock === undefined) {
		console.error("No protocolGenesis found for EnshroudProtocol on chain "
					+ "Id " + chainConn.chainConfig.chainId);
		startBlock = "earliest";
	}
	const enshProtocolContract = contracts["EnshroudProtocol"];
	const userAcct = web3.utils.toChecksumAddress(accounts[0]);

	// determine whether this user (accounts[0]) is allowed to admin
	const authToAdmin = async () => {
		const auth = await enshProtocolContract.methods.adminStatus(
									accounts[0]).call({ from: accounts[0] });
		props.setGreylistAdmin(auth);
	};
	authToAdmin();

	/* utility method to obtain the greylisting reason from the
	 * idToAuditorGreylist mapping for a given ID
	 * @param eId the eNFT ID
	 * @return the reason, or empty string if found
	 */
	const getGreylistReason = async (eId) => {
		const reason
			= await enshProtocolContract.methods.idToAuditorGreylist(eId)
				.call({ from: userAcct })
			.catch(err => {
				console.error("Error fetching greylisting reason, " + err);
			});
		return reason;
	};

	// obtain the set of all previous greylistings
	const fetchPastGreylists = async () => {
		// first, obtain all past GreyListAdd events
		const eventSig = web3.eth.abi.encodeEventSignature(
											'GreyListAdd(string,uint256[])');
		var prevGreylistings = [];
		await web3.eth.getPastLogs({
			address: enshProtocolContract.options.address,
			fromBlock: startBlock,
			topics: [ eventSig ],
		})
		.then(logEventList => {
			logEventList.forEach(logEvent => {
				/* The .data field will contain a variable number of chars,
				 * consisting of a leading 0x followed by the AuditorId (of the
				 * form 'AUD-00N'), plus the Ids (each one a uint256 in hex),
				 * plus the AUD's greylisting reason for each (which is
				 * free-form any length).
				 */
				var greylistLog = web3.eth.abi.decodeLog([{
						type: 'string',
						name: 'audId'
					},{
						type: 'uint256[]',
						name: 'ids'
					}],
					logEvent.data,
					logEvent.topics
				);

				// create an individual record for each id+reason in the event
				const ids = greylistLog.ids;
				for (let lIdx = 0; lIdx < ids.length; lIdx++) {
					// convert ID from decimal to hex
					const eId = new BigNumber(ids[lIdx]);
					const greyListRec = {
						block: logEvent.blockNumber,
						audId: greylistLog.audId,
						id: web3.utils.padLeft(eId.toString(16), 64),
					};
					prevGreylistings.push(greyListRec);
				}
			});
		})
		.catch(err => {
			console.error("Error: code " + err.code + ", " + err.message);
			alert("Error: code " + err.code + ", " + err.message
				+ "; [you may need a paid API key to see events this old]");
		});

		/* loop through created records and obtain greylist reason from the 
		 * idToAuditorGreylist mapping
		 */
		for await (const glRec of prevGreylistings) {
			const chainReason = await getGreylistReason("0x" + glRec.id);
			if (chainReason !== '') {
				glRec.reason = chainReason;
			}
		}

		// now also get the previous logs for GreyListDeletion events
		const delEventSig
			= web3.eth.abi.encodeEventSignature('GreyListDeletion(uint256)');
		var prevGreylistDeletions = [];
		await web3.eth.getPastLogs({
			address: enshProtocolContract.options.address,
			fromBlock: startBlock,
			topics: [ delEventSig ],
		})
		.then(delEventList => {
			delEventList.forEach(delEvent => {
				/* The .data field will contain a leading 0x plus a single
				 * uint256 representing the eNFT Id.
				 */
				const eId = delEvent.data.substring(2);
				const unGreyRec = {
					block: delEvent.blockNumber,
					id: eId,
				};
				prevGreylistDeletions.push(unGreyRec);
			});
		})
		.catch(err => {
			console.error("Error: code " + err.code + ", " + err.message);
			alert("Error: code " + err.code + ", " + err.message
				+ "; [you may need a paid API key to see events this old]");
		});

		// loop through all greylisting recs and see if they were un-greylisted
		prevGreylistings.forEach(greyListRec => {
			const found = prevGreylistDeletions.findLast(
											elt => elt.id === greyListRec.id);
			if (found !== undefined) {
				// add block number of ungreylist record to greylist record
				greyListRec.ungreylistBlock = found.block;
				greyListRec.reason = '';
			} else {
				greyListRec.ungreylistBlock = 'n/a';
			}
		});

		// tell React the log extracts worked
		props.addGreylistRecs(prevGreylistings);
	};

	// do actual rendering
	return (
		<div className="enshGreylistings">
			<h4>Use button to populate table with previous greylistings</h4>
			<p><i>
				Note: due to the age of these events, you may need a JSON-RPC
				endpoint using a paid API key in order to retrieve them.
			</i></p>
			<Button variant="primary"
				onClick={() => fetchPastGreylists()} className="m-3"
				title="Fetch eNFT ID greylisting records from blockchain"
			>
				Fetch Previous Greylistings
			</Button>
			<br/><br/>

			{ /* display prior greylistings in table form */ }
			<GreylistingTable greylistings={greylistings} />

			{ /* provide form to enter a new greylist removal */ }
			<UnGreylistId greylistings={greylistings}
				isGreylistEnabled={props.isGreylistEnabled}
				recordUnGreylist={props.recordUnGreylist}
			/>
			<br/><br/>
		</div>
	);
}

export default DoEnshroudGreylisting;
