use core::fmt::Debug;

use soroban_sdk::{Env, Val, Vec};
#[cfg(any(test, feature = "testutils"))]
pub use testutils::*;

pub trait Event: Debug + PartialEq + Sized {
    fn emit(self, env: &Env);

    fn from_event(env: &Env, topics: Vec<Val>, data: Val) -> Self;

    fn schema(env: &Env) -> &'static str;
}

#[cfg(any(test, feature = "testutils"))]
mod testutils {
    use soroban_sdk::{xdr, Address, Env, TryFromVal, Val, Vec};

    use crate::events::Event;
    use crate::testutils::Events;

    pub fn fmt_last_emitted_event<E>(env: &Env) -> std::string::String
    where
        E: Event,
    {
        fmt_emitted_event_at_idx::<E>(env, -1)
    }

    pub fn fmt_emitted_event_at_idx<E>(env: &Env, mut idx: i32) -> std::string::String
    where
        E: Event,
    {
        let events = env.events().all();
        let event_slice = events.events();

        if idx < 0 {
            idx += event_slice.len() as i32;
        }

        let event = &event_slice[idx as usize];

        let contract_id = event
            .contract_id
            .as_ref()
            .map(|id| {
                let sc_val = xdr::ScVal::Address(xdr::ScAddress::Contract(id.clone()));
                Address::try_from_val(env, &sc_val).expect("failed to parse contract_id")
            })
            .expect("event has no contract_id");

        let xdr::ContractEventBody::V0(body) = &event.body;

        let topics: Vec<Val> =
            Vec::try_from_val(env, &xdr::ScVal::Vec(Some(body.topics.clone().into())))
                .expect("failed to convert topics");
        let data: Val = Val::try_from_val(env, &body.data).expect("failed to convert data");

        let parsed = E::from_event(env, topics, data);
        std::format!("{:#?}\n\n{:?}\n\n{}", parsed, contract_id, E::schema(env))
    }
}

#[cfg(test)]
mod test {
    use core::fmt::Debug;

    use stellar_axelar_std::events::Event;
    use stellar_axelar_std::xdr::Int32;
    use stellar_axelar_std::{contract, BytesN, Env, String, Symbol};
    use stellar_axelar_std_derive::{contractimpl, IntoEvent};

    use crate as stellar_axelar_std;
    use crate::events::fmt_last_emitted_event;

    #[derive(Debug, PartialEq, Eq, IntoEvent)]
    struct TestEvent {
        topic1: Symbol,
        topic2: String,
        topic3: Int32,
        #[data]
        data1: String,
        #[data]
        data2: BytesN<32>,
    }

    #[contract]
    struct Contract;

    #[contractimpl]
    impl Contract {
        pub fn test(env: &Env) {
            TestEvent {
                topic1: Symbol::new(env, "topic1"),
                topic2: String::from_str(env, "topic2"),
                topic3: 10,
                data1: String::from_str(env, "data1"),
                data2: BytesN::from_array(env, &[3; 32]),
            }
            .emit(env);
        }
    }

    #[test]
    fn format_last_emitted_event() {
        let env = Env::default();

        let contract = env.register(Contract, ());
        let client = ContractClient::new(&env, &contract);

        client.test();
        goldie::assert!(fmt_last_emitted_event::<TestEvent>(&env));
    }
}
