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

use crate::services::flex::gcl::ToGcl;
use crate::services::flex::PolicyConfig;
use indoc::formatdoc;

/// Configuration for a local Service.
#[derive(Debug, Clone)]
pub struct PolicyBindingConfig {
    name: String,
    target: Target,
    policy_config: PolicyConfig,
}

#[derive(Debug, Clone)]
pub struct Target {
    name: String,
    kind: Option<TargetKind>,
}

impl Target {
    fn new() -> Self {
        Self {
            name: "".to_string(),
            kind: None,
        }
    }

    pub fn builder() -> TargetBuilder {
        TargetBuilder::new()
    }
}

pub struct TargetBuilder {
    target: Target,
}

impl TargetBuilder {
    /// Set the upstream service name.
    pub fn name<T: Into<String>>(self, name: T) -> Self {
        Self {
            target: Target {
                name: name.into(),
                ..self.target
            },
        }
    }

    /// Set the upstream service name.
    pub fn kind<T: Into<TargetKind>>(self, kind: T) -> Self {
        Self {
            target: Target {
                kind: Some(kind.into()),
                ..self.target
            },
        }
    }

    pub fn build(self) -> Target {
        self.target
    }

    pub fn new() -> Self {
        Self {
            target: Target::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub enum TargetKind {
    Service,
    ApiInstance,
}

impl PolicyBindingConfig {
    fn new() -> Self {
        Self {
            name: "".to_string(),
            target: Target {
                name: "".to_string(),
                kind: None,
            },
            policy_config: PolicyConfig::builder().build(),
        }
    }

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

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

impl PolicyBindingConfigBuilder {
    fn new() -> Self {
        Self {
            config: PolicyBindingConfig::new(),
        }
    }

    /// Set the target of the binding.
    pub fn config<T: Into<PolicyConfig>>(self, config: T) -> Self {
        Self {
            config: PolicyBindingConfig {
                policy_config: config.into(),
                ..self.config
            },
        }
    }

    /// Set the target of the binding.
    pub fn target<T: Into<Target>>(self, target: T) -> Self {
        Self {
            config: PolicyBindingConfig {
                target: target.into(),
                ..self.config
            },
        }
    }

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

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

impl ToGcl for PolicyBindingConfig {
    // NOTE: If in the future we need to support non-compact policy GCLs (to apply policies to
    // services) we'll need change the api GCL to the non-compact one.
    fn to_gcl(&self) -> String {
        let name = self.name.as_str();
        let target_name = &self.target.name.as_str();
        let kind = match &self.target.kind {
            None => "ApiInstance",
            Some(kind) => match kind {
                TargetKind::Service => "Service",
                TargetKind::ApiInstance => "ApiInstance",
            },
        };

        let ref_name = self.policy_config.name();
        let config = self.policy_config.config();
        formatdoc!(
            r#"
        # Copyright (c) 2026, Salesforce, Inc.,
        # All rights reserved.
        # For full license text, see the LICENSE.txt file
        ---
        apiVersion: gateway.mulesoft.com/v1alpha1
        kind: PolicyBinding
        metadata:
          name: {name}
        spec:
          targetRef:
             kind: {kind}
             name: {target_name}
          policyRef:
            name: {ref_name}
          config: {config}"#
        )
    }

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

#[cfg(test)]
mod tests {
    use crate::services::flex::gcl::ToGcl as _;
    use crate::services::flex::policy_binding::{PolicyBindingConfig, Target, TargetKind};
    use crate::services::flex::PolicyConfig;
    use indoc::formatdoc;

    #[test]
    fn generate_policy_biding_to_service() {
        let service_config = PolicyBindingConfig::builder()
            .name("httpbin-route-oauth2")
            .target(Target::builder().name("httpbin-api-oauth2").build())
            .config(
                PolicyConfig::builder()
                    .name("route")
                    .configuration(serde_json::json!({"destinationRef": [{
                        "name": "httpbin-local-oauth2"
                    }]}))
                    .build(),
            )
            .build();

        let expected = formatdoc!(
            r#"
        # Copyright (c) 2026, Salesforce, Inc.,
        # All rights reserved.
        # For full license text, see the LICENSE.txt file
        ---
        apiVersion: gateway.mulesoft.com/v1alpha1
        kind: PolicyBinding
        metadata:
          name: httpbin-route-oauth2
        spec:
          targetRef:
             kind: ApiInstance
             name: httpbin-api-oauth2
          policyRef:
            name: route
          config: {{"destinationRef":[{{"name":"httpbin-local-oauth2"}}]}}"#
        );

        assert_eq!(service_config.to_gcl(), expected);
    }

    #[test]
    fn generate_policy_biding_to_upstream() {
        let service_config = PolicyBindingConfig::builder()
            .name("httpbin-route-oauth2")
            .target(
                Target::builder()
                    .kind(TargetKind::Service)
                    .name("httpbin-local-oauth2")
                    .build(),
            )
            .config(
                PolicyConfig::builder()
                    .name("credential-injection-oauth2-flex")
                    .configuration(serde_json::json!({
                        "oauthService": "http://oauth-service:9100/token",
                        "tokenFetchTimeout": 5,
                        "clientId": "id",
                        "clientSecret": "secret",
                        "overwrite": true,
                        "scope": ["pepe","pia"],
                        "allowRequestWithoutCredential": false
                    }))
                    .build(),
            )
            .build();

        let expected = formatdoc!(
            r#"
        # Copyright (c) 2026, Salesforce, Inc.,
        # All rights reserved.
        # For full license text, see the LICENSE.txt file
        ---
        apiVersion: gateway.mulesoft.com/v1alpha1
        kind: PolicyBinding
        metadata:
          name: httpbin-route-oauth2
        spec:
          targetRef:
             kind: Service
             name: httpbin-local-oauth2
          policyRef:
            name: credential-injection-oauth2-flex
          config: {{"allowRequestWithoutCredential":false,"clientId":"id","clientSecret":"secret","oauthService":"http://oauth-service:9100/token","overwrite":true,"scope":["pepe","pia"],"tokenFetchTimeout":5}}"#
        );

        assert_eq!(service_config.to_gcl(), expected);
    }
}