// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Instructions for the REX Processor program.
//!
//! This module defines the instructions for communicating resulting data from REX requests.

use rialo_s_instruction::{AccountMeta, Instruction};
use rialo_s_program::system_program;
use rialo_s_pubkey::Pubkey;
use rialo_types::{Nonce, RexId};
use serde::{Deserialize, Serialize};

pub use crate::state::RexUpdate;

/// Static seed for rex update PDA derivation
pub const REX_REPORT_SEED: &[u8] = b"rex_report";

/// Static seed for payer PDA derivation
pub const REX_REPORT_PAYER_SEED: &[u8] = b"report_payer";

/// Default payer for rex updates - synchronized with `runtime/vm/svm-execution/src/bank.rs`
pub const REX_UPDATE_PAYER: Pubkey =
    Pubkey::from_str_const("Qrac1eUpdatePayer11111111111111111111111111");

/// Instructions supported by the REX Processor program. Made to report rex updates from
/// validators.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum RexProcessorInstruction {
    /// Report rex updates from validators
    Report {
        /// Unique identifier for the rex
        rex_id: RexId,
        /// Updates from each validator
        updates: Vec<RexUpdate>,
    },
}

impl RexProcessorInstruction {
    /// Creates an instruction for rex reporting.
    ///
    /// This function creates an instruction that will report an rex update and emit
    /// an event that subscribers can listen to.
    ///
    /// # Arguments
    ///
    /// * `payer` - The public key of the payer
    /// * `rex_id` - The ID of the rex
    /// * `updates` - A vector of rex updates
    ///
    /// # Returns
    ///
    /// An instruction that stores rex data in a PDA and emits an event.
    /// Subscribers can listen to the instance-specific topic to receive
    /// notifications when rex reports are stored.
    ///
    /// # Accounts
    ///
    /// * `[0]` - Payer account (signer, writable)
    /// * `[1]` - Report PDA account (writable)
    /// * `[2]` - Event PDA account (writable)
    /// * `[3]` - System program (readonly)
    pub fn report(payer: Pubkey, rex_id: RexId, updates: Vec<RexUpdate>) -> Instruction {
        use rialo_events_core::derive_event_address;

        use crate::event::RexReportEvent;

        // Derive the PDA for the report account
        let (report_key, _bump) = derive_report_address(&rex_id.creator, rex_id.nonce);

        // Derive the PDA for the event account using the new standardized method
        let event = RexReportEvent::new(rex_id);
        let (event_key, _bump) = derive_event_address(&event.instance_topic(), &crate::id());

        Instruction::new_with_bincode(
            crate::id(),
            &RexProcessorInstruction::Report { rex_id, updates },
            vec![
                AccountMeta::new(payer, true),
                AccountMeta::new(report_key, false),
                AccountMeta::new(event_key, false),
                AccountMeta::new_readonly(system_program::id(), false),
            ],
        )
    }
}

/// Derive the PDA for an rex report account
///
/// # Arguments
///
/// * `creator` - The creator of the rex
/// * `nonce` - The nonce of the rex
///
/// # Returns
///
/// A tuple containing the PDA and the bump seed
pub fn derive_report_address<NONCE: Into<Nonce>>(creator: &Pubkey, nonce: NONCE) -> (Pubkey, u8) {
    let nonce: Nonce = nonce.into();
    Pubkey::find_program_address(
        &[REX_REPORT_SEED, &creator.to_bytes(), nonce.as_bytes()],
        &crate::id(),
    )
}

#[cfg(test)]
mod tests {
    use rialo_events_core::RialoEvent;
    use rialo_s_pubkey::Pubkey;

    use super::*;

    #[derive(Default, RialoEvent)]
    struct TestEvent {
        name: String,
        value: u64,
    }

    #[test]
    fn test_report_rex_updates() {
        use rialo_events_core::derive_event_address;

        use crate::event::RexReportEvent;

        let authority_key = [0; 96];
        let payer = Pubkey::new_unique();
        let data = vec![0, 1, 2, 3];

        let updates = vec![RexUpdate::new(data, authority_key)];
        let nonce = Nonce::from(b"test-rex-42");
        let rex_id = RexId::new(payer, nonce);

        // Create the instruction
        let instruction = RexProcessorInstruction::report(payer, rex_id, updates.clone());

        // Derive event PDA using the new standardized method
        let event = RexReportEvent::new(rex_id);
        let (expected_event_key, _) = derive_event_address(&event.instance_topic(), &crate::id());

        // Check the report instruction
        let expected_accounts = vec![
            AccountMeta::new(payer, true),
            AccountMeta::new(
                derive_report_address(&rex_id.creator, rex_id.nonce).0,
                false,
            ),
            AccountMeta::new(expected_event_key, false),
            AccountMeta::new_readonly(system_program::id(), false),
        ];

        // Verify the instruction data
        let expected_data = bincode::serialize(&RexProcessorInstruction::Report {
            rex_id,
            updates: updates.clone(),
        })
        .unwrap();

        assert_eq!(instruction.data, expected_data);
        assert_eq!(instruction.accounts, expected_accounts);
        assert_eq!(instruction.program_id, crate::id());
    }

    #[test]
    fn test_report_address_derivation() {
        let creator = Pubkey::new_unique();
        let (report_pubkey1, _) = derive_report_address(&creator, b"test-rex-a42");
        let (report_pubkey2, _) = derive_report_address(&creator, b"test-rex-43");
        assert_ne!(report_pubkey1, report_pubkey2);
    }
}
