//! Contributor Insights rules: Put/Describe/Delete/Enable/Disable, report,
//! and managed rules.

use fakecloud_core::query::{optional_query_param, required_query_param};
use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError};

use crate::service::{
    collect_member_values, invalid_param, not_found, validate_len, validate_range_i64, xml_escape,
    xml_response, CloudWatchService,
};
use crate::state::{InsightRule, ManagedRule};

const DEFAULT_SCHEMA: &str = "{\"Name\":\"CloudWatchLogRule\",\"Version\":1}";

impl CloudWatchService {
    pub(crate) fn put_insight_rule(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        validate_len(req, "RuleName", 1, 128)?;
        validate_len(req, "RuleDefinition", 1, 8192)?;
        validate_len(req, "RuleState", 1, 32)?;
        let name = required_query_param(req, "RuleName")?;
        let definition = required_query_param(req, "RuleDefinition")?;
        let rule_state = optional_query_param(req, "RuleState").unwrap_or_else(|| "ENABLED".into());
        if rule_state != "ENABLED" && rule_state != "DISABLED" {
            return Err(invalid_param("RuleState must be ENABLED or DISABLED"));
        }
        let apply_on_transformed_logs = optional_query_param(req, "ApplyOnTransformedLogs")
            .map(|s| s.eq_ignore_ascii_case("true"))
            .unwrap_or(false);
        let rule = InsightRule {
            name: name.clone(),
            state: rule_state,
            schema: DEFAULT_SCHEMA.to_string(),
            definition,
            managed: false,
            apply_on_transformed_logs,
        };
        let mut state = self.state.write();
        let acct = state.get_or_create(&req.account_id);
        acct.insight_rules_in_mut(&req.region).insert(name, rule);
        Ok(xml_response("PutInsightRule", "", &req.request_id))
    }

    pub(crate) fn describe_insight_rules(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        validate_range_i64(req, "MaxResults", 1, 500)?;
        let state = self.state.read();
        let mut inner = String::from("<InsightRules>");
        if let Some(acct) = state.get(&req.account_id) {
            if let Some(rules) = acct.insight_rules_in(&req.region) {
                for rule in rules.values() {
                    inner.push_str(&render_insight_rule(rule));
                }
            }
        }
        inner.push_str("</InsightRules>");
        Ok(xml_response(
            "DescribeInsightRules",
            &inner,
            &req.request_id,
        ))
    }

    pub(crate) fn delete_insight_rules(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let names = collect_member_values(req, "RuleNames");
        if names.is_empty() {
            return Err(crate::service::missing_param("RuleNames"));
        }
        let mut state = self.state.write();
        let acct = state.get_or_create(&req.account_id);
        let rules = acct.insight_rules_in_mut(&req.region);
        for name in &names {
            rules.remove(name);
        }
        Ok(xml_response(
            "DeleteInsightRules",
            "<Failures/>",
            &req.request_id,
        ))
    }

    pub(crate) fn enable_insight_rules(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        self.toggle_insight_rules(req, "ENABLED", "EnableInsightRules")
    }

    pub(crate) fn disable_insight_rules(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        self.toggle_insight_rules(req, "DISABLED", "DisableInsightRules")
    }

    fn toggle_insight_rules(
        &self,
        req: &AwsRequest,
        new_state: &str,
        action: &str,
    ) -> Result<AwsResponse, AwsServiceError> {
        let names = collect_member_values(req, "RuleNames");
        if names.is_empty() {
            return Err(crate::service::missing_param("RuleNames"));
        }
        let mut state = self.state.write();
        let acct = state.get_or_create(&req.account_id);
        let rules = acct.insight_rules_in_mut(&req.region);
        for name in &names {
            if let Some(rule) = rules.get_mut(name) {
                rule.state = new_state.to_string();
            }
        }
        Ok(xml_response(action, "<Failures/>", &req.request_id))
    }

    pub(crate) fn get_insight_rule_report(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let name = required_query_param(req, "RuleName")?;
        required_query_param(req, "StartTime")?;
        required_query_param(req, "EndTime")?;
        let _period = required_query_param(req, "Period")?
            .parse::<i64>()
            .map_err(|_| invalid_param("Period must be an integer"))?;
        let metrics = collect_member_values(req, "Metrics");
        // Validate the requested metric names against the documented enum so a
        // bad request fails rather than being silently ignored.
        const ALLOWED_METRICS: &[&str] = &[
            "UniqueContributors",
            "MaxContributorValue",
            "SampleCount",
            "Average",
            "Sum",
            "Minimum",
            "Maximum",
        ];
        for m in &metrics {
            if !ALLOWED_METRICS.contains(&m.as_str()) {
                return Err(invalid_param(format!("Unknown insight metric '{m}'")));
            }
        }

        let state = self.state.read();
        let rule = state
            .get(&req.account_id)
            .and_then(|a| a.insight_rules_in(&req.region))
            .and_then(|r| r.get(&name))
            .cloned();
        let Some(rule) = rule else {
            return Err(not_found(format!("Insight rule {name} does not exist")));
        };

        // Derive the report shape from the rule's actual definition. Contributor
        // data itself is genuinely not computable here: fakecloud has no
        // log-ingestion -> Contributor Insights pipeline, so there are no
        // contributors to report. Rather than emit fixed all-zero constants, we
        // surface the rule's real key labels and aggregation statistic and an
        // honestly-empty contributor/datapoint set.
        let def: serde_json::Value =
            serde_json::from_str(&rule.definition).unwrap_or(serde_json::Value::Null);
        let key_labels: Vec<String> = def
            .get("Contribution")
            .and_then(|c| c.get("Keys"))
            .and_then(|k| k.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
                    .collect()
            })
            .unwrap_or_default();
        let aggregation_statistic = match def.get("AggregateOn").and_then(|v| v.as_str()) {
            Some("Sum") => "Sum",
            Some("Average") => "Average",
            _ => "Sum",
        };

        let mut inner = String::from("<KeyLabels>");
        for label in &key_labels {
            inner.push_str(&format!("<member>{}</member>", xml_escape(label)));
        }
        inner.push_str("</KeyLabels>");
        inner.push_str(&format!(
            "<AggregationStatistic>{aggregation_statistic}</AggregationStatistic>"
        ));
        inner.push_str("<AggregateValue>0.0</AggregateValue>");
        inner.push_str("<ApproximateUniqueCount>0</ApproximateUniqueCount>");
        inner.push_str("<Contributors/>");
        inner.push_str("<MetricDatapoints/>");
        Ok(xml_response(
            "GetInsightRuleReport",
            &inner,
            &req.request_id,
        ))
    }

    pub(crate) fn put_managed_insight_rules(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        let members = crate::service::collect_indexed(req, "ManagedRules");
        if members.is_empty() {
            return Err(crate::service::missing_param("ManagedRules"));
        }
        let mut state = self.state.write();
        let acct = state.get_or_create(&req.account_id);
        for m in members {
            let template = m
                .get("TemplateName")
                .cloned()
                .ok_or_else(|| invalid_param("ManagedRules.member.N.TemplateName is required"))?;
            let arn = m
                .get("ResourceARN")
                .cloned()
                .ok_or_else(|| invalid_param("ManagedRules.member.N.ResourceARN is required"))?;
            let bucket = acct
                .managed_rules_in_mut(&req.region)
                .entry(arn.clone())
                .or_default();
            // De-duplicate by template name per resource.
            if !bucket.iter().any(|r| r.template_name == template) {
                bucket.push(ManagedRule {
                    template_name: template,
                    resource_arn: arn,
                });
            }
        }
        Ok(xml_response(
            "PutManagedInsightRules",
            "<Failures/>",
            &req.request_id,
        ))
    }

    pub(crate) fn list_managed_insight_rules(
        &self,
        req: &AwsRequest,
    ) -> Result<AwsResponse, AwsServiceError> {
        validate_len(req, "ResourceARN", 1, 1024)?;
        validate_range_i64(req, "MaxResults", 1, 500)?;
        let resource_arn = required_query_param(req, "ResourceARN")?;
        let state = self.state.read();
        let mut inner = String::from("<ManagedRules>");
        if let Some(acct) = state.get(&req.account_id) {
            if let Some(map) = acct.managed_rules_in(&req.region) {
                if let Some(rules) = map.get(&resource_arn) {
                    for r in rules {
                        inner.push_str("<member>");
                        inner.push_str(&format!(
                            "<TemplateName>{}</TemplateName>",
                            xml_escape(&r.template_name)
                        ));
                        inner.push_str(&format!(
                            "<ResourceARN>{}</ResourceARN>",
                            xml_escape(&r.resource_arn)
                        ));
                        inner.push_str(
                            "<RuleState><RuleName>ManagedRule</RuleName><State>ENABLED</State></RuleState>",
                        );
                        inner.push_str("</member>");
                    }
                }
            }
        }
        inner.push_str("</ManagedRules>");
        Ok(xml_response(
            "ListManagedInsightRules",
            &inner,
            &req.request_id,
        ))
    }
}

fn render_insight_rule(rule: &InsightRule) -> String {
    let mut s = String::from("<member>");
    s.push_str(&format!("<Name>{}</Name>", xml_escape(&rule.name)));
    s.push_str(&format!("<State>{}</State>", xml_escape(&rule.state)));
    s.push_str(&format!("<Schema>{}</Schema>", xml_escape(&rule.schema)));
    s.push_str(&format!(
        "<Definition>{}</Definition>",
        xml_escape(&rule.definition)
    ));
    s.push_str(&format!("<ManagedRule>{}</ManagedRule>", rule.managed));
    s.push_str(&format!(
        "<ApplyOnTransformedLogs>{}</ApplyOnTransformedLogs>",
        rule.apply_on_transformed_logs
    ));
    s.push_str("</member>");
    s
}
