// Copyright 2023 Salesforce, Inc. All rights reserved.
use de::Deserialize;
use serde::{de, Deserializer};
use sha2::{Digest, Sha256};

use classy::client::{InvalidUri, Service, Uri};

use crate::policy_context::api::Metadata;

const SERVICE_NAME_SUFFIX: &str = "service";

/// Deserializes a valid URI into a `Service` using policy's metadata to generate the service name
pub fn deserialize_service<'de, D>(deserializer: D) -> Result<Service, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;

    let string: String = Deserialize::deserialize(deserializer)?;

    let metadata = Metadata::new();
    let service = new_service(string.as_str(), &metadata)
        .map_err(|err: InvalidUri| Error::custom(error_message(string.as_str(), err)))?;

    Ok(service)
}

/// Deserializes a valid URI into a `Option<Service>` using policy's metadata to generate the service name
pub fn deserialize_service_opt<'de, D>(deserializer: D) -> Result<Option<Service>, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;

    let string: Option<String> = Deserialize::deserialize(deserializer)?;

    match string {
        Some(string) => {
            let metadata = Metadata::new();
            let service = new_service(string.as_str(), &metadata)
                .map_err(|err: InvalidUri| Error::custom(error_message(string.as_str(), err)))?;
            Ok(Some(service))
        }
        None => Ok(None),
    }
}

/// Deserializes a set of valid URIs into a `Vec<Service>` using policy's metadata to generate the service name
pub fn deserialize_service_vec<'de, D>(deserializer: D) -> Result<Vec<Service>, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;

    let strings: Vec<String> = Deserialize::deserialize(deserializer)?;

    let metadata = Metadata::new();

    let services: Vec<Service> = strings
        .iter()
        .map(|string| {
            let service = new_service(string.as_str(), &metadata)
                .map_err(|err: InvalidUri| Error::custom(error_message(string.as_str(), err)))?;

            Ok(service)
        })
        .collect::<Result<Vec<_>, _>>()?;

    Ok(services)
}

/// Deserializes a set of valid URIs into a `Option<Vec<Service>>` using policy's metadata to generate the service name
pub fn deserialize_service_opt_vec<'de, D>(
    deserializer: D,
) -> Result<Option<Vec<Service>>, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;

    let strings: Option<Vec<String>> = Deserialize::deserialize(deserializer)?;

    match strings {
        Some(strings) => {
            let metadata = Metadata::new();

            let services: Vec<Service> = strings
                .iter()
                .map(|string| {
                    let service =
                        new_service(string.as_str(), &metadata).map_err(|err: InvalidUri| {
                            Error::custom(error_message(string.as_str(), err))
                        })?;

                    Ok(service)
                })
                .collect::<Result<Vec<_>, _>>()?;

            Ok(Some(services))
        }
        None => Ok(None),
    }
}

/// Generates a compliant Service name using the policy name and the authority part of an URL
pub fn service_name(policy_name: &str, authority: &str) -> String {
    let full_name = format!("{}-{}", policy_name, authority);

    let digest = &Sha256::digest(full_name.as_str());
    let hex = &format!("{:x}", digest)[..7];

    let compliant_full_name: String = full_name
        .chars()
        .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
        .collect();

    let capped_name = &compliant_full_name[..std::cmp::min(47, compliant_full_name.len())];

    format!("{}-{}-{}", capped_name.to_owned(), hex, SERVICE_NAME_SUFFIX)
}

fn new_service(string: &str, metadata: &Metadata) -> Result<Service, InvalidUri> {
    let uri: Uri = string.parse()?;
    let service_namespace = metadata.policy_metadata.policy_namespace.as_str();
    let service_name = service_name(
        metadata.policy_metadata.policy_name.as_str(),
        uri.authority(),
    );
    Ok(Service::from(service_name.as_str(), service_namespace, uri))
}

fn error_message(string: &str, err: InvalidUri) -> String {
    format!("Error parsing {} as Uri: {}", string, err)
}

#[cfg(test)]
mod test {
    use std::vec::IntoIter;

    use serde::de::value::{Error as ValueError, SeqDeserializer, StrDeserializer};
    use serde::de::{Error, IntoDeserializer};
    use serde_derive::Deserialize;
    use serde_json::json;

    use classy::hl::Service;

    use crate::client::{
        deserialize_service, deserialize_service_opt, deserialize_service_opt_vec,
        deserialize_service_vec, service_name,
    };

    #[test]
    fn service_name_test() {
        let name = service_name("policy", "authority");

        assert_eq!(name, "policy-authority-15aeecc-service");
    }

    #[test]
    fn service_name_is_idempotent() {
        let name = service_name("policy", "authority");
        let name2 = service_name("policy", "authority");

        assert_eq!(name, name2);
    }

    #[test]
    fn service_name_long_does_not_exceed_63_chars() {
        let authority = "a-really-long-authority-exceeding-max-large-service-name";
        let name = service_name("policy", authority);

        assert_eq!(name.len(), 63);
        assert_eq!(
            name,
            "policy-a-really-long-authority-exceeding-max-la-3b21324-service"
        );
    }

    #[test]
    fn service_name_two_long_names_that_end_different_have_different_hashes() {
        let authority = "a-really-long-authority-exceeding-max-large-service-name";
        let authority_2 = "a-really-long-authority-exceeding-max-large-service-name-2";
        let name = service_name("policy", authority);
        let name2 = service_name("policy", authority_2);

        assert_ne!(name, name2);
    }

    #[test]
    fn service_name_two_different_names_that_have_same_sanitized_name_have_different_hashes() {
        let authority = "authority:port";
        let authority_2 = "authority@port";
        let name = service_name("policy", authority);
        let name2 = service_name("policy", authority_2);

        assert_ne!(name, name2);
    }

    #[test]
    fn service_name_invalid_chars_in_authority_are_converted() {
        let authority = "username:password@[ipv6]:port";
        let name = service_name("policy", authority);

        assert_eq!(name, "policy-username-password--ipv6--port-c248764-service");
    }

    #[test]
    fn deserialize_service_successful_parsing() {
        let uri_string = "https://localhost:8080/api";

        let result = deserialize_service(deserializer(uri_string));

        assert!(result.is_ok());
        let service = result.unwrap();
        assert_eq!(service.uri().to_string(), uri_string.to_string());
        assert_eq!(
            service.cluster_name(),
            "NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
        );
    }

    #[test]
    fn deserialize_service_invalid_uri() {
        let uri_string = "localhost:8080/api";

        let result = deserialize_service(deserializer(uri_string));

        assert_eq!(
            result.unwrap_err(),
            ValueError::custom("Error parsing localhost:8080/api as Uri: invalid format")
        )
    }

    #[test]
    fn deserialize_service_vec_successful_parsing() {
        let uri_string_1 = "https://localhost:8080/api";
        let uri_string_2 = "https://another-host:9090/api";
        let vec = vec![uri_string_1, uri_string_2];

        let result = deserialize_service_vec(vec_deserializer(vec));

        assert!(result.is_ok(), "{:?}", result.err());
        let services = result.unwrap();
        assert_eq!(services.len(), 2);
        let service1 = services.first().unwrap();
        assert_eq!(service1.uri().to_string(), uri_string_1.to_string());
        assert_eq!(
            service1.cluster_name(),
            "NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
        );
        let service2 = services.get(1).unwrap();
        assert_eq!(service2.uri().to_string(), uri_string_2.to_string());
        assert_eq!(
            service2.cluster_name(),
            "NoPolicyId-another-host-9090-f35336e-service.NoPolicyNamespace.svc"
        );
    }

    #[test]
    fn deserialize_service_vec_invalid_uri_first_element() {
        let uri_string_1 = "localhost:8080/api";
        let uri_string_2 = "https://another-host:9090/api";
        let vec = vec![uri_string_1, uri_string_2];

        let result = deserialize_service_vec(vec_deserializer(vec));

        assert_eq!(
            result.unwrap_err(),
            ValueError::custom("Error parsing localhost:8080/api as Uri: invalid format")
        )
    }

    #[test]
    fn deserialize_service_vec_invalid_uri_last_element() {
        let uri_string_1 = "https://localhost:8080/api";
        let uri_string_2 = "another-host:9090/api";
        let vec = vec![uri_string_1, uri_string_2];

        let result = deserialize_service_vec(vec_deserializer(vec));

        assert_eq!(
            result.unwrap_err(),
            ValueError::custom("Error parsing another-host:9090/api as Uri: invalid format")
        )
    }

    #[test]
    fn deserialize_opt_service_successful_parsing() {
        let uri_string = "https://localhost:8080/api";

        let test: TestOpt = serde_json::from_value(json!({ "service": uri_string })).unwrap();

        let option = test.service;
        assert!(option.is_some());

        let service = option.unwrap();
        assert_eq!(service.uri().to_string(), uri_string.to_string());
        assert_eq!(
            service.cluster_name(),
            "NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
        );
    }

    #[test]
    fn deserialize_opt_service_invalid_uri() {
        let result: Result<TestOpt, serde_json::Error> =
            serde_json::from_value(json!({ "service": "localhost:8080/api" }));

        assert_eq!(
            result.unwrap_err().to_string(),
            "Error parsing localhost:8080/api as Uri: invalid format"
        )
    }

    #[test]
    fn deserialize_opt_missing_field() {
        let test: TestOpt = serde_json::from_value(json!({})).unwrap();

        assert!(test.service.is_none());
    }

    #[test]
    fn deserialize_service_opt_vec_successful_parsing() {
        let uri_string_1 = "https://localhost:8080/api";
        let uri_string_2 = "https://another-host:9090/api";

        let test: TestOptVec =
            serde_json::from_value(json!({ "services": [uri_string_1, uri_string_2] })).unwrap();

        let option = test.services;
        assert!(option.is_some());

        let services = option.unwrap();
        assert_eq!(services.len(), 2);
        let service1 = services.first().unwrap();
        assert_eq!(service1.uri().to_string(), uri_string_1.to_string());
        assert_eq!(
            service1.cluster_name(),
            "NoPolicyId-localhost-8080-a5ec11e-service.NoPolicyNamespace.svc"
        );
        let service2 = services.get(1).unwrap();
        assert_eq!(service2.uri().to_string(), uri_string_2.to_string());
        assert_eq!(
            service2.cluster_name(),
            "NoPolicyId-another-host-9090-f35336e-service.NoPolicyNamespace.svc"
        );
    }

    #[test]
    fn deserialize_service_opt_vec_invalid_first_uri() {
        let result: Result<TestOptVec, serde_json::Error> = serde_json::from_value(
            json!({ "services": ["localhost:8080/api", "https://another-host:9090/api"] }),
        );

        assert_eq!(
            result.unwrap_err().to_string(),
            "Error parsing localhost:8080/api as Uri: invalid format"
        )
    }

    #[test]
    fn deserialize_service_opt_vec_invalid_last_uri() {
        let result: Result<TestOptVec, serde_json::Error> = serde_json::from_value(
            json!({ "services": ["https://localhost:8080/api", "another-host:9090/api"] }),
        );

        assert_eq!(
            result.unwrap_err().to_string(),
            "Error parsing another-host:9090/api as Uri: invalid format"
        )
    }

    #[test]
    fn deserialize_service_opt_vec_missing_field() {
        let test: TestOptVec = serde_json::from_value(json!({})).unwrap();

        assert!(test.services.is_none());
    }

    #[derive(Debug, Deserialize)]
    struct TestOpt {
        #[serde(default)]
        #[serde(deserialize_with = "deserialize_service_opt")]
        pub service: Option<Service>,
    }

    #[derive(Debug, Deserialize)]
    struct TestOptVec {
        #[serde(default)]
        #[serde(deserialize_with = "deserialize_service_opt_vec")]
        pub services: Option<Vec<Service>>,
    }

    fn deserializer(uri_string: &str) -> StrDeserializer<serde::de::value::Error> {
        let deserializer: StrDeserializer<ValueError> = uri_string.into_deserializer();
        deserializer
    }

    fn vec_deserializer(vec: Vec<&str>) -> SeqDeserializer<IntoIter<&str>, ValueError> {
        let deserializer = vec.into_deserializer();
        deserializer
    }
}
