// Copyright AGNTCY Contributors (https://github.com/agntcy)
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;

use serde::Deserialize;

use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
use slim_config::auth::identity::{IdentityProviderConfig, IdentityVerifierConfig};
use slim_config::component::configuration::Configuration;
use slim_config::component::id::ID;
use slim_config::grpc::client::ClientConfig;
use slim_config::grpc::server::ServerConfig;
use slim_datapath::message_processing::MessageProcessor;

use crate::errors::ControllerError;
use crate::service::{ControlPlane, ControlPlaneSettings, from_server_config};

/// Configuration for the Control-Plane / Data-Plane component
#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Config {
    /// Controller GRPC server settings
    #[serde(default)]
    pub servers: Vec<ServerConfig>,

    /// Controller client config to connect to control plane
    #[serde(default)]
    pub clients: Vec<ClientConfig>,

    /// Token provider authentication configuration
    #[serde(default)]
    pub token_provider: IdentityProviderConfig,

    /// Token verifier authentication configuration
    #[serde(default)]
    pub token_verifier: IdentityVerifierConfig,

    /// How long to keep routing state after a server-side connection drops,
    /// waiting for the peer to reconnect before notifying the control plane.
    /// Accepts duration strings like "30s", "1s", "500ms".  Defaults to 30 s.
    #[serde(default)]
    pub recovery_ttl: Option<duration_string::DurationString>,
}

impl Config {
    /// Create a new Config instance with default values
    pub fn new() -> Self {
        Self::default()
    }

    pub fn is_default(&self) -> bool {
        self == &Self::default()
    }

    /// Create a new Config instance with the given servers
    pub fn with_servers(self, servers: Vec<ServerConfig>) -> Self {
        Self { servers, ..self }
    }

    /// Create a new Config instance with the given clients
    pub fn with_clients(self, clients: Vec<ClientConfig>) -> Self {
        Self { clients, ..self }
    }

    /// Set the token provider authentication configuration
    pub fn with_token_provider_auth(self, auth: IdentityProviderConfig) -> Self {
        Self {
            token_provider: auth,
            ..self
        }
    }

    /// Set the token verifier authentication configuration
    pub fn with_token_verifier_auth(self, auth: IdentityVerifierConfig) -> Self {
        Self {
            token_verifier: auth,
            ..self
        }
    }

    /// Get the list of server configurations
    pub fn servers(&self) -> &[ServerConfig] {
        &self.servers
    }

    /// Get the list of client configurations
    pub fn clients(&self) -> &[ClientConfig] {
        &self.clients
    }

    fn get_token_provider_auth(&self) -> Option<AuthProvider> {
        match &self.token_provider {
            IdentityProviderConfig::SharedSecret { id, data } => {
                AuthProvider::shared_secret_from_str(id, data).ok()
            }
            IdentityProviderConfig::StaticJwt(static_jwt_config) => {
                let provider = static_jwt_config
                    .build_static_token_provider()
                    .expect("Failed to build StaticTokenProvider");
                Some(AuthProvider::static_token(provider))
            }
            IdentityProviderConfig::Jwt(jwt_config) => {
                let provider = jwt_config
                    .get_provider()
                    .expect("Failed to build JwtTokenProvider");
                Some(AuthProvider::jwt_signer(provider))
            }
            #[cfg(not(target_family = "windows"))]
            IdentityProviderConfig::Spire(spire_config) => {
                let manager = spire_config
                    .create_provider()
                    .expect("Failed to build SpireIdentityManager");
                Some(AuthProvider::spire(manager))
            }
            IdentityProviderConfig::None => None,
        }
    }

    fn get_token_verifier_auth(&self) -> Option<AuthVerifier> {
        match &self.token_verifier {
            IdentityVerifierConfig::SharedSecret { id, data } => {
                AuthVerifier::shared_secret_from_str(id, data).ok()
            }
            IdentityVerifierConfig::Jwt(jwt_config) => {
                let verifier = jwt_config
                    .get_verifier()
                    .expect("Failed to build JwtTokenVerifier");
                Some(AuthVerifier::jwt_verifier(verifier))
            }
            #[cfg(not(target_family = "windows"))]
            IdentityVerifierConfig::Spire(spire_config) => {
                let manager = spire_config
                    .create_provider()
                    .expect("Failed to build SpireIdentityManager");
                Some(AuthVerifier::spire(manager))
            }
            IdentityVerifierConfig::None => None,
        }
    }

    /// Create a ControlPlane service instance from this configuration
    pub fn into_service(
        &self,
        id: ID,
        group_name: Option<String>,
        message_processor: Arc<MessageProcessor>,
        // List of server configurations for the dataplane services.
        // Used to extract connection type information required to connect to the node
        // (e.g., TLS settings). This information is used by the control plane.
        dataplane_servers: &[ServerConfig],
    ) -> ControlPlane {
        let auth_provider = self.get_token_provider_auth();
        let auth_verifier = self.get_token_verifier_auth();

        let connection_details = dataplane_servers.iter().map(from_server_config).collect();

        ControlPlane::new(ControlPlaneSettings {
            id,
            group_name,
            servers: self.servers.clone(),
            clients: self.clients.clone(),
            message_processor,
            auth_provider,
            auth_verifier,
            connection_details,
        })
    }
}

impl Configuration for Config {
    type Error = ControllerError;

    fn validate(&self) -> Result<(), Self::Error> {
        // Validate client and server configurations
        for server in self.servers.iter() {
            server.validate()?;
        }

        for client in &self.clients {
            client.validate()?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use slim_config::auth::jwt::Config as JwtConfig;
    use slim_config::auth::static_jwt::Config as StaticJwtConfig;
    use slim_config::component::id::{ID, Kind};
    use slim_config::grpc::client::ClientConfig;
    use slim_config::grpc::server::ServerConfig;
    use slim_datapath::message_processing::MessageProcessor;
    use slim_testing::utils::TEST_VALID_SECRET;
    use std::sync::Arc;

    fn create_test_server_config() -> ServerConfig {
        ServerConfig::with_endpoint("127.0.0.1:50051")
            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
    }

    fn create_test_client_config() -> ClientConfig {
        ClientConfig::with_endpoint("http://127.0.0.1:50051")
            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
    }

    #[test]
    fn test_config_new() {
        let config = Config::new();
        assert!(config.servers.is_empty());
        assert!(config.clients.is_empty());
        assert_eq!(config.token_provider, IdentityProviderConfig::None);
        assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
    }

    #[test]
    fn test_config_default() {
        let config = Config::default();
        assert!(config.servers.is_empty());
        assert!(config.clients.is_empty());
        assert_eq!(config.token_provider, IdentityProviderConfig::None);
        assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
    }

    #[test]
    fn test_config_with_servers() {
        let server_config = create_test_server_config();
        let config = Config::new().with_servers(vec![server_config.clone()]);

        assert_eq!(config.servers.len(), 1);
        assert_eq!(config.servers[0], server_config);
        assert!(config.clients.is_empty());
    }

    #[test]
    fn test_config_with_clients() {
        let client_config = create_test_client_config();
        let config = Config::new().with_clients(vec![client_config.clone()]);

        assert_eq!(config.clients.len(), 1);
        assert_eq!(config.clients[0], client_config);
        assert!(config.servers.is_empty());
    }

    #[test]
    fn test_config_with_token_provider_auth_shared_secret() {
        let auth = IdentityProviderConfig::SharedSecret {
            id: "test-provider".to_string(),
            data: "test-secret".to_string(),
        };
        let config = Config::new().with_token_provider_auth(auth.clone());

        assert_eq!(config.token_provider, auth);
        assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
    }

    #[test]
    fn test_config_with_token_provider_auth_static_jwt() {
        let static_jwt_config = StaticJwtConfig::with_file("test-key".to_string());
        let auth = IdentityProviderConfig::StaticJwt(static_jwt_config);
        let config = Config::new().with_token_provider_auth(auth.clone());

        assert_eq!(config.token_provider, auth);
    }

    #[test]
    fn test_config_with_token_provider_auth_jwt() {
        use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
        use slim_config::auth::jwt::{Claims, JwtKey};
        use std::time::Duration;

        let claims = Claims::default();
        let duration = Duration::from_secs(3600);
        let key = JwtKey::Encoding(Key {
            algorithm: Algorithm::HS256,
            format: KeyFormat::Pem,
            key: KeyData::Data("test-secret".to_string()),
        });
        let jwt_config = JwtConfig::new(claims, duration, key);
        let auth = IdentityProviderConfig::Jwt(jwt_config);
        let config = Config::new().with_token_provider_auth(auth.clone());

        assert_eq!(config.token_provider, auth);
    }

    #[test]
    fn test_config_with_token_verifier_auth_shared_secret() {
        let auth = IdentityVerifierConfig::SharedSecret {
            id: "test-verifier".to_string(),
            data: "test-secret".to_string(),
        };
        let config = Config::new().with_token_verifier_auth(auth.clone());

        assert_eq!(config.token_verifier, auth);
        assert_eq!(config.token_provider, IdentityProviderConfig::None);
    }

    #[test]
    fn test_config_with_token_verifier_auth_jwt() {
        use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
        use slim_config::auth::jwt::{Claims, JwtKey};
        use std::time::Duration;

        let claims = Claims::default();
        let duration = Duration::from_secs(3600);
        let key = JwtKey::Decoding(Key {
            algorithm: Algorithm::HS256,
            format: KeyFormat::Pem,
            key: KeyData::Data("test-secret".to_string()),
        });
        let jwt_config = JwtConfig::new(claims, duration, key);
        let auth = IdentityVerifierConfig::Jwt(jwt_config);
        let config = Config::new().with_token_verifier_auth(auth.clone());

        assert_eq!(config.token_verifier, auth);
    }

    #[test]
    fn test_config_servers_getter() {
        let server_config = create_test_server_config();
        let config = Config::new().with_servers(vec![server_config.clone()]);

        let servers = config.servers();
        assert_eq!(servers.len(), 1);
        assert_eq!(servers[0], server_config);
    }

    #[test]
    fn test_config_clients_getter() {
        let client_config = create_test_client_config();
        let config = Config::new().with_clients(vec![client_config.clone()]);

        let clients = config.clients();
        assert_eq!(clients.len(), 1);
        assert_eq!(clients[0], client_config);
    }

    #[test]
    fn test_config_chaining() {
        let server_config = create_test_server_config();
        let client_config = create_test_client_config();
        let provider_auth = IdentityProviderConfig::SharedSecret {
            id: "test-provider".to_string(),
            data: "provider-secret".to_string(),
        };
        let verifier_auth = IdentityVerifierConfig::SharedSecret {
            id: "test-verifier".to_string(),
            data: "verifier-secret".to_string(),
        };

        let config = Config::new()
            .with_servers(vec![server_config.clone()])
            .with_clients(vec![client_config.clone()])
            .with_token_provider_auth(provider_auth.clone())
            .with_token_verifier_auth(verifier_auth.clone());

        assert_eq!(config.servers.len(), 1);
        assert_eq!(config.clients.len(), 1);
        assert_eq!(config.token_provider, provider_auth);
        assert_eq!(config.token_verifier, verifier_auth);
    }

    #[test]
    fn test_config_validate_empty() {
        let config = Config::new();
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_config_validate_with_valid_servers_and_clients() {
        let server_config = create_test_server_config();
        let client_config = create_test_client_config();
        let config = Config::new()
            .with_servers(vec![server_config])
            .with_clients(vec![client_config]);

        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_token_provider_auth_config_equality() {
        let secret1 = IdentityProviderConfig::SharedSecret {
            id: "test-id".to_string(),
            data: "secret0".to_string(),
        };
        let secret2 = IdentityProviderConfig::SharedSecret {
            id: "test-id".to_string(),
            data: "secret0".to_string(),
        };
        let secret3 = IdentityProviderConfig::SharedSecret {
            id: "test-id".to_string(),
            data: "secret2".to_string(),
        };

        assert_eq!(secret1, secret2);
        assert_ne!(secret1, secret3);
    }

    #[test]
    fn test_token_verifier_auth_config_equality() {
        let secret1 = IdentityVerifierConfig::SharedSecret {
            id: "test-id".to_string(),
            data: "secret0".to_string(),
        };
        let secret2 = IdentityVerifierConfig::SharedSecret {
            id: "test-id".to_string(),
            data: "secret0".to_string(),
        };
        let secret3 = IdentityVerifierConfig::SharedSecret {
            id: "test-id".to_string(),
            data: "secret2".to_string(),
        };

        assert_eq!(secret1, secret2);
        assert_ne!(secret1, secret3);
    }

    #[test]
    fn test_config_clone() {
        let server_config = create_test_server_config();
        let client_config = create_test_client_config();
        let auth = IdentityProviderConfig::SharedSecret {
            id: "test-provider".to_string(),
            data: "secret0".to_string(),
        };

        let config1 = Config::new()
            .with_servers(vec![server_config])
            .with_clients(vec![client_config])
            .with_token_provider_auth(auth);

        let config2 = config1.clone();

        assert_eq!(config1.servers, config2.servers);
        assert_eq!(config1.clients, config2.clients);
        assert_eq!(config1.token_provider, config2.token_provider);
        assert_eq!(config1.token_verifier, config2.token_verifier);
    }

    #[tokio::test]
    async fn test_config_into_service() {
        let server_config = create_test_server_config();
        let client_config = create_test_client_config();
        let auth = IdentityProviderConfig::SharedSecret {
            id: "test-provider".to_string(),
            data: TEST_VALID_SECRET.to_string(),
        };

        let config = Config::new()
            .with_servers(vec![server_config.clone()])
            .with_clients(vec![client_config])
            .with_token_provider_auth(auth);

        let id = ID::new_with_name(Kind::new("slim").unwrap(), "test-instance").unwrap();
        let group_name = Some("test-group".to_string());
        let message_processor = Arc::new(MessageProcessor::new());

        let _control_plane =
            config.into_service(id, group_name, message_processor, &[server_config]);
    }

    #[test]
    fn test_config_debug_trait() {
        let config = Config::new();
        let debug_str = format!("{:?}", config);
        assert!(debug_str.contains("Config"));
        assert!(debug_str.contains("servers"));
        assert!(debug_str.contains("clients"));
    }

    mod serde_tests {
        use super::*;
        use serde_json;

        #[test]
        fn test_token_provider_auth_config_serialize_shared_secret() {
            let auth = IdentityProviderConfig::SharedSecret {
                id: "test-provider".to_string(),
                data: "test-secret".to_string(),
            };
            let json = serde_json::to_string(&auth).unwrap();
            assert!(json.contains("shared_secret"));
            assert!(json.contains("test-secret"));
        }

        #[test]
        fn test_token_provider_auth_config_deserialize_shared_secret() {
            let json = r#"{"type": "shared_secret", "id": "test-provider", "data": "test-secret"}"#;
            let auth: IdentityProviderConfig = serde_json::from_str(json).unwrap();

            match auth {
                IdentityProviderConfig::SharedSecret { id, data } => {
                    assert_eq!(id, "test-provider");
                    assert_eq!(data, "test-secret");
                }
                _ => panic!("Expected SharedSecret variant"),
            }
        }

        #[test]
        fn test_config_validate_with_multiple_servers() {
            let server1 = create_test_server_config();
            let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
                .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());

            let config = Config::new().with_servers(vec![server1, server2]);
            assert!(config.validate().is_ok());
        }

        #[test]
        fn test_config_validate_with_multiple_clients() {
            let client1 = create_test_client_config();
            let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
                .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());

            let config = Config::new().with_clients(vec![client1, client2]);
            assert!(config.validate().is_ok());
        }

        #[test]
        fn test_config_with_all_auth_combinations() {
            let provider_auth = IdentityProviderConfig::SharedSecret {
                id: "test-provider".to_string(),
                data: "provider-secret".to_string(),
            };
            let verifier_auth = IdentityVerifierConfig::SharedSecret {
                id: "test-verifier".to_string(),
                data: "verifier-secret".to_string(),
            };

            let config = Config::new()
                .with_token_provider_auth(provider_auth.clone())
                .with_token_verifier_auth(verifier_auth.clone());

            assert_eq!(config.token_provider, provider_auth);
            assert_eq!(config.token_verifier, verifier_auth);
        }

        #[test]
        fn test_empty_servers_slice() {
            let config = Config::new();
            let servers = config.servers();
            assert!(servers.is_empty());
            assert_eq!(servers.len(), 0);
        }

        #[test]
        fn test_empty_clients_slice() {
            let config = Config::new();
            let clients = config.clients();
            assert!(clients.is_empty());
            assert_eq!(clients.len(), 0);
        }

        #[test]
        fn test_config_partial_eq() {
            let config1 = Config::new();
            let config2 = Config::new();

            // Default configs should be equal
            assert_eq!(config1, config2);

            // Add server to one config
            let server_config = create_test_server_config();
            let config3 = config1.clone().with_servers(vec![server_config]);

            // Should not be equal anymore
            assert_ne!(config1, config3);
        }

        #[test]
        fn test_mixed_auth_types() {
            use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};

            let static_jwt =
                IdentityProviderConfig::StaticJwt(StaticJwtConfig::with_file("test-token.jwt"));

            let jwt = IdentityVerifierConfig::Jwt(JwtConfig::new(
                slim_config::auth::jwt::Claims::default(),
                std::time::Duration::from_secs(3600),
                slim_config::auth::jwt::JwtKey::Decoding(Key {
                    algorithm: Algorithm::HS256,
                    format: KeyFormat::Pem,
                    key: KeyData::Data("test-key".to_string()),
                }),
            ));

            let config = Config::new()
                .with_token_provider_auth(static_jwt.clone())
                .with_token_verifier_auth(jwt.clone());

            assert_eq!(config.token_provider, static_jwt);
            assert_eq!(config.token_verifier, jwt);
        }

        mod edge_case_tests {
            use super::*;

            #[test]
            fn test_config_builder_pattern_reuse() {
                let base_config = Config::new();

                let config1 = base_config
                    .clone()
                    .with_servers(vec![create_test_server_config()]);
                let config2 = base_config
                    .clone()
                    .with_clients(vec![create_test_client_config()]);

                // Base config should still be empty
                assert!(base_config.servers.is_empty());
                assert!(base_config.clients.is_empty());

                // Derived configs should have their respective additions
                assert_eq!(config1.servers.len(), 1);
                assert!(config1.clients.is_empty());

                assert!(config2.servers.is_empty());
                assert_eq!(config2.clients.len(), 1);
            }

            #[test]
            fn test_config_overwrite_behavior() {
                let server1 = create_test_server_config();
                let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
                    .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());

                let config = Config::new()
                    .with_servers(vec![server1])
                    .with_servers(vec![server2.clone()]); // This should overwrite, not append

                assert_eq!(config.servers.len(), 1);
                assert_eq!(config.servers[0], server2);
            }

            #[test]
            fn test_auth_config_none_variants() {
                let config = Config::new();

                assert_eq!(config.token_provider, IdentityProviderConfig::None);
                assert_eq!(config.token_verifier, IdentityVerifierConfig::None);

                // Adding one shouldn't affect the other
                let config_with_provider =
                    config
                        .clone()
                        .with_token_provider_auth(IdentityProviderConfig::SharedSecret {
                            id: "test-provider".to_string(),
                            data: "secret".to_string(),
                        });

                assert_ne!(
                    config_with_provider.token_provider,
                    IdentityProviderConfig::None
                );
                assert_eq!(
                    config_with_provider.token_verifier,
                    IdentityVerifierConfig::None
                );
            }
        }

        #[test]
        fn test_token_verifier_auth_config_serialize_shared_secret() {
            let auth = IdentityVerifierConfig::SharedSecret {
                id: "test-verifier".to_string(),
                data: "test-secret".to_string(),
            };
            let json = serde_json::to_string(&auth).unwrap();
            assert!(json.contains("shared_secret"));
            assert!(json.contains("test-secret"));
        }

        #[test]
        fn test_token_verifier_auth_config_deserialize_shared_secret() {
            let json = r#"{"type": "shared_secret", "id": "test-verifier", "data": "test-secret"}"#;
            let auth: IdentityVerifierConfig = serde_json::from_str(json).unwrap();

            match auth {
                IdentityVerifierConfig::SharedSecret { id, data } => {
                    assert_eq!(id, "test-verifier");
                    assert_eq!(data, "test-secret");
                }
                _ => panic!("Expected SharedSecret variant"),
            }
        }
    }
}
