Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use std::fs::File;
use std::io::Write;

use tempfile::TempDir;

use crate::services::flex::gcl::ToGcl;
#[cfg(feature = "experimental")]
use crate::services::flex::policy_binding::PolicyBindingConfig;
use crate::services::flex::ApiConfig;
#[cfg(feature = "experimental")]
use crate::services::flex::UpstreamServiceConfig;

/// Struct to manage the dynamic Flex config.
#[derive(Debug)]
pub struct DynamicFlexConfig {
    dir: Result<TempDir, std::io::Error>,
    path: String,
    apis: Vec<ApiConfig>,
    #[cfg(feature = "experimental")]
    services: Vec<UpstreamServiceConfig>,
    #[cfg(feature = "experimental")]
    policy_bindings: Vec<PolicyBindingConfig>,
}

impl Clone for DynamicFlexConfig {
    fn clone(&self) -> Self {
        let mut cloned = DynamicFlexConfig::new();
        for api in self.apis.iter() {
            cloned = cloned.with_api(api.clone())
        }
        #[cfg(feature = "experimental")]
        for service in self.services.iter() {
            cloned = cloned.with_upstream_service(service.clone())
        }

        #[cfg(feature = "experimental")]
        for policy_bindings in self.policy_bindings.iter() {
            cloned = cloned.with_policy_bindings(policy_bindings.clone())
        }
        cloned
    }
}

impl DynamicFlexConfig {
    /// Create a new dynamic Flex config.
    pub fn new() -> Self {
        let dir = tempfile::tempdir();
        let path = dir
            .as_ref()
            .map(|dir| dir.path().to_string_lossy().to_string())
            .unwrap_or_default();

        Self {
            dir,
            path,
            apis: vec![],
            #[cfg(feature = "experimental")]
            services: vec![],
            #[cfg(feature = "experimental")]
            policy_bindings: vec![],
        }
    }

    fn dump_config<T: ToGcl>(dir: TempDir, config: &T) -> Result<TempDir, std::io::Error> {
        let file_path = dir.path().join(format!("{}.yaml", config.name()));
        let mut tmp_file = File::create(&file_path)?;

        let gcl = config.to_gcl();
        tmp_file.write_all(gcl.as_bytes())?;
        tmp_file.flush()?;

        log::info!(
            "Generated GCL name={} path={}",
            config.name(),
            file_path.display()
        );

        log::info!("\n{gcl}");

        Ok(dir)
    }

    /// Add an API config to the dynamic Flex config.
    pub fn with_api(self, api_config: ApiConfig) -> Self {
        let dir = self.dir.and_then(|d| Self::dump_config(d, &api_config));

        let mut apis = self.apis;
        apis.push(api_config);

        Self { dir, apis, ..self }
    }

    /// Add an upstream service config to the dynamic Flex config
    /// This is an experimental feature.
    #[cfg(feature = "experimental")]
    pub fn with_upstream_service(self, service_config: UpstreamServiceConfig) -> Self {
        let dir = self.dir.and_then(|d| Self::dump_config(d, &service_config));

        let mut services = self.services;
        services.push(service_config);

        Self {
            dir,
            services,
            ..self
        }
    }

    /// Add a policy binding config to the dynamic Flex config
    /// This is an experimental feature.
    #[cfg(feature = "experimental")]
    pub fn with_policy_bindings(self, policy_binding: PolicyBindingConfig) -> Self {
        let dir = self.dir.and_then(|d| Self::dump_config(d, &policy_binding));

        let mut bindings = self.policy_bindings;
        bindings.push(policy_binding);

        Self {
            dir,
            policy_bindings: bindings,
            ..self
        }
    }

    /// Get the directories for the dynamic Flex config.
    pub fn dirs(&self) -> Result<Vec<(String, String)>, std::io::Error> {
        match &self.dir {
            Ok(_) => Ok(vec![(self.path.clone(), "_dynamic".to_string())]),
            Err(e) => Err(std::io::Error::new(e.kind(), e.to_string())),
        }
    }
}