use primitive_types::H256;
use serde::{Deserialize, Serialize};

use crate::{neo_protocol::LogNotification, TypeError};
use neo3::prelude::{deserialize_h256, serialize_h256, StackItem, VMState};

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ApplicationLog {
	#[serde(rename = "txid")]
	#[serde(serialize_with = "serialize_h256")]
	#[serde(deserialize_with = "deserialize_h256")]
	pub transaction_id: H256,
	#[serde(default)]
	pub executions: Vec<Execution>,
}

impl Default for ApplicationLog {
	fn default() -> Self {
		Self { transaction_id: H256::zero(), executions: vec![] }
	}
}

impl ApplicationLog {
	pub fn get_first_execution(&self) -> Result<&Execution, TypeError> {
		if self.executions.is_empty() {
			return Err(TypeError::IndexOutOfBounds(
				"This transaction does not have any executions.".to_string(),
			));
		}
		self.get_execution(0)
	}

	pub fn get_execution(&self, index: usize) -> Result<&Execution, TypeError> {
		if index >= self.executions.len() {
			return Err(TypeError::IndexOutOfBounds(format!(
				"This transaction has only {} executions.",
				self.executions.len()
			)));
		}
		Ok(&self.executions[index])
	}
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Execution {
	pub trigger: String,
	#[serde(rename = "vmstate")]
	pub state: VMState,
	pub exception: Option<String>,
	#[serde(rename = "gasconsumed")]
	pub gas_consumed: String,
	#[serde(default)]
	pub stack: Vec<StackItem>,
	#[serde(default)]
	pub notifications: Vec<LogNotification>,
}

impl Execution {
	pub fn get_first_stack_item(&self) -> Result<&StackItem, TypeError> {
		if self.stack.is_empty() {
			return Err(TypeError::IndexOutOfBounds(
                "The stack is empty. This means that no items were left on the NeoVM stack after this execution."
                    .to_string(),
            ));
		}
		self.get_stack_item(0)
	}

	pub fn get_stack_item(&self, index: usize) -> Result<&StackItem, TypeError> {
		if index >= self.stack.len() {
			return Err(TypeError::IndexOutOfBounds(format!(
				"There were only {} items left on the NeoVM stack after this execution.",
				self.stack.len()
			)));
		}
		Ok(&self.stack[index])
	}

	pub fn get_first_notification(&self) -> Result<&LogNotification, TypeError> {
		if self.notifications.is_empty() {
			return Err(TypeError::IndexOutOfBounds(
				"This execution did not send any notifications.".to_string(),
			));
		}
		self.get_notification(0)
	}

	pub fn get_notification(&self, index: usize) -> Result<&LogNotification, TypeError> {
		if index >= self.notifications.len() {
			return Err(TypeError::IndexOutOfBounds(format!(
				"This execution only sent {} notifications.",
				self.notifications.len()
			)));
		}
		Ok(&self.notifications[index])
	}
}

impl Default for Execution {
	fn default() -> Self {
		Self {
			trigger: "".to_string(),
			state: VMState::Halt,
			exception: None,
			gas_consumed: "0".to_string(),
			stack: vec![],
			notifications: vec![],
		}
	}
}
