// Copyright 2023 Salesforce, Inc. All rights reserved.
use crate::policy_context::metadata::{Api, EnvironmentContext};
use crate::policy_context::static_policy_context_cache::StaticPolicyContextCache;
use classy::extract::context::ConfigureContext;
use classy::extract::FromContext;
use std::convert::Infallible;

#[derive(Clone, Debug)]
pub struct Metadata {
    pub flex_metadata: FlexMetadata,
    pub policy_metadata: PolicyMetadata,
    pub api_metadata: ApiMetadata,
    pub platform_metadata: PlatformMetadata,
}

#[derive(Clone, Debug)]
pub struct FlexMetadata {
    pub flex_name: String,
    pub flex_version: String,
}

#[derive(Clone, Debug)]
pub struct PolicyMetadata {
    pub policy_name: String,
    pub policy_namespace: String,
}

#[derive(Clone, Debug)]
pub struct ApiMetadata {
    pub id: Option<String>,
    pub name: Option<String>,
    pub version: Option<String>,
    pub slas: Option<Vec<ApiSla>>,
}

#[derive(Clone, Debug, Default)]
pub struct PlatformMetadata {
    pub organization_id: String,
    pub environment_id: String,
    pub root_organization_id: String,
}

#[derive(Clone, Debug)]
pub struct ApiSla {
    pub id: String,
    pub tiers: Vec<Tier>,
}

#[derive(Clone, Debug)]
pub struct Tier {
    pub requests: u64,
    pub period_in_millis: u64,
}

impl From<&crate::policy_context::metadata::PolicyMetadata> for Metadata {
    fn from(value: &crate::policy_context::metadata::PolicyMetadata) -> Self {
        Metadata {
            flex_metadata: value.into(),
            policy_metadata: value.into(),
            api_metadata: value.into(),
            platform_metadata: value.into(),
        }
    }
}

impl From<&crate::policy_context::metadata::PolicyMetadata> for FlexMetadata {
    fn from(value: &crate::policy_context::metadata::PolicyMetadata) -> Self {
        FlexMetadata {
            flex_name: value.flex_name().to_string(),
            flex_version: value
                .anypoint_environment()
                .and_then(EnvironmentContext::flex_version)
                .map(str::to_string)
                // Will always be set since min flex version is 1.6.1
                .unwrap_or_default(),
        }
    }
}

impl From<&crate::policy_context::metadata::PolicyMetadata> for PolicyMetadata {
    fn from(value: &crate::policy_context::metadata::PolicyMetadata) -> Self {
        PolicyMetadata {
            policy_name: value.policy_id().to_string(),
            policy_namespace: value.policy_namespace().to_string(),
        }
    }
}

impl From<&crate::policy_context::metadata::PolicyMetadata> for ApiMetadata {
    fn from(value: &crate::policy_context::metadata::PolicyMetadata) -> Self {
        ApiMetadata {
            id: value.api_info().map(Api::id).map(str::to_string),
            name: value.api_info().map(Api::name).map(str::to_string),
            version: value.api_info().map(Api::version).map(str::to_string),
            slas: value
                .api_tiers()
                .map(|slas| slas.iter().map(|sla| sla.into()).collect()),
        }
    }
}

impl From<&crate::policy_context::metadata::ApiSla> for ApiSla {
    fn from(value: &crate::policy_context::metadata::ApiSla) -> Self {
        ApiSla {
            id: value.id().to_string(),
            tiers: value.tiers().iter().map(|s| s.into()).collect(),
        }
    }
}

impl From<&crate::policy_context::metadata::Tier> for Tier {
    fn from(value: &crate::policy_context::metadata::Tier) -> Self {
        Tier {
            requests: value.requests(),
            period_in_millis: value.period_in_millis(),
        }
    }
}

impl From<&crate::policy_context::metadata::PolicyMetadata> for PlatformMetadata {
    fn from(value: &crate::policy_context::metadata::PolicyMetadata) -> Self {
        // PlatformMetadata should always be set since it is read from the registration yaml.
        value
            .anypoint_environment()
            .map(|env| PlatformMetadata {
                organization_id: env.organization_id().to_string(),
                environment_id: env.environment_id().to_string(),
                root_organization_id: env.master_organization_id().to_string(),
            })
            .unwrap_or_default()
    }
}

impl FromContext<ConfigureContext> for Metadata {
    type Error = Infallible;

    fn from_context(_context: &ConfigureContext) -> Result<Self, Self::Error> {
        Ok(Metadata::new())
    }
}

impl Metadata {
    pub fn new() -> Metadata {
        let context = StaticPolicyContextCache::read_metadata();
        Metadata::from(context.as_ref())
    }
}

impl Default for Metadata {
    fn default() -> Self {
        Metadata::new()
    }
}
