use rand::RngCore;

use monero_serai::{
  transaction::Transaction,
  wallet::{address::SubaddressIndex, extra::PaymentId},
};

mod runner;

test!(
  scan_standard_address,
  (
    |_, mut builder: Builder, _| async move {
      let view = runner::random_address().1;
      let scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
      builder.add_payment(view.address(Network::Mainnet, AddressSpec::Standard), 5);
      (builder.build().unwrap(), scanner)
    },
    |_, tx: Transaction, _, mut state: Scanner| async move {
      let output = state.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      let dummy_payment_id = PaymentId::Encrypted([0u8; 8]);
      assert_eq!(output.metadata.payment_id, Some(dummy_payment_id));
    },
  ),
);

test!(
  scan_subaddress,
  (
    |_, mut builder: Builder, _| async move {
      let subaddress = SubaddressIndex::new(0, 1).unwrap();

      let view = runner::random_address().1;
      let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
      scanner.register_subaddress(subaddress);

      builder.add_payment(view.address(Network::Mainnet, AddressSpec::Subaddress(subaddress)), 5);
      (builder.build().unwrap(), (scanner, subaddress))
    },
    |_, tx: Transaction, _, mut state: (Scanner, SubaddressIndex)| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.subaddress, Some(state.1));
    },
  ),
);

test!(
  scan_integrated_address,
  (
    |_, mut builder: Builder, _| async move {
      let view = runner::random_address().1;
      let scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));

      let mut payment_id = [0u8; 8];
      OsRng.fill_bytes(&mut payment_id);

      builder.add_payment(view.address(Network::Mainnet, AddressSpec::Integrated(payment_id)), 5);
      (builder.build().unwrap(), (scanner, payment_id))
    },
    |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(state.1)));
    },
  ),
);

test!(
  scan_featured_standard,
  (
    |_, mut builder: Builder, _| async move {
      let view = runner::random_address().1;
      let scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured { subaddress: None, payment_id: None, guaranteed: false },
        ),
        5,
      );
      (builder.build().unwrap(), scanner)
    },
    |_, tx: Transaction, _, mut state: Scanner| async move {
      let output = state.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
    },
  ),
);

test!(
  scan_featured_subaddress,
  (
    |_, mut builder: Builder, _| async move {
      let subaddress = SubaddressIndex::new(0, 2).unwrap();

      let view = runner::random_address().1;
      let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
      scanner.register_subaddress(subaddress);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured {
            subaddress: Some(subaddress),
            payment_id: None,
            guaranteed: false,
          },
        ),
        5,
      );
      (builder.build().unwrap(), (scanner, subaddress))
    },
    |_, tx: Transaction, _, mut state: (Scanner, SubaddressIndex)| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.subaddress, Some(state.1));
    },
  ),
);

test!(
  scan_featured_integrated,
  (
    |_, mut builder: Builder, _| async move {
      let view = runner::random_address().1;
      let scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
      let mut payment_id = [0u8; 8];
      OsRng.fill_bytes(&mut payment_id);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured {
            subaddress: None,
            payment_id: Some(payment_id),
            guaranteed: false,
          },
        ),
        5,
      );
      (builder.build().unwrap(), (scanner, payment_id))
    },
    |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(state.1)));
    },
  ),
);

test!(
  scan_featured_integrated_subaddress,
  (
    |_, mut builder: Builder, _| async move {
      let subaddress = SubaddressIndex::new(0, 3).unwrap();

      let view = runner::random_address().1;
      let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
      scanner.register_subaddress(subaddress);

      let mut payment_id = [0u8; 8];
      OsRng.fill_bytes(&mut payment_id);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured {
            subaddress: Some(subaddress),
            payment_id: Some(payment_id),
            guaranteed: false,
          },
        ),
        5,
      );
      (builder.build().unwrap(), (scanner, payment_id, subaddress))
    },
    |_, tx: Transaction, _, mut state: (Scanner, [u8; 8], SubaddressIndex)| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(state.1)));
      assert_eq!(output.metadata.subaddress, Some(state.2));
    },
  ),
);

test!(
  scan_guaranteed_standard,
  (
    |_, mut builder: Builder, _| async move {
      let view = runner::random_address().1;
      let scanner = Scanner::from_view(view.clone(), None);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured { subaddress: None, payment_id: None, guaranteed: true },
        ),
        5,
      );
      (builder.build().unwrap(), scanner)
    },
    |_, tx: Transaction, _, mut state: Scanner| async move {
      let output = state.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
    },
  ),
);

test!(
  scan_guaranteed_subaddress,
  (
    |_, mut builder: Builder, _| async move {
      let subaddress = SubaddressIndex::new(1, 0).unwrap();

      let view = runner::random_address().1;
      let mut scanner = Scanner::from_view(view.clone(), None);
      scanner.register_subaddress(subaddress);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured {
            subaddress: Some(subaddress),
            payment_id: None,
            guaranteed: true,
          },
        ),
        5,
      );
      (builder.build().unwrap(), (scanner, subaddress))
    },
    |_, tx: Transaction, _, mut state: (Scanner, SubaddressIndex)| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.subaddress, Some(state.1));
    },
  ),
);

test!(
  scan_guaranteed_integrated,
  (
    |_, mut builder: Builder, _| async move {
      let view = runner::random_address().1;
      let scanner = Scanner::from_view(view.clone(), None);
      let mut payment_id = [0u8; 8];
      OsRng.fill_bytes(&mut payment_id);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured {
            subaddress: None,
            payment_id: Some(payment_id),
            guaranteed: true,
          },
        ),
        5,
      );
      (builder.build().unwrap(), (scanner, payment_id))
    },
    |_, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(state.1)));
    },
  ),
);

test!(
  scan_guaranteed_integrated_subaddress,
  (
    |_, mut builder: Builder, _| async move {
      let subaddress = SubaddressIndex::new(1, 1).unwrap();

      let view = runner::random_address().1;
      let mut scanner = Scanner::from_view(view.clone(), None);
      scanner.register_subaddress(subaddress);

      let mut payment_id = [0u8; 8];
      OsRng.fill_bytes(&mut payment_id);

      builder.add_payment(
        view.address(
          Network::Mainnet,
          AddressSpec::Featured {
            subaddress: Some(subaddress),
            payment_id: Some(payment_id),
            guaranteed: true,
          },
        ),
        5,
      );
      (builder.build().unwrap(), (scanner, payment_id, subaddress))
    },
    |_, tx: Transaction, _, mut state: (Scanner, [u8; 8], SubaddressIndex)| async move {
      let output = state.0.scan_transaction(&tx).not_locked().swap_remove(0);
      assert_eq!(output.commitment().amount, 5);
      assert_eq!(output.metadata.payment_id, Some(PaymentId::Encrypted(state.1)));
      assert_eq!(output.metadata.subaddress, Some(state.2));
    },
  ),
);