/*
 * last modified---
 * 	01-28-25 use LoadingButton
 * 	03-18-24 allow zeroing out stakingQuantum field to reset
 * 	03-01-24 allow zeroing out 0x data fields to replace
 * 	01-15-24 check for admin status before making buttons available
 * 	05-24-23 move MVOStaking class to ../MVOs.js
 * 	05-17-23 new
 *
 * purpose---
 * 	provide UI to examine and update "struct MVO{}" records stored in the
 * 	MVOStaking contract
 */

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';
import { MVOStaking } from '../MVOs.js';
import LoadingButton from '../LoadingButton.jsx';


// tell the user if they're not entitled to make changes to MVOStaking records
function NoticeNoUpdates() {
	return (
		<p>⚠️ Disabled -- not admin.</p>
	);
}

// tell the user if they're not entitled to make changes to MVOStaking records
function NoticeNoEntries() {
	return (
		<>
			<p>
				⚠️ You are not an admin for
				the <span className="code">MVOStaking</span> contract.
				New record entry disabled.
			</p>
		</>
	);
}

/* render an MVOStaking as a table row, with change inputs following the values
 * @param props.mvoStaking the MVOStaking record to render
 * @param props.onUpdateStaking method to record changes made on-chain
 * @param props.isStakingAuth whether user has permission to make changes
 * @param props.unique value to make form id= values unique (== key)
 */
function MVOStakingRenderer(props) {
	const staking = props.mvoStaking;
	const upd = 'Update: ';
	const uniq = props.unique;

	// enable use of our contracts and accounts
	const { state: { contracts, accounts, web3 } } = useEth();
	const userAcct = accounts[0];
	const mvoStakingContract = contracts["MVOStaking"];

	// track state of form fields
	const [updateForm, setUpdateForm] = useState({
		mvoId: staking.mvoId,
		listenURI: staking.listenURI,
		stakingAddress: staking.stakingAddress,
		signingAddress: staking.signingAddress,
		encrPubkey: staking.encrPubkey,
		stakingQuantum: staking.stakingQuantum,
		rating: staking.rating,
		active: staking.active,
	});

	// process a MVOId entry (modification not allowed)
	const handleIdChange = e => {
		if (staking.mvoId === '' && /MVO-[0-9]+/.test(e.target.value)) {
			setUpdateForm({...updateForm, mvoId: e.target.value});
		}
	};

	// process a stakingAddress value change
	const handleStakingChange = e => {
		if (e.target.value === '' || /^0x[0-9a-fA-F]+/.test(e.target.value)) {
			setUpdateForm({...updateForm, stakingAddress: e.target.value});
		}
	};

	// process a signingAddress value change
	const handleSigningChange = e => {
		if (e.target.value === '' || /^0x[0-9a-fA-F]+/.test(e.target.value)) {
			setUpdateForm({...updateForm, signingAddress: e.target.value});
		}
	};

	// process a encrPubkey value change
	const handlePubkeyChange = e => {
		let pubKeyInp = e.target.value.trim();
		if (pubKeyInp === '' || /^0x[0-9a-fA-F]+/.test(pubKeyInp)) {
			setUpdateForm({...updateForm, encrPubkey: pubKeyInp});
		}
	};

	// process a stakingQuantum value change
	const handleQuantumChange = e => {
		let stakeAmt = e.target.value;
		if (stakeAmt === '') {
			stakeAmt = '0';
		}
		const stakeWei = web3.utils.toWei(stakeAmt);
		if (/^[0-9]+/.test(stakeAmt)) {
			setUpdateForm({...updateForm, stakingQuantum: stakeWei});
		}
	};

	// process a rating value change
	const handleRatingChange = e => {
		if (/^[0-9]+/.test(e.target.value)) {
			setUpdateForm({...updateForm, rating: e.target.value});
		}
	};

	// handle active setting change
	const handleActiveChange = e => {
		setUpdateForm({...updateForm, active: e.target.checked});
	};

	// submit a changed mvoStaking record to the blockchain
	async function updateStakingRecord(resolve, reject) {
		const mvoStaking = staking;
		// determine whether anything was changed in the form
		var changed = false;
		if (mvoStaking.listenURI !== updateForm.listenURI) {
			changed = true;
		} else if (mvoStaking.stakingAddress
					!== updateForm.stakingAddress.trim())
		{
			changed = true;
		} else if (mvoStaking.signingAddress
					!== updateForm.signingAddress.trim())
		{
			changed = true;
		} else if (mvoStaking.encrPubkey !== updateForm.encrPubkey) {
			changed = true;
		} else if (mvoStaking.stakingQuantum !== updateForm.stakingQuantum) {
			changed = true;
		} else if (mvoStaking.rating !== updateForm.rating.trim()) {
			changed = true;
		} else if (mvoStaking.active !== updateForm.active) {
			changed = true;
		}
		if (changed) {
			// update record on-chain
			const MVO = {
				// build new record from changes
				listenURI: updateForm.listenURI,
				stakingAddress: updateForm.stakingAddress,
				signingAddress: updateForm.signingAddress,
				encrPubkey: updateForm.encrPubkey,
				stakingQuantum: updateForm.stakingQuantum,
				memberPoolId: mvoStaking.memberPoolId,
				rating: updateForm.rating,
				active: updateForm.active,
			};
			await mvoStakingContract.methods.updateMvoStatus(mvoStaking.mvoId,
					MVO).send({ from: userAcct })
				.then(tx => {
					// tell the React context update worked
					props.onUpdateStaking(mvoStaking);
					staking.config(MVO);
					resolve(true);
				})
				.catch(err => {
					alert("Error: code " + err.code + ", " + err.message);
					reject(err);
					return false;
				});
		}
		else {
			alert("No change made to record for MVO Id " + mvoStaking.mvoId);
			resolve(true);
		}
		return true;
	}

	// render object
	return (
		<tr align="center" valign="middle">
			{ /* mvoId not updatable, unless it's unset */}
			<td>{staking.mvoId}<br/>
				<label htmlFor={uniq + "idUpdate"}>
					<input type="text" size="8" maxLength="10"
						value={updateForm.mvoId} disabled id={uniq + "idUpdate"}
						onChange={handleIdChange}/>
				</label>
			</td>
			{ /* update listenURI */ }
			<td>{staking.listenURI}<br/>
				<label htmlFor={uniq + "urlUpdate"}>{upd}
					<input type="url" size="24" maxLength="64"
						value={updateForm.listenURI} id={uniq + "urlUpdate"}
						onChange={e => setUpdateForm({...updateForm,
												listenURI: e.target.value})} />
				</label>
			</td>
			{ /* update stakingAddress */ }
			<td>{staking.stakingAddress}<br/>
				<label htmlFor={uniq + "stakingUpd"}>{upd}
					<input type="text" size="40" maxLength="42"
						value={updateForm.stakingAddress} className="text-break"
						id={uniq + "stakingUpd"}
						onChange={handleStakingChange} />
				</label>
			</td>
			{ /* update signingAddress */ }
			<td>{staking.signingAddress}<br/>
				<label htmlFor={uniq + "signingUpd"}>{upd}
					<input type="text" size="40" maxLength="42"
						id={uniq + "signingUpd"}
						value={updateForm.signingAddress} className="text-break"
						onChange={handleSigningChange} />
				</label>
			</td>
			{ /* update encrPubkey */ }
			<td>{staking.encrPubkey}<br/>
				<label htmlFor={uniq + "keyUpd"}>{upd}
					<input type="text" size="130" maxLength="132"
						id={uniq + "keyUpd"} placeholder="0x"
						value={updateForm.encrPubkey} className="text-break"
						onChange={handlePubkeyChange} />
				</label>
			</td>
			{ /* update stakingQuantum */}
			<td>{web3.utils.fromWei(staking.stakingQuantum)}<br/>
				<label htmlFor={uniq + "quantumUpd"}>{upd}
					<input type="text" size="8" maxLength="12"
						id={uniq + "quantumUpd"}
						value={web3.utils.fromWei(updateForm.stakingQuantum)}
						onChange={handleQuantumChange} />
				</label>
			</td>
			<td>{staking.memberPoolId}</td>
			{ /* memberPoolId not updatable */ }
			{ /* update rating */ }
			<td>{staking.rating}<br/>
				<label htmlFor={uniq + "ratingUpd"}>{upd}
					<input type="text" size="3" maxLength="4"
						id={uniq + "ratingUpd"} value={updateForm.rating}
						onChange={handleRatingChange} />
				</label>
			</td>
			{ /* update active status */ }
			<td>
				{staking.active ? "true" : "false"}
				<Form.Label htmlFor={uniq + "activeToggle"}>{upd}
					<Form.Switch id={uniq + "activeToggle"}
						checked={updateForm.active}
						onChange={handleActiveChange}
					/>
				</Form.Label>
			</td>

			{ /* submit button */ }
			<td align="center">
				{
					!props.isStakingAuth ? <NoticeNoUpdates /> :
						<LoadingButton variant="secondary"
							buttonStyle="m-3"
							buttonTitle="Post changes to the blockchain"
							buttonText="Update"
							buttonIcon="images/check2.svg"
							netMethod={(resolve, reject) => updateStakingRecord(resolve, reject)}
						/>
				}
			</td>
		</tr>
	);

}

/* method to render the table of MVOStaking records
 * @param props.stakingData the list of MVOStaking records to render
 * @param props.onUpdateStaking method to record any changes made on-chain
 * @param props.isStakingAuth whether user has permission to make changes
 */
function MVOStakingsTable(props) {
	let sIdx = 1;
	const nf = new Intl.NumberFormat("en-US", {minimumIntegerDigits: 3});
	const hdr = "header" + nf.format(sIdx);
	return (
		<Table striped bordered hover responsive variant="dark">
			<caption className="caption-top">
				MVO Stakings:
			</caption>
			<thead>
				<tr align="center" key={hdr}>
					<th scope="col">MVO Id</th>
					<th scope="col">Listen URI</th>
					<th scope="col">Staking Address</th>
					<th scope="col">Signing Address</th>
					<th scope="col">Encryption Pubkey</th>
					<th scope="col">Staked Tokens</th>
					<th scope="col">MVO Pool Id</th>
					<th scope="col">Rating (1-100)</th>
					<th scope="col">Active Status</th>
					<th scope="col">Update</th>
				</tr>
			</thead>
			<tbody>
				{props.stakingData.map((staking) =>
					<MVOStakingRenderer unique={sIdx}
						key={sIdx++}
						mvoStaking={staking}
						onUpdateStaking={props.onUpdateStaking}
						isStakingAuth={props.isStakingAuth}
					/>
				)}
			</tbody>
		</Table>
	);
}

/* method to display entry form for an entirely new MVO staking
 * @param props.onNewStaking method to record newly submitted MVO staking
 * @param props.minStaking minimum staking required for a new record
 * @param props.isStakingAuth whether the current user is designated an admin
 */
function NewMVOStaking(props) {
	// enable use of our contracts and Metamask accounts
	const { state: { contracts, accounts, web3 } } = useEth();
	const userAcct = accounts[0];

	// track state of form fields
	const [entryForm, setEntryForm] = useState({
		mvoId: '',
		listenURI: '',
		stakingAddress: '',
		signingAddress: '',
		encrPubkey: '',
		stakingQuantum: 0,
		rating: 0,
		active: true,
	});

	// submit a new mvoStaking record to the blockchain
	async function addStakingRecord(resolve, reject) {
		const mvoStakingContract = contracts["MVOStaking"];

		// sanity check form inputs
		if (!web3.utils.isAddress(entryForm.stakingAddress)) {
			let stakeAddr = new Error("Illegal MVO staking address, \""
									+ entryForm.stakingAddress + "\"");
			alert(stakeAddr.message);
			reject(stakeAddr);
			return false;
		}
		if (!web3.utils.isAddress(entryForm.signingAddress)) {
			let signAddr = new Error("Illegal MVO signing address, \""
									+ entryForm.signingAddress + "\"");
			alert(signAddr.message);
			reject(signAddr);
			return false;
		}
		if (entryForm.stakingQuantum < 0) {
			let badQuant = new Error("Illegal staking quantum, \""
									+ entryForm.stakingQuantum + "\"");
			alert(badQuant.message);
			reject(badQuant);
			return false;
		}

		// update record on-chain
		const MVO = {
			// build new record from changes
			listenURI: entryForm.listenURI,
			stakingAddress: entryForm.stakingAddress,
			signingAddress: entryForm.signingAddress,
			encrPubkey: entryForm.encrPubkey,
			// scale up to 1e18
			stakingQuantum: web3.utils.toWei(`${entryForm.stakingQuantum}`),
			memberPoolId: 0,
			rating: entryForm.rating,
			active: entryForm.active,
		};

		// NB: inserts are handled by the contract's update method
		await mvoStakingContract.methods.updateMvoStatus(entryForm.mvoId, MVO)
				.send({ from: userAcct })
			.then(tx => {
				// build new UI object and tell React the insert worked
				const mvoStaking = new MVOStaking();
				mvoStaking.setMVOId(entryForm.mvoId);
				mvoStaking.config(MVO);
				const newStakings = [];
				newStakings.push(mvoStaking);
				props.onNewStaking(newStakings);
				resolve(true);
			})
			.catch(err => {
				alert("Error: code " + err.code + ", " + err.message);
				reject(err);
				return false;
			});
		return true;
	}

	// render entry form
	return (
		<>
			<br/><br/>
			<h3>Enter new MVO Staking record:</h3>
			<br/>
			<Form>
				<Form.Group className="mb-3" controlId="MVOid">
					<Form.Label>MVO ID</Form.Label>
					<Form.Control type="text" placeholder="MVO-nnn"
						value={entryForm.mvoId}
						onChange={e => setEntryForm({...entryForm,
													mvoId: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="URL">
					<Form.Label>MVO's user URL</Form.Label>
					<Form.Control type="url" placeholder="http://"
						value={entryForm.listenURI}
						onChange={e => setEntryForm({...entryForm,
													listenURI: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="stakeAddr">
					<Form.Label>Staking address (hex)</Form.Label>
					<Form.Control type="text" placeholder="0x"
						value={entryForm.stakingAddress}
						onChange={e => setEntryForm({...entryForm,
											stakingAddress: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="signAddr">
					<Form.Label>Signing address (hex)</Form.Label>
					<Form.Control type="text" placeholder="0x"
						value={entryForm.signingAddress}
						onChange={e => setEntryForm({...entryForm,
											signingAddress: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="pubkey">
					<Form.Label>
						Encryption pubkey (uncompressed, hex)
					</Form.Label>
					<Form.Control type="text"
						value={entryForm.encrPubkey}
						onChange={e => setEntryForm({...entryForm,
												encrPubkey: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="quantum">
					<Form.Label>
						Staking quantum (1e-18 scale, min {props.minStaking})
					</Form.Label>
					<Form.Control type="text" placeholder={props.minStaking}
						value={entryForm.stakingQuantum}
						onChange={e => setEntryForm({...entryForm,
											stakingQuantum: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="rating">
					<Form.Label>Rating (0-100)</Form.Label>
					<Form.Control type="text"
						value={entryForm.rating} placeholder="50"
						onChange={e => setEntryForm({...entryForm,
													rating: e.target.value})}
					/>
				</Form.Group>
				<Form.Group className="mb-3" controlId="active">
					<Form.Label>Active (true / false)</Form.Label>
					<Form.Switch checked={entryForm.active}
						onChange={e => setEntryForm({...entryForm,
													active: e.target.checked})}
					/>
				</Form.Group>
				{
					!props.isStakingAuth ? <NoticeNoEntries /> :
						<LoadingButton variant="success"
							buttonStyle="m-3"
							buttonText="Add Staking"
							buttonIcon="images/plus-circle.svg"
							buttonTitle="Create staking on the blockchain"
							netMethod={(resolve, reject) => addStakingRecord(resolve, reject)}
						/>
				}
			</Form>
		</>
	);
}

/* display existing MVO stakings records, and provide opportunity for entry of
 * new records and modification of selected existing
 * @param props.stakings list of downloaded MVOStaking records
 * @param props.minStaking minimum staking an MVO can have (150000 default)
 * @param props.updMinStake method to adjust the minimum MVO staking quantum
 * @param props.onNewStaking method to record a newly submitted MVOStaking rec
 * @param props.onUpdateStaking method to record an update to a MVOStaking rec
 * @param props.isStakingAuth whether the current user is designated an admin
 * @param props.setStakingAuth method to record whether current user is an admin
 */
function UpdateMVOStakings(props) {
	const stakings = props.stakings;

	// enable use of our contracts and Metamask accounts
	const { state: { contracts, accounts } } = useEth();
	const mvoStakingContract = contracts["MVOStaking"];
	const enshProtocolContract = contracts["EnshroudProtocol"];
	const userAcct = accounts[0];

	// determine whether this user (accounts[0]) is allowed to update
	const authToUpdate = async () => {
		// NB: MVOStaking admins are EnshroudProtocol admins
		const auth = await enshProtocolContract.methods.adminStatus(userAcct)
													.call({ from: userAcct } );
		props.setStakingAuth(auth);
	};
	authToUpdate();

	// obtain the set of all existing MVO records
	const fetchMVOrecords = async (stakingRecs) => {
		// get the total number of struct MVO records
		const mvoCnt = await mvoStakingContract.methods.mvoIndex().call(
														{ from: accounts[0] });
		
		const minStake = await mvoStakingContract.methods.minStake().call(
														{ from: accounts[0] });
		if (minStake > 0) {
			const minMVOStakingQuantum = minStake / 1e18;
			props.updMinStake(minMVOStakingQuantum);
		}

		// get the list of MVO Ids at all indexes
		var mvoIds = [];
		for (let iii = 0; iii < mvoCnt; iii++) {
			const mvoId = await mvoStakingContract.methods.mvoIds(iii).call(
														{ from: accounts[0] });
			if (mvoId !== undefined) {
				mvoIds.push(mvoId);
			}
		}

		// get the actual records
		var mvoRecords = [];
		for await (const mId of mvoIds) {
			const mvoRec = await mvoStakingContract.methods.idToMVO(mId).call(
														{ from: accounts[0] });
			if (mvoRec !== undefined) {
				mvoRecords.push(mvoRec);
			} else {
				console.error("No staking found for MVOId " + mId);
			}
		}
		const newStakings = [];

		// create UI objects for each record found
		for (let jjj = 0; jjj < mvoRecords.length; jjj++) {
			const mvoStaking = new MVOStaking();
			mvoStaking.setMVOId(mvoIds[jjj]);
			mvoStaking.config(mvoRecords[jjj]);

			// add if new, otherwise update
			var found = false;
			for (let kkk = 0; kkk < stakings.length; kkk++) {
				if (stakings[kkk].mvoId === mvoStaking.mvoId) {
					found = true;
					break;
				}
			}
			if (found) {
				props.onUpdateStaking(mvoStaking);
			} else {
				newStakings.push(mvoStaking);
			}
		}
		// add all of our new ones in one go
		if (newStakings.length > 0) {
			props.onNewStaking(newStakings);
		}
	};

	return (
		<div className="mvoStakings">
			<h4>Use button to populate table with existing records</h4>
			<Button variant="primary"
				onClick={() => fetchMVOrecords(stakings)}
				className="m-3"
				title="Fetch MVO Staking records from blockchain"
			>
				Fetch Staking Records
			</Button>
			<br/><br/>

			{ /* display existing records in modifiable table form */ }
			<MVOStakingsTable stakingData={stakings}
				onUpdateStaking={props.onUpdateStaking}
				isStakingAuth={props.isStakingAuth}
			/>
			<br/><br/>

			{ /* provide form to enter a new staking record */ }
			<NewMVOStaking onNewStaking={props.onNewStaking}
				minStaking={props.minStaking}
				isStakingAuth={props.isStakingAuth}
			/>
			<br/><br/>
		</div>
	);
}

export default UpdateMVOStakings;
