use super::*;
impl<N: Network> Process<N> {
    #[inline]
    pub fn verify_fee(&self, fee: &Fee<N>, deployment_or_execution_id: Field<N>) -> Result<()> {
        let timer = timer!("Process::verify_fee");
        #[cfg(debug_assertions)]
        {
            println!("Verifying fee from {}/{}...", fee.program_id(), fee.function_name());
            let stack = self.get_stack(fee.program_id())?;
            let function = stack.get_function(fee.function_name())?;
            if stack.get_number_of_calls(function.name())? != 1 {
                bail!("The number of function calls in '{}/{}' should be 1", stack.program_id(), function.name())
            }
            debug_assert_eq!(
                **fee.id(),
                N::hash_bhp512(&(fee.to_root()?, *fee.tcm()).to_bits_le())?,
                "Transition ID of the fee is incorrect"
            );
        }
        let is_fee_private = fee.is_fee_private();
        let is_fee_public = fee.is_fee_public();
        ensure!(is_fee_private || is_fee_public, "Incorrect program ID or function name for fee transition");
        ensure!(fee.inputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of inputs");
        ensure!(fee.outputs().len() <= N::MAX_INPUTS, "Fee exceeded maximum number of outputs");
        let Ok(candidate_id) = fee.deployment_or_execution_id() else {
            bail!("Failed to get the deployment or execution ID in the fee transition")
        };
        if candidate_id != deployment_or_execution_id {
            bail!("Incorrect deployment or execution ID in the fee transition")
        }
        lap!(timer, "Verify the deployment or execution ID");
        match is_fee_private {
            true => self.verify_fee_private(&fee)?,
            false => self.verify_fee_public(&fee)?,
        }
        finish!(timer, "Verify the fee transition");
        Ok(())
    }
}
impl<N: Network> Process<N> {
    fn verify_fee_private(&self, fee: &&Fee<N>) -> Result<()> {
        let timer = timer!("Process::verify_fee_private");
        let function_id = N::hash_bhp1024(
            &(U16::<N>::new(N::ID), fee.program_id().name(), fee.program_id().network(), fee.function_name())
                .to_bits_le(),
        )?;
        ensure!(
            fee.inputs().iter().filter(|input| matches!(input, Input::Record(..))).count() == 1,
            "The fee transition must contain *1* input record"
        );
        let num_inputs = fee.inputs().len();
        ensure!(num_inputs == 4, "The number of inputs in the fee transition should be 4, found {num_inputs}",);
        if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) {
            bail!("Failed to verify a fee input")
        }
        lap!(timer, "Verify the inputs");
        ensure!(
            fee.outputs().len() == 1,
            "The number of outputs in the fee transition should be 1, found {}",
            fee.outputs().len()
        );
        if fee
            .outputs()
            .iter()
            .enumerate()
            .any(|(index, output)| !output.verify(function_id, fee.tcm(), num_inputs + index))
        {
            bail!("Failed to verify a fee output")
        }
        lap!(timer, "Verify the outputs");
        let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates();
        let (parent_x, parent_y) = fee.program_id().to_address()?.to_xy_coordinates();
        let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm()];
        inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs()));
        inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]);
        inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs()));
        lap!(timer, "Construct the verifier inputs");
        #[cfg(debug_assertions)]
        println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs);
        let verifying_key = self.get_verifying_key(fee.program_id(), fee.function_name())?;
        Trace::verify_fee_proof((verifying_key, vec![inputs]), fee)?;
        finish!(timer, "Verify the fee proof");
        Ok(())
    }
    fn verify_fee_public(&self, fee: &&Fee<N>) -> Result<()> {
        let timer = timer!("Process::verify_fee_public");
        let function_id = N::hash_bhp1024(
            &(U16::<N>::new(N::ID), fee.program_id().name(), fee.program_id().network(), fee.function_name())
                .to_bits_le(),
        )?;
        ensure!(
            fee.inputs().iter().all(|input| matches!(input, Input::Public(..))),
            "The fee transition must contain *only* public inputs"
        );
        let num_inputs = fee.inputs().len();
        ensure!(num_inputs == 3, "The number of inputs in the fee transition should be 3, found {num_inputs}",);
        if fee.inputs().iter().enumerate().any(|(index, input)| !input.verify(function_id, fee.tcm(), index)) {
            bail!("Failed to verify a fee input")
        }
        lap!(timer, "Verify the inputs");
        ensure!(
            fee.outputs().len() == 1,
            "The number of outputs in the fee transition should be 1, found {}",
            fee.outputs().len()
        );
        if fee
            .outputs()
            .iter()
            .enumerate()
            .any(|(index, output)| !output.verify(function_id, fee.tcm(), num_inputs + index))
        {
            bail!("Failed to verify a fee output")
        }
        lap!(timer, "Verify the outputs");
        let (tpk_x, tpk_y) = fee.tpk().to_xy_coordinates();
        let (parent_x, parent_y) = fee.program_id().to_address()?.to_xy_coordinates();
        let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **fee.tcm()];
        inputs.extend(fee.inputs().iter().flat_map(|input| input.verifier_inputs()));
        inputs.extend([*Field::<N>::one(), *parent_x, *parent_y]);
        inputs.extend(fee.outputs().iter().flat_map(|output| output.verifier_inputs()));
        lap!(timer, "Construct the verifier inputs");
        #[cfg(debug_assertions)]
        println!("Fee public inputs ({} elements): {:#?}", inputs.len(), inputs);
        let verifying_key = self.get_verifying_key(fee.program_id(), fee.function_name())?;
        Trace::verify_fee_proof((verifying_key, vec![inputs]), fee)?;
        finish!(timer, "Verify the fee proof");
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use console::prelude::TestRng;
    use ledger_block::Transaction;
    #[test]
    fn test_verify_fee() {
        let rng = &mut TestRng::default();
        let transactions = [
            ledger_test_helpers::sample_deployment_transaction(true, rng),
            ledger_test_helpers::sample_deployment_transaction(false, rng),
            ledger_test_helpers::sample_execution_transaction_with_fee(true, rng),
            ledger_test_helpers::sample_execution_transaction_with_fee(false, rng),
            ledger_test_helpers::sample_fee_private_transaction(rng),
            ledger_test_helpers::sample_fee_public_transaction(rng),
        ];
        let process = Process::load().unwrap();
        for transaction in transactions {
            match transaction {
                Transaction::Deploy(_, _, deployment, fee) => {
                    let deployment_id = deployment.to_deployment_id().unwrap();
                    assert!(process.verify_fee(&fee, deployment_id).is_ok());
                }
                Transaction::Execute(_, execution, fee) => {
                    let execution_id = execution.to_execution_id().unwrap();
                    assert!(process.verify_fee(&fee.unwrap(), execution_id).is_ok());
                }
                Transaction::Fee(_, fee) => match fee.is_fee_private() {
                    true => assert!(process.verify_fee_private(&&fee).is_ok()),
                    false => assert!(process.verify_fee_public(&&fee).is_ok()),
                },
            }
        }
    }
}