use std::collections::BTreeMap;

use crate::{config::AppConfig, net::ServiceType};

pub trait NetService {
    fn app_id(&self) -> u32;
    fn get_service_type(&self) -> ServiceType;
    fn app_map(&self) -> &'static BTreeMap<ServiceType, Vec<AppConfig>>;

    fn get_app_config(&self, ty: ServiceType, app_id: u32) -> Option<AppConfig> {
        self.app_map()
            .get(&ty)?
            .iter()
            .find(|a| a.app_id == app_id)
            .cloned()
    }

    fn get_service_type_by_app_id(&self, app_id: u32) -> Option<ServiceType> {
        for (ty, apps) in self.app_map() {
            if apps.iter().any(|a| a.app_id == app_id) {
                return Some(*ty);
            }
        }

        None
    }
}

#[macro_export]
macro_rules! impl_net_service {
    ($name: ident) => {
        impl ::common::base::NetService for NetworkMgr {
            ::paste::paste! {
                fn app_id(&self) -> u32 {
                    crate::[<$name:lower _service>]().config.app_id
                }

                fn get_service_type(&self) -> ::common::net::ServiceType {
                    ::common::net::ServiceType::$name
                }

                fn app_map(&self) -> &'static ::std::collections::BTreeMap<::common::net::ServiceType, Vec<::common::config::AppConfig>> {
                    &[<$name:lower _service>]().config.applications
                }
            }
        }
    };
}

#[macro_export]
macro_rules! services {
    ($($name: ident;)*) => {
        async fn on_recv_to_svc(
            &self,
            packet: ::common::net::tools::Packet,
        ) -> Result<(), Box<dyn Error>> {
            let Some(app_type) = self.get_service_type_by_app_id(packet.head.sender_app_id) else {
                return Ok(());
            };

            paste! { match app_type {
                $(ServiceType::$name => self.[<on_recv_from_ $name:snake>](packet).await,)*
                _ => self.on_recv(packet).await,
            }}
        }

        paste! {
            $(
                async fn [<on_recv_from_ $name:snake>](
                    &self,
                    packet: ::common::net::tools::Packet,
                ) -> Result<(), Box<dyn Error>> {
                    let sessions = &self.[<$name:snake _sessions>];

                    if let Some(session) = sessions.get(&packet.head.sender_app_id) {
                        session.on_recv(packet).await
                    } else {
                        let Some(config) = self.get_app_config(ServiceType::$name, packet.head.sender_app_id) else {
                            tracing::warn!("config for app_id:{} not found", packet.head.sender_app_id);
                            return Ok(());
                        };
                        let session = [<$name Session>]::new(packet.head.sender_app_id, config.inner_ip.clone(), self.app_id()).await?;
                        let session = sessions
                            .insert(packet.head.sender_app_id, session);

                        sessions.get(&packet.head.sender_app_id).unwrap().on_recv(packet).await
                    }
                }
            )*
        }
    };
}
