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

//! Simple integration tests to verify program functionality compiles and works.

#[cfg(test)]
mod tests {
    use rialo_s_sdk::signature::{Keypair, Signer};

    use crate::{
        program::{
            FileProgramDataSource, ProgramDeployment, ProgramDeploymentBuilder, ProgramInvocation,
            ProgramInvocationBuilder,
        },
        rpc::types::Pubkey,
    };

    #[test]
    fn test_program_deployment_creation() {
        let deployment = ProgramDeployment::new(FileProgramDataSource::from("/path/to/program.so"));
        assert_eq!(
            deployment.data_source_description(),
            "file:/path/to/program.so"
        );
        assert!(deployment.program_keypair.pubkey() != Pubkey::default());
        assert!(deployment.authority.is_none());
    }

    #[test]
    fn test_program_deployment_builder() {
        let program_keypair = Keypair::new();
        let program_id = program_keypair.pubkey();
        let authority = Pubkey::new_unique();

        let deployment: ProgramDeployment<FileProgramDataSource> =
            ProgramDeploymentBuilder::new(FileProgramDataSource::from("/path/to/program.so"))
                .with_program_keypair(program_keypair)
                .with_authority(authority)
                .build();

        assert_eq!(
            deployment.data_source_description(),
            "file:/path/to/program.so"
        );
        assert_eq!(deployment.program_keypair.pubkey(), program_id);
        assert_eq!(deployment.authority, Some(authority));
    }

    #[test]
    fn test_program_invocation_creation() {
        let program_id = Pubkey::new_unique();
        let invocation = ProgramInvocation::new(program_id);

        assert_eq!(invocation.program_id, program_id);
        assert!(invocation.accounts.is_empty());
        assert!(invocation.data.is_empty());
    }

    #[test]
    fn test_program_invocation_add_accounts() {
        let program_id = Pubkey::new_unique();
        let account1 = Pubkey::new_unique();
        let account2 = Pubkey::new_unique();
        let account3 = Pubkey::new_unique();

        let invocation = ProgramInvocation::new(program_id)
            .add_readonly_account(account1)
            .add_writable_account(account2)
            .add_signer_account(account3, true);

        assert_eq!(invocation.accounts.len(), 3);

        // Check first account (readonly)
        assert_eq!(invocation.accounts[0].pubkey, account1);
        assert!(!invocation.accounts[0].is_signer);
        assert!(!invocation.accounts[0].is_writable);

        // Check second account (writable)
        assert_eq!(invocation.accounts[1].pubkey, account2);
        assert!(!invocation.accounts[1].is_signer);
        assert!(invocation.accounts[1].is_writable);

        // Check third account (signer + writable)
        assert_eq!(invocation.accounts[2].pubkey, account3);
        assert!(invocation.accounts[2].is_signer);
        assert!(invocation.accounts[2].is_writable);
    }

    #[test]
    fn test_program_invocation_with_data() {
        let program_id = Pubkey::new_unique();
        let test_data = vec![1, 2, 3, 4, 5];

        let invocation = ProgramInvocation::new(program_id).with_data(test_data.clone());

        assert_eq!(invocation.data, test_data);
    }

    #[test]
    fn test_program_invocation_builder() {
        let program_id = Pubkey::new_unique();
        let account = Pubkey::new_unique();
        let test_data = vec![1, 2, 3];

        let invocation = ProgramInvocationBuilder::new(program_id)
            .add_signer_account(account, true)
            .with_data(test_data.clone())
            .build();

        assert_eq!(invocation.program_id, program_id);
        assert_eq!(invocation.accounts.len(), 1);
        assert_eq!(invocation.accounts[0].pubkey, account);
        assert!(invocation.accounts[0].is_signer);
        assert!(invocation.accounts[0].is_writable);
        assert_eq!(invocation.data, test_data);
    }

    #[test]
    fn test_program_invocation_helper_functions() {
        let program_id = Pubkey::new_unique();
        let from = Pubkey::new_unique();
        let to = Pubkey::new_unique();
        let data = vec![1, 2, 3];

        // Test transfer_like helper
        let transfer_invocation =
            ProgramInvocation::transfer_like(program_id, from, to, data.clone());

        assert_eq!(transfer_invocation.program_id, program_id);
        assert_eq!(transfer_invocation.accounts.len(), 2);
        assert_eq!(transfer_invocation.accounts[0].pubkey, from);
        assert!(transfer_invocation.accounts[0].is_signer);
        assert!(transfer_invocation.accounts[0].is_writable);
        assert_eq!(transfer_invocation.accounts[1].pubkey, to);
        assert!(!transfer_invocation.accounts[1].is_signer);
        assert!(transfer_invocation.accounts[1].is_writable);
        assert_eq!(transfer_invocation.data, data);

        // Test initialize helper
        let initializer = Pubkey::new_unique();
        let account_to_init = Pubkey::new_unique();
        let init_invocation =
            ProgramInvocation::initialize(program_id, initializer, account_to_init, data.clone());

        assert_eq!(init_invocation.program_id, program_id);
        assert_eq!(init_invocation.accounts.len(), 2);
        assert_eq!(init_invocation.accounts[0].pubkey, initializer);
        assert!(init_invocation.accounts[0].is_signer);
        assert!(init_invocation.accounts[0].is_writable);
        assert_eq!(init_invocation.accounts[1].pubkey, account_to_init);
        assert!(!init_invocation.accounts[1].is_signer);
        assert!(init_invocation.accounts[1].is_writable);

        // Test close_account helper
        let authority = Pubkey::new_unique();
        let account_to_close = Pubkey::new_unique();
        let rent_recipient = Pubkey::new_unique();
        let close_invocation = ProgramInvocation::close_account(
            program_id,
            authority,
            account_to_close,
            rent_recipient,
        );

        assert_eq!(close_invocation.program_id, program_id);
        assert_eq!(close_invocation.accounts.len(), 3);
        assert_eq!(close_invocation.accounts[0].pubkey, authority);
        assert!(close_invocation.accounts[0].is_signer);
        assert!(!close_invocation.accounts[0].is_writable);
        assert_eq!(close_invocation.accounts[1].pubkey, account_to_close);
        assert!(close_invocation.accounts[1].is_writable);
        assert_eq!(close_invocation.accounts[2].pubkey, rent_recipient);
        assert!(close_invocation.accounts[2].is_writable);
        assert!(close_invocation.data.is_empty());
    }

    #[test]
    fn test_instruction_conversion() {
        let program_id = Pubkey::new_unique();
        let account = Pubkey::new_unique();
        let data = vec![1, 2, 3, 4];

        let invocation = ProgramInvocation::new(program_id)
            .add_signer_account(account, true)
            .with_data(data.clone());

        let instruction = invocation.into_instruction();

        assert_eq!(instruction.program_id, program_id);
        assert_eq!(instruction.accounts.len(), 1);
        assert_eq!(instruction.accounts[0].pubkey, account);
        assert!(instruction.accounts[0].is_signer);
        assert!(instruction.accounts[0].is_writable);
        assert_eq!(instruction.data, data);
    }

    // Feature-gated tests
    #[cfg(feature = "borsh")]
    #[test]
    fn test_program_invocation_with_borsh_data() {
        use borsh::{BorshDeserialize, BorshSerialize};

        #[derive(PartialEq, Debug)]
        struct TestInstruction {
            value: u64,
            name: String,
        }

        impl BorshSerialize for TestInstruction {
            fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
                self.value.serialize(writer)?;
                self.name.serialize(writer)
            }
        }

        impl BorshDeserialize for TestInstruction {
            fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
                Ok(Self {
                    value: BorshDeserialize::deserialize_reader(reader)?,
                    name: BorshDeserialize::deserialize_reader(reader)?,
                })
            }
        }

        let program_id = Pubkey::new_unique();
        let test_instruction = TestInstruction {
            value: 42,
            name: "test".to_string(),
        };

        let invocation = ProgramInvocation::new(program_id)
            .with_borsh_data(&test_instruction)
            .expect("Failed to serialize with borsh");

        // Verify the data was serialized correctly
        let deserialized: TestInstruction =
            TestInstruction::try_from_slice(&invocation.data).expect("Failed to deserialize");
        assert_eq!(deserialized, test_instruction);
    }
}
