use std::{
    fmt::{Display, Formatter},
    str::FromStr,
};

use strum::IntoStaticStr;
use winnow::{
    ModalResult,
    Parser,
    combinator::{alt, cut_err, eof, not, opt},
    error::{StrContext, StrContextValue},
    token::{rest, take},
};

use crate::{
    error::Error,
    identifiers::{IdentifierString, SegmentPath},
};

/// Combines a [`Role`] and a [`Mode`] to describe the context in which signature verifiers
/// in a directory structure are used.
///
/// The combination of [`Role`] and [`Mode`] reflects one directory layer in the VOA directory
/// hierarchy. Purpose paths have values such as: `packages`, `trust-anchor-packages`,
/// `repository-metadata`.
///
/// See <https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/#purpose>
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Purpose {
    role: Role,
    mode: Mode,
}

impl Purpose {
    /// Creates a new [`Purpose`].
    ///
    /// # Examples
    ///
    /// ```
    /// use voa_core::identifiers::{Mode, Purpose, Role};
    ///
    /// # fn main() -> Result<(), voa_core::Error> {
    /// Purpose::new(Role::Packages, Mode::ArtifactVerifier);
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(role: Role, mode: Mode) -> Self {
        Self { role, mode }
    }

    /// Recognizes a [`Purpose`] in a string slice.
    ///
    /// # Errors
    ///
    /// # Examples
    ///
    /// ```
    /// use voa_core::identifiers::{Mode, Purpose, Role};
    /// use winnow::Parser;
    ///
    /// # fn main() -> Result<(), voa_core::Error> {
    /// assert_eq!(
    ///     Purpose::parser.parse("trust-anchor-test")?,
    ///     Purpose::new(Role::Custom("test".parse()?), Mode::TrustAnchor),
    /// );
    /// assert_eq!(
    ///     Purpose::parser.parse("test")?,
    ///     Purpose::new(Role::Custom("test".parse()?), Mode::ArtifactVerifier),
    /// );
    /// # Ok(())
    /// # }
    /// ```
    pub fn parser(input: &mut &str) -> ModalResult<Self> {
        // Check whether we have a `trust-anchor-` prefix.
        // The case of a pure `trust-anchor` string, is handled in the `Role::Custom` parser.
        let trust_anchor = opt("trust-anchor-").parse_next(input)?;

        let mode = if trust_anchor.is_some() {
            Mode::TrustAnchor
        } else {
            Mode::ArtifactVerifier
        };

        // Take the rest of the input and create a `Role` from it.
        let role = Role::parser.parse_next(input)?;

        Ok(Self { role, mode })
    }

    /// A [`String`] representation of this Purpose specifier.
    ///     
    /// This function produces the exact representation specified in
    /// <https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/#purpose>
    pub fn purpose_to_string(&self) -> String {
        match self.mode {
            Mode::TrustAnchor => format!("{}-{}", self.mode, self.role),
            Mode::ArtifactVerifier => format!("{}", self.role),
        }
    }

    /// Returns the path segment for this [`Purpose`]
    pub(crate) fn path_segment(&self) -> Result<SegmentPath, Error> {
        self.purpose_to_string().try_into()
    }

    /// Checks whether `self` uses [`Mode::TrustAnchor`].
    ///
    /// Returns `true` if `self` uses [`Mode::TrustAnchor`], `false` otherwise.
    pub fn is_trust_anchor(&self) -> bool {
        self.mode == Mode::TrustAnchor
    }

    /// Consumes `self` and returns a [`Purpose`] which uses [`Mode::TrustAnchor`].
    pub fn to_trust_anchor(mut self) -> Self {
        self.mode = Mode::TrustAnchor;
        self
    }
}

impl Display for Purpose {
    fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result {
        write!(fmt, "{}", self.purpose_to_string())
    }
}

impl FromStr for Purpose {
    type Err = crate::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self::parser.parse(s)?)
    }
}

/// Acts as a trust domain that is associated with a set of verifiers.
///
/// A [`Role`] is always combined with a [`Mode`] and in combination forms a [`Purpose`].
/// E.g. [`Role::Packages`] combined with [`Mode::TrustAnchor`] specify the purpose path
/// `trust-anchor-packages`.
///
/// See <https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/#purpose>
#[derive(Clone, Debug, strum::Display, Eq, Hash, IntoStaticStr, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Role {
    /// Identifies verifiers used for verifying package signatures.
    #[strum(to_string = "packages")]
    #[cfg_attr(feature = "serde", serde(rename = "packages"))]
    Packages,

    /// Identifies verifiers used for verifying package repository metadata signatures.
    #[strum(to_string = "repository-metadata")]
    #[cfg_attr(feature = "serde", serde(rename = "repository-metadata"))]
    RepositoryMetadata,

    /// Identifies verifiers used for verifying OS image signatures.
    #[strum(to_string = "image")]
    #[cfg_attr(feature = "serde", serde(rename = "image"))]
    Image,

    /// Identifies verifiers used for verifying OS image signatures.
    #[strum(to_string = "{0}")]
    #[cfg_attr(feature = "serde", serde(rename = "custom"))]
    Custom(CustomRole),
}

impl Role {
    /// Recognizes a [`Role`] in a string slice.
    ///
    /// Consumes all of its `input`.
    ///
    /// # Errors
    ///
    /// Returns an error if none of the variants of [`Role`] can be created from `input`.
    ///
    /// # Examples
    ///
    /// ```
    /// use voa_core::identifiers::Role;
    /// use winnow::Parser;
    ///
    /// # fn main() -> Result<(), voa_core::Error> {
    /// assert_eq!(Role::parser.parse("packages")?, Role::Packages);
    /// assert_eq!(
    ///     Role::parser.parse("repository-metadata")?,
    ///     Role::RepositoryMetadata
    /// );
    /// assert_eq!(Role::parser.parse("image")?, Role::Image);
    /// assert_eq!(
    ///     Role::parser.parse("custom")?,
    ///     Role::Custom("custom".parse()?)
    /// );
    /// # Ok(())
    /// # }
    /// ```
    pub fn parser(input: &mut &str) -> ModalResult<Self> {
        // Perform a direct mapping of known static variants.
        //
        // Usually, such logic would be handled by strum's `EnumString` impl.
        // However, since this enum contains the special `Custom` struct variant, we have to
        // perform manual mapping.
        cut_err(alt((
            ("packages", eof).value(Role::Packages),
            ("repository-metadata", eof).value(Role::RepositoryMetadata),
            ("image", eof).value(Role::Image),
            // At this point, we know that we have a custom string and delegate to the
            // `CustomRole` parser, which will thrown an contextualized error if
            // any invalid characters are provided.
            rest.and_then(CustomRole::parser).map(Self::Custom),
        )))
        .context(StrContext::Label("a valid VOA role"))
        .context(StrContext::Expected(StrContextValue::Description(
            "'packages', 'repository-metadata', 'image' or a custom value",
        )))
        .parse_next(input)
    }
}

impl FromStr for Role {
    type Err = Error;

    /// Creates a new [`Role`] from a string slice.
    ///
    /// # Note
    ///
    /// Delegates to [`Role::parser`].
    ///
    /// # Errors
    ///
    /// Returns an error if [`Role::parser`] fails.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self::parser.parse(s)?)
    }
}

/// A custom value for a [`Role`]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub struct CustomRole(IdentifierString);

impl CustomRole {
    /// Creates a new [`CustomRole`] instance.
    ///
    /// # Errors
    ///
    /// Returns an error if the `role` starts with the string "trust-anchor-".
    /// This prefix is disallowed to avoid overlaps with [`Mode::TrustAnchor`].
    pub fn new(role: IdentifierString) -> Result<Self, Error> {
        if role.as_str().starts_with("trust-anchor-") {
            return Err(Error::IllegalIdentifier {
                context: "Custom role may not start with 'trust-anchor-'",
            });
        }

        Ok(Self(role))
    }

    /// Recognizes a [`CustomRole`] in a string slice.
    ///
    /// Consumes all of its `input`.
    ///
    /// # Errors
    ///
    /// Returns an error if
    ///
    /// - `input` starts with the string representation of [`Mode::TrustAnchor`],
    /// - or one of the characters in `input` is not covered by [`IdentifierString::valid_chars`].
    ///
    /// # Examples
    ///
    /// ```
    /// use voa_core::identifiers::CustomRole;
    /// use winnow::Parser;
    ///
    /// # fn main() -> Result<(), voa_core::Error> {
    /// assert_eq!(CustomRole::parser.parse("test")?.to_string(), "test");
    /// assert_eq!(
    ///     CustomRole::parser
    ///         .parse("something-very-special")?
    ///         .to_string(),
    ///     "something-very-special"
    /// );
    /// assert_eq!(
    ///     CustomRole::parser
    ///         .parse("something-very-trust-anchor")?
    ///         .to_string(),
    ///     "something-very-trust-anchor"
    /// );
    /// assert!(CustomRole::parser.parse("trust-anchor").is_err());
    /// assert!(CustomRole::parser.parse("trust-anchor-test").is_err());
    /// # Ok(())
    /// # }
    /// ```
    pub fn parser(input: &mut &str) -> ModalResult<Self> {
        // Make sure that we **don't** start with a trust-anchor prefix.
        // That prefix is strictly forbidden.
        // This handles both cases of `trust-anchor` and `trust-anchor-`.
        cut_err(not("trust-anchor"))
            .context(StrContext::Label(
                "custom VOA role. Custom roles may not start with 'trust-anchor'.",
            ))
            .parse_next(input)?;

        // Take the rest of the input and create a IdentifierString from it.
        let id_string = cut_err(rest.try_map(IdentifierString::from_str))
            .context(StrContext::Label("role in a VOA purpose"))
            .parse_next(input)?;

        Ok(Self(id_string))
    }
}

impl Display for CustomRole {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl FromStr for CustomRole {
    type Err = crate::Error;

    /// Creates a new [`CustomRole`] from a string slice.
    ///
    /// # Errors
    ///
    /// Returns an error if either [`IdentifierString::from_str`] or [`CustomRole::new`] fail.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self::parser.parse(s)?)
    }
}

impl From<CustomRole> for Role {
    fn from(val: CustomRole) -> Self {
        Role::Custom(val)
    }
}

/// Component of a [`Purpose`] to distinguish between direct artifact verifiers and trust anchors.
///
/// A [`Mode`] is always combined with a [`Role`] and in combination forms a [`Purpose`].
/// E.g. [`Role::Packages`] combined with [`Mode::TrustAnchor`] specify the purpose path
/// `trust-anchor-packages`.
///
/// See <https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/#purpose>
#[derive(
    Clone, Copy, Debug, strum::Display, Eq, Hash, IntoStaticStr, Ord, PartialEq, PartialOrd,
)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum Mode {
    /// Identifies verifiers that are used to directly validate signatures on artifacts.
    ///
    /// For a [`Role`] `foo`, in artifact verifier mode, the purpose is represented as `foo`.
    /// That is, [`Mode::ArtifactVerifier`] is represented in the purpose by an empty string
    /// (and no additional dash).
    #[strum(serialize = "")]
    ArtifactVerifier,

    /// Identifies verifiers that are used to ascertain the authenticity of verifiers used to
    /// directly validate signatures on artifacts.
    ///
    /// For a [`Role`] `foo`, in trust anchor mode, the purpose is represented as
    /// `trust-anchor-foo`.
    /// That is, [`Mode::TrustAnchor`] is represented in the purpose by the string "trust-anchor"
    /// (and an additional dash).
    #[strum(serialize = "trust-anchor")]
    TrustAnchor,
}

impl Mode {
    /// Recognizes a [`Mode`] in a string slice.
    ///
    /// # Errors
    ///
    /// # Examples
    ///
    /// ```
    /// use voa_core::identifiers::Mode;
    /// use winnow::Parser;
    ///
    /// # fn main() -> Result<(), voa_core::Error> {
    /// assert_eq!(Mode::parser.parse("")?, Mode::ArtifactVerifier);
    /// assert_eq!(Mode::parser.parse("trust-anchor")?, Mode::TrustAnchor);
    ///
    /// assert!(Mode::parser.parse("test").is_err());
    /// # Ok(())
    /// # }
    /// ```
    pub fn parser(input: &mut &str) -> ModalResult<Self> {
        if input.is_empty() {
            return Ok(Self::ArtifactVerifier);
        }

        take(input.len())
            .and_then(Into::<&str>::into(Self::TrustAnchor))
            .context(StrContext::Label("trust-anchor mode for VOA purpose"))
            .context(StrContext::Expected(StrContextValue::StringLiteral(
                Mode::TrustAnchor.into(),
            )))
            .parse_next(input)?;

        Ok(Mode::TrustAnchor)
    }
}

impl FromStr for Mode {
    type Err = crate::Error;

    /// Creates a new [`Mode`] from a string slice.
    ///
    /// # Note
    ///
    /// Delegates to [`Mode::parser`].
    ///
    /// # Errors
    ///
    /// Returns an error if [`Mode::parser`] fails.
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self::parser.parse(s)?)
    }
}

#[cfg(test)]
mod tests {
    use rstest::rstest;
    use testresult::TestResult;

    use super::*;

    #[rstest]
    #[case(Mode::ArtifactVerifier, "")]
    #[case(Mode::TrustAnchor, "trust-anchor")]
    fn mode_display(#[case] mode: Mode, #[case] display: &str) {
        assert_eq!(format!("{mode}"), display);
    }

    #[rstest]
    #[case(Role::Packages, "packages")]
    #[case(Role::Image, "image")]
    #[case(Role::RepositoryMetadata, "repository-metadata")]
    #[case(Role::Custom(CustomRole::new("foo".parse()?)?), "foo")]
    fn role_display(#[case] role: Role, #[case] display: &str) -> TestResult {
        assert_eq!(format!("{role}"), display);
        Ok(())
    }

    #[rstest]
    #[case(Purpose::new(Role::Packages, Mode::ArtifactVerifier), "packages")]
    #[case(
        Purpose::new(Role::Packages, Mode::TrustAnchor),
        "trust-anchor-packages"
    )]
    #[case(Purpose::new(Role::Packages, Mode::ArtifactVerifier), "packages")]
    #[case(
        Purpose::new(Role::RepositoryMetadata, Mode::TrustAnchor),
        "trust-anchor-repository-metadata"
    )]
    #[case(Purpose::new(
                Role::Custom(CustomRole::new("foo".parse()?)?),
                Mode::ArtifactVerifier
        ), "foo")]
    #[case(Purpose::new(
                Role::Custom(CustomRole::new("foo".parse()?)?),
                Mode::TrustAnchor
        ), "trust-anchor-foo")]
    fn purpose_display(#[case] purpose: Purpose, #[case] display: &str) -> TestResult {
        assert_eq!(format!("{purpose}"), display);
        Ok(())
    }

    #[test]
    fn illegal_custom_role() -> TestResult {
        let res = CustomRole::new("trust-anchor-foo".parse()?);
        assert!(matches!(res, Err(Error::IllegalIdentifier { .. })));

        Ok(())
    }

    #[rstest]
    #[case::no_mode("test")]
    #[case::no_mode("trust-anchor-test")]
    #[case::no_mode("trust-anchor-test-foo-bar")]
    #[case::no_mode("test-foo-bar")]
    fn purpose_from_str_valid(#[case] input: &str) -> TestResult {
        assert_eq!(Purpose::from_str(input)?.to_string(), input);
        Ok(())
    }

    #[test]
    fn purpose_is_trust_anchor() -> TestResult {
        let purpose: Purpose = "trust-anchor-foo".parse()?;
        assert!(purpose.is_trust_anchor());
        Ok(())
    }

    #[test]
    fn purpose_is_not_trust_anchor() -> TestResult {
        let purpose: Purpose = "foo".parse()?;
        assert!(!purpose.is_trust_anchor());
        Ok(())
    }

    #[rstest]
    #[case::artifact_verifier("foo".parse()?, "trust-anchor-foo".parse()?)]
    #[case::trust_anchor("trust-anchor-foo".parse()?, "trust-anchor-foo".parse()?)]
    fn purpose_to_trust_anchor(#[case] purpose: Purpose, #[case] output: Purpose) -> TestResult {
        assert_eq!(purpose.to_trust_anchor(), output);
        Ok(())
    }

    #[rstest]
    #[case::custom("test", Role::Custom(CustomRole::new("test".parse()?)?))]
    #[case::packages("packages", Role::Packages)]
    #[case::repository_metadata("repository-metadata", Role::RepositoryMetadata)]
    #[case::image("image", Role::Image)]
    fn role_from_str_succeeds(#[case] input: &str, #[case] expected: Role) -> TestResult {
        assert_eq!(Role::from_str(input)?, expected);
        Ok(())
    }

    #[rstest]
    #[case::invalid_character(
        "test$",
        "test$\n^\ninvalid role in a VOA purpose\nexpected 'packages', 'repository-metadata', 'image' or a custom value\nParser error:\ntest$\n    ^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
    )]
    #[case::all_caps(
        "TEST",
        "TEST\n^\ninvalid role in a VOA purpose\nexpected 'packages', 'repository-metadata', 'image' or a custom value\nParser error:\nTEST\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
    )]
    #[case::empty_string(
        "",
        "\n^\ninvalid role in a VOA purpose\nexpected 'packages', 'repository-metadata', 'image' or a custom value\nParser error:\n\n^\ninvalid VOA identifier string\nexpected lowercase alphanumeric ASCII characters, `_`, `-`, `.`"
    )]
    fn role_from_str_invalid_chars(#[case] input: &str, #[case] error_msg: &str) -> TestResult {
        match Role::from_str(input) {
            Ok(id_string) => {
                panic!("Should have failed to parse {input} but succeeded: {id_string}");
            }
            Err(error) => {
                assert_eq!(error.to_string(), format!("Parser error:\n{error_msg}"));
                Ok(())
            }
        }
    }

    #[rstest]
    #[case::artifact_verifier("", Mode::ArtifactVerifier)]
    #[case::trust_anchor("trust-anchor", Mode::TrustAnchor)]
    fn mode_from_str_succeeds(#[case] input: &str, #[case] expected: Mode) -> TestResult {
        assert_eq!(Mode::from_str(input)?, expected);
        Ok(())
    }

    #[rstest]
    #[case::invalid_character(
        "test$",
        "test$\n^\ninvalid trust-anchor mode for VOA purpose\nexpected `trust-anchor`"
    )]
    #[case::all_caps(
        "TEST",
        "TEST\n^\ninvalid trust-anchor mode for VOA purpose\nexpected `trust-anchor`"
    )]
    fn mode_from_str_invalid_chars(#[case] input: &str, #[case] error_msg: &str) -> TestResult {
        match Mode::from_str(input) {
            Ok(id_string) => {
                panic!("Should have failed to parse {input} but succeeded: {id_string}");
            }
            Err(error) => {
                assert_eq!(error.to_string(), format!("Parser error:\n{error_msg}"));
                Ok(())
            }
        }
    }
}
