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

use crate::config::Config;
use crate::port::Port;
use crate::services::flex::gcl::ToGcl;
use crate::services::flex::policy::PolicyConfig;
use convert_case::{Case, Casing};
use indoc::formatdoc;
use std::ops::Add;

/// Configuration for a local API service.
#[derive(Debug, Clone)]
pub struct ApiConfig {
    pub(super) name: String,
    upstream: String,
    pub(super) port: Port,
    path: String,
    policies: Vec<PolicyConfig>,
}

impl ApiConfig {
    fn new() -> Self {
        Self {
            name: "ingress-http".to_string(),
            upstream: "http://backend:80".to_string(),
            port: 8081,
            path: "/anything/echo/".to_string(),
            policies: vec![],
        }
    }

    /// Creates a builder for initialize a [`ApiConfig`].
    pub fn builder() -> ApiConfigBuilder {
        ApiConfigBuilder::new()
    }
}

/// A builder for initilizing a [`ApiConfig`].
#[derive(Debug, Clone)]
pub struct ApiConfigBuilder {
    config: ApiConfig,
}

impl ApiConfigBuilder {
    fn new() -> Self {
        Self {
            config: ApiConfig::new(),
        }
    }

    /// Set the API name.
    pub fn name<T: Into<String>>(self, name: T) -> Self {
        Self {
            config: ApiConfig {
                name: name.into(),
                ..self.config
            },
        }
    }

    /// Set the API upstream.
    pub fn upstream(self, upstream: &dyn Config) -> Self {
        Self {
            config: ApiConfig {
                upstream: format!(
                    "{}://{}:{}",
                    upstream.schema(),
                    upstream.hostname(),
                    upstream.port()
                ),
                ..self.config
            },
        }
    }

    /// Set the API port.
    pub fn port(self, port: Port) -> Self {
        Self {
            config: ApiConfig {
                port,
                ..self.config
            },
        }
    }

    /// Set the API path.
    pub fn path<T: Into<String>>(self, path: T) -> Self {
        Self {
            config: ApiConfig {
                path: path.into(),
                ..self.config
            },
        }
    }

    /// Set the API policies.
    pub fn policies<T>(self, policies: T) -> Self
    where
        T: IntoIterator<Item = PolicyConfig>,
    {
        Self {
            config: ApiConfig {
                policies: policies.into_iter().collect(),
                ..self.config
            },
        }
    }

    /// Builds a new [`ApiConfig`].
    pub fn build(self) -> ApiConfig {
        self.config
    }
}

const API_POLICY_SECTION_GCL: &str = r#"  policies:"#;

impl ToGcl for ApiConfig {
    fn to_gcl(&self) -> String {
        let name = self.name.to_case(Case::Kebab);
        let port = self.port;
        let upstream = self.upstream.as_str();
        let path = self.path.as_str();

        let mut result = formatdoc!(
            "
        # Copyright (c) 2025, Salesforce, Inc.,
        # All rights reserved.
        # For full license text, see the LICENSE.txt file
        ---
        apiVersion: gateway.mulesoft.com/v1alpha1
        kind: ApiInstance
        metadata:
          name: {name}
        spec:
          address: http://0.0.0.0:{port}
          services:
            upstream:
              address: {upstream}
              routes:
                - config:
                    destinationPath: {path}
        "
        );

        if !self.policies.is_empty() {
            result = result.add(API_POLICY_SECTION_GCL);
        }

        for policy_config in self.policies.iter() {
            result = result.add(&policy_config.to_gcl());
        }

        result
    }

    fn name(&self) -> &str {
        &self.name
    }
}

#[cfg(test)]
mod tests {
    use crate::services::flex::gcl::ToGcl;
    use crate::services::flex::{ApiConfig, PolicyConfig};
    use crate::services::httpmock::HttpMockConfig;
    use indoc::formatdoc;

    #[test]
    fn generate_api_gcl() {
        let upstream_config = HttpMockConfig::builder().hostname("mock").port(80).build();

        let policy_config = PolicyConfig::builder()
            .name("simple-oauth-2-validation-v1-0-impl")
            .configuration(serde_json::json!({
                    "authorization": "whatever",
                    "oauthService": "http://mock:80/auth",
            }))
            .build();

        let api_config = ApiConfig::builder()
            .upstream(&upstream_config)
            .policies([policy_config])
            .build();

        let expected = formatdoc!(
            r#"
        # Copyright (c) 2025, Salesforce, Inc.,
        # All rights reserved.
        # For full license text, see the LICENSE.txt file
        ---
        apiVersion: gateway.mulesoft.com/v1alpha1
        kind: ApiInstance
        metadata:
          name: ingress-http
        spec:
          address: http://0.0.0.0:8081
          services:
            upstream:
              address: http://mock:80
              routes:
                - config:
                    destinationPath: /anything/echo/
          policies:
            - policyRef:
                name: simple-oauth-2-validation-v1-0-impl
                namespace: default
              config: {{"authorization":"whatever","oauthService":"http://mock:80/auth"}}"#
        );

        assert_eq!(api_config.to_gcl(), expected)
    }
}