use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SyncPreference {
    RrdpThenRsync,
    RsyncOnly,
}

impl Default for SyncPreference {
    fn default() -> Self {
        Self::RrdpThenRsync
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaFailedFetchPolicy {
    ReuseCurrentInstanceVcir,
    StopAllOutput,
}

impl Default for CaFailedFetchPolicy {
    fn default() -> Self {
        Self::ReuseCurrentInstanceVcir
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SignedObjectFailurePolicy {
    DropObject,
    DropPublicationPoint,
}

impl Default for SignedObjectFailurePolicy {
    fn default() -> Self {
        Self::DropObject
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ResourceValidationMode {
    #[serde(rename = "validation-update-03")]
    ValidationUpdate03,
    #[serde(rename = "rfc6487")]
    Rfc6487,
}

impl Default for ResourceValidationMode {
    fn default() -> Self {
        Self::ValidationUpdate03
    }
}

impl ResourceValidationMode {
    pub fn parse_cli_value(raw: &str) -> Result<Self, String> {
        match raw.trim() {
            "validation-update-03" => Ok(Self::ValidationUpdate03),
            "rfc6487" => Ok(Self::Rfc6487),
            value => Err(format!(
                "unknown resource validation mode: {value}; supported: validation-update-03,rfc6487"
            )),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct StrictPolicy {
    pub name: bool,
    pub cms_der: bool,
    pub signed_attrs: bool,
}

impl StrictPolicy {
    pub fn none() -> Self {
        Self::default()
    }

    pub fn all() -> Self {
        Self {
            name: true,
            cms_der: true,
            signed_attrs: true,
        }
    }

    pub fn parse_cli_spec(spec: Option<&str>) -> Result<Self, String> {
        let Some(spec) = spec else {
            return Ok(Self::all());
        };
        let spec = spec.trim();
        if spec.is_empty() || spec == "all" {
            return Ok(Self::all());
        }
        if spec == "none" {
            return Ok(Self::none());
        }

        let mut out = Self::none();
        for raw in spec.split(',') {
            let item = raw.trim();
            match item {
                "name" => out.name = true,
                "cms-der" | "cms_der" => out.cms_der = true,
                "signed-attrs" | "signed_attrs" => out.signed_attrs = true,
                "all" => out = Self::all(),
                "none" => out = Self::none(),
                "" => return Err("empty strict policy name".to_string()),
                _ => {
                    return Err(format!(
                        "unknown strict policy: {item}; supported: name,cms-der,signed-attrs,all,none"
                    ));
                }
            }
        }
        Ok(out)
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Policy {
    pub sync_preference: SyncPreference,
    pub ca_failed_fetch_policy: CaFailedFetchPolicy,
    pub signed_object_failure_policy: SignedObjectFailurePolicy,
    pub resource_validation_mode: ResourceValidationMode,
    pub strict: StrictPolicy,
    /// Ephemeral verification-only override.  This is deliberately excluded
    /// from policy files, contracts, and policy fingerprints: it only selects
    /// the already contract-bound VCIR fallback branch while replaying a run.
    #[serde(skip)]
    pub verification_forced_vcir_reuse_manifest_uris: BTreeSet<String>,
}

impl Default for Policy {
    fn default() -> Self {
        Self {
            sync_preference: SyncPreference::default(),
            ca_failed_fetch_policy: CaFailedFetchPolicy::default(),
            signed_object_failure_policy: SignedObjectFailurePolicy::default(),
            resource_validation_mode: ResourceValidationMode::default(),
            strict: StrictPolicy::default(),
            verification_forced_vcir_reuse_manifest_uris: BTreeSet::new(),
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub enum PolicyParseError {
    #[error("policy TOML parse error: {0}")]
    Toml(String),
}

impl Policy {
    pub fn from_toml_str(s: &str) -> Result<Self, PolicyParseError> {
        toml::from_str(s).map_err(|e| PolicyParseError::Toml(e.to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strict_policy_parses_cli_specs() {
        assert_eq!(
            StrictPolicy::parse_cli_spec(None).unwrap(),
            StrictPolicy::all()
        );
        assert_eq!(
            StrictPolicy::parse_cli_spec(Some("name,signed-attrs")).unwrap(),
            StrictPolicy {
                name: true,
                cms_der: false,
                signed_attrs: true,
            }
        );
        assert_eq!(
            StrictPolicy::parse_cli_spec(Some("none")).unwrap(),
            StrictPolicy::none()
        );
        assert!(StrictPolicy::parse_cli_spec(Some("bogus")).is_err());
    }

    #[test]
    fn resource_validation_mode_parses_cli_values() {
        assert_eq!(
            ResourceValidationMode::parse_cli_value("validation-update-03").unwrap(),
            ResourceValidationMode::ValidationUpdate03
        );
        assert_eq!(
            ResourceValidationMode::parse_cli_value("rfc6487").unwrap(),
            ResourceValidationMode::Rfc6487
        );
        assert!(ResourceValidationMode::parse_cli_value("bogus").is_err());
    }

    #[test]
    fn policy_toml_accepts_strict_table() {
        let policy = Policy::from_toml_str(
            r#"
            [strict]
            name = true
            cms_der = true
            signed_attrs = false
            "#,
        )
        .expect("parse policy");
        assert_eq!(
            policy.strict,
            StrictPolicy {
                name: true,
                cms_der: true,
                signed_attrs: false,
            }
        );
    }

    #[test]
    fn policy_toml_accepts_resource_validation_mode() {
        let policy = Policy::from_toml_str(
            r#"
            resource_validation_mode = "rfc6487"
            "#,
        )
        .expect("parse policy");
        assert_eq!(
            policy.resource_validation_mode,
            ResourceValidationMode::Rfc6487
        );
    }
}
