// Copyright 2023 Salesforce, Inc. All rights reserved.
use derive_builder::Builder;
use regex::Regex;
use std::borrow::Cow;

/// Configuration for CORS checkings.
#[derive(Default, Debug, Builder)]
pub struct Configuration<'a> {
    public_resource: bool,

    support_credentials: bool,

    origin_groups: Vec<OriginGroup<'a>>,
}

impl<'a> Configuration<'a> {
    /// Returns `true` if public resource are allowed.
    pub fn public_resource(&self) -> bool {
        self.public_resource
    }

    /// Returns `true` if credentials are supported.
    pub fn support_credentials(&self) -> bool {
        self.support_credentials
    }

    /// Returns the origin groups.
    pub fn origin_groups(&self) -> &[OriginGroup<'a>] {
        &self.origin_groups
    }
}

impl Configuration<'_> {
    /// Returns a fresh builder for [`CorsConfiguration`]
    pub fn builder<'a>() -> ConfigurationBuilder<'a> {
        ConfigurationBuilder::default()
    }
}

#[derive(Default, Debug, Clone, Builder)]
pub struct OriginGroup<'a> {
    /// Origin group name.
    #[builder(setter(into))]
    origin_group_name: String,

    /// List of plain origins.
    #[builder(setter(into))]
    plain_origins: Cow<'a, [String]>,

    /// List of regex origins.
    #[builder(setter(into))]
    regex_origins: Cow<'a, [Regex]>,

    /// Access control max age.
    access_control_max_age: u32,

    /// List of allowed methods.
    allowed_methods: Vec<AllowedMethod>,

    /// List of headers.
    headers: Vec<String>,

    /// List of exposed headers.
    exposed_headers: Vec<String>,
}

impl<'a> OriginGroup<'a> {
    /// Returns the origin group name.
    pub fn origin_group_name(&self) -> &str {
        &self.origin_group_name
    }

    pub(crate) fn plain_origins_cow(&self) -> &Cow<'a, [String]> {
        &self.plain_origins
    }

    pub(crate) fn regex_origins_cow(&self) -> &Cow<'a, [Regex]> {
        &self.regex_origins
    }

    /// Returns the list of regex origins.
    pub fn regex_origins(&self) -> &[Regex] {
        &self.regex_origins
    }

    /// Returns the list of plain origins.
    pub fn plain_origins(&self) -> &[String] {
        &self.plain_origins
    }

    /// Returns the acces control max age.
    pub fn access_control_max_age(&self) -> u32 {
        self.access_control_max_age
    }

    /// Returns the list of allowed methods.
    pub fn allowed_methods(&self) -> &[AllowedMethod] {
        &self.allowed_methods
    }

    /// Returns the list of headers.
    pub fn headers(&self) -> &[String] {
        &self.headers
    }

    /// Returns the list of exposed headers.
    pub fn exposed_headers(&self) -> &[String] {
        &self.exposed_headers
    }
}

impl OriginGroup<'_> {
    /// Returns a fresh builder for [`OriginGroup`]
    pub fn builder<'b>() -> OriginGroupBuilder<'b> {
        OriginGroupBuilder::default()
    }
}

/// CORS allowed method.
#[derive(Default, Debug, Clone, Builder)]
pub struct AllowedMethod {
    /// Allowed method name.
    #[builder(setter(into))]
    method_name: String,

    /// Determines if the method is allowed.
    allowed: bool,
}

impl AllowedMethod {
    /// Returns a fresh builder for [`AllowedMethod`]
    pub fn builder() -> AllowedMethodBuilder {
        AllowedMethodBuilder::default()
    }
}

pub(crate) trait CorsConfig<'a> {
    fn public_resource(&self) -> bool;
    fn support_credentials(&self) -> bool;
    fn origin_groups(&self) -> &[OriginGroup<'a>];
}

impl<'a> CorsConfig<'a> for Configuration<'a> {
    fn public_resource(&self) -> bool {
        self.public_resource
    }

    fn support_credentials(&self) -> bool {
        self.support_credentials
    }

    fn origin_groups(&self) -> &[OriginGroup<'a>] {
        &self.origin_groups
    }
}

#[cfg(test)]
mockall::mock! {
    pub Configuration {}
    impl CorsConfig<'_> for Configuration {
        fn public_resource(&self) -> bool {
            self.public_resource
        }

        fn support_credentials(&self) -> bool {
            self.support_credentials
        }

        fn origin_groups(&self) -> &[OriginGroup<'static>] {
            self.origin_groups.as_slice()
        }
    }
}

impl AllowedMethod {
    pub fn method_name(&self) -> &str {
        &self.method_name
    }

    pub fn allowed(&self) -> bool {
        self.allowed
    }
}
