use std::io;

use ciphersuite::Secp256k1;
use frost::dkg::ThresholdKeys;

use alloy_core::primitives::U256;

use serai_client::networks::ethereum::Address;

use scheduler::SignableTransaction;

use ethereum_primitives::keccak256;
use ethereum_schnorr::{PublicKey, Signature};
use ethereum_router::{Coin, OutInstructions, Executed, Router};

use crate::{output::OutputId, machine::ClonableTransctionMachine};

#[derive(Clone, PartialEq, Debug)]
pub(crate) enum Action {
  SetKey { nonce: u64, key: PublicKey },
  Batch { nonce: u64, coin: Coin, fee: U256, outs: Vec<(Address, U256)> },
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct Eventuality(pub(crate) Executed);

impl Action {
  pub(crate) fn nonce(&self) -> u64 {
    match self {
      Action::SetKey { nonce, .. } | Action::Batch { nonce, .. } => *nonce,
    }
  }

  pub(crate) fn message(&self) -> Vec<u8> {
    match self {
      Action::SetKey { nonce, key } => Router::update_serai_key_message(*nonce, key),
      Action::Batch { nonce, coin, fee, outs } => {
        Router::execute_message(*nonce, *coin, *fee, OutInstructions::from(outs.as_ref()))
      }
    }
  }

  pub(crate) fn eventuality(&self) -> Eventuality {
    Eventuality(match self {
      Self::SetKey { nonce, key } => Executed::SetKey { nonce: *nonce, key: key.eth_repr() },
      Self::Batch { nonce, .. } => {
        Executed::Batch { nonce: *nonce, message_hash: keccak256(self.message()) }
      }
    })
  }
}

#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Transaction(pub(crate) Action, pub(crate) Signature);
impl scheduler::Transaction for Transaction {
  fn read(reader: &mut impl io::Read) -> io::Result<Self> {
    let action = Action::read(reader)?;
    let signature = Signature::read(reader)?;
    Ok(Transaction(action, signature))
  }
  fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
    self.0.write(writer)?;
    self.1.write(writer)?;
    Ok(())
  }
}

impl SignableTransaction for Action {
  type Transaction = Transaction;
  type Ciphersuite = Secp256k1;
  type PreprocessMachine = ClonableTransctionMachine;

  fn read(reader: &mut impl io::Read) -> io::Result<Self> {
    let mut kind = [0xff];
    reader.read_exact(&mut kind)?;
    if kind[0] >= 2 {
      Err(io::Error::other("unrecognized Action type"))?;
    }

    let mut nonce = [0; 8];
    reader.read_exact(&mut nonce)?;
    let nonce = u64::from_le_bytes(nonce);

    Ok(match kind[0] {
      0 => {
        let mut key = [0; 32];
        reader.read_exact(&mut key)?;
        let key =
          PublicKey::from_eth_repr(key).ok_or_else(|| io::Error::other("invalid key in Action"))?;

        Action::SetKey { nonce, key }
      }
      1 => {
        let coin = Coin::read(reader)?;

        let mut fee = [0; 32];
        reader.read_exact(&mut fee)?;
        let fee = U256::from_le_bytes(fee);

        let mut outs_len = [0; 4];
        reader.read_exact(&mut outs_len)?;
        let outs_len = usize::try_from(u32::from_le_bytes(outs_len)).unwrap();

        let mut outs = vec![];
        for _ in 0 .. outs_len {
          let address = borsh::from_reader(reader)?;

          let mut amount = [0; 32];
          reader.read_exact(&mut amount)?;
          let amount = U256::from_le_bytes(amount);

          outs.push((address, amount));
        }
        Action::Batch { nonce, coin, fee, outs }
      }
      _ => unreachable!(),
    })
  }
  fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
    match self {
      Self::SetKey { nonce, key } => {
        writer.write_all(&[0])?;
        writer.write_all(&nonce.to_le_bytes())?;
        writer.write_all(&key.eth_repr())
      }
      Self::Batch { nonce, coin, fee, outs } => {
        writer.write_all(&[1])?;
        writer.write_all(&nonce.to_le_bytes())?;
        coin.write(writer)?;
        writer.write_all(&fee.as_le_bytes())?;
        writer.write_all(&u32::try_from(outs.len()).unwrap().to_le_bytes())?;
        for (address, amount) in outs {
          borsh::BorshSerialize::serialize(address, writer)?;
          writer.write_all(&amount.as_le_bytes())?;
        }
        Ok(())
      }
    }
  }

  fn id(&self) -> [u8; 32] {
    let mut res = [0; 32];
    res[.. 8].copy_from_slice(&self.nonce().to_le_bytes());
    res
  }

  fn sign(self, keys: ThresholdKeys<Self::Ciphersuite>) -> Self::PreprocessMachine {
    ClonableTransctionMachine { keys, action: self }
  }
}

impl primitives::Eventuality for Eventuality {
  type OutputId = OutputId;

  fn id(&self) -> [u8; 32] {
    let mut res = [0; 32];
    res[.. 8].copy_from_slice(&self.0.nonce().to_le_bytes());
    res
  }

  fn lookup(&self) -> Vec<u8> {
    self.0.nonce().to_le_bytes().to_vec()
  }

  fn singular_spent_output(&self) -> Option<Self::OutputId> {
    None
  }

  fn read(reader: &mut impl io::Read) -> io::Result<Self> {
    Executed::read(reader).map(Self)
  }
  fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
    self.0.write(writer)
  }
}
