use std::{str::FromStr, string::ToString};

use ring::hmac;
use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};

#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Default, Eq, PartialEq, Clone, Copy)]
#[repr(u32)]
// #[enum_type(name = "OTPMethod")]
pub enum Method {
    // #[enum_value(name = "TOTP")]
    #[default]
    TOTP = 0,
    // #[enum_value(name = "HOTP")]
    HOTP = 1,
    Steam = 2,
}

impl Serialize for Method {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for Method {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(Self::from_str(&String::deserialize(deserializer)?).unwrap())
    }
}

impl From<u32> for Method {
    fn from(u: u32) -> Self {
        match u {
            1 => Self::HOTP,
            2 => Self::Steam,
            _ => Self::default(),
        }
    }
}

impl Method {
    pub fn is_time_based(self) -> bool {
        matches!(self, Self::TOTP | Self::Steam)
    }

    pub fn is_event_based(self) -> bool {
        matches!(self, Self::HOTP)
    }

    pub fn to_string(self) -> String {
        match self {
            Self::HOTP => "Counter-based".to_string(),
            Self::TOTP => "Time-based".to_string(),
            Self::Steam => "Steam".to_string(),
        }
    }
}

impl FromStr for Method {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_ref() {
            "totp" | "otp" => Ok(Self::TOTP),
            "hotp" => Ok(Self::HOTP),
            "steam" => Ok(Self::Steam),
            _ => anyhow::bail!("Unsupported Method"),
        }
    }
}

impl ToString for Method {
    fn to_string(&self) -> String {
        match *self {
            Self::TOTP => "totp",
            Self::HOTP => "hotp",
            Self::Steam => "steam",
        }
        .to_string()
    }
}

#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Default, Eq, PartialEq, Clone, Copy)]
#[repr(u32)]
// #[enum_type(name = "OTPAlgorithm")]
pub enum Algorithm {
    // #[enum_value(name = "SHA1")]
    #[default]
    SHA1 = 0,
    // #[enum_value(name = "SHA256")]
    SHA256 = 1,
    // #[enum_value(name = "SHA512")]
    SHA512 = 2,
}

impl Serialize for Algorithm {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for Algorithm {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(Self::from_str(&String::deserialize(deserializer)?).unwrap())
    }
}

impl Algorithm {
    pub fn to_string(self) -> String {
        match self {
            Self::SHA1 => "SHA-1".to_string(),
            Self::SHA256 => "SHA-256".to_string(),
            Self::SHA512 => "SHA-512".to_string(),
        }
    }
}

impl FromStr for Algorithm {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_ref() {
            "sha1" => Ok(Self::SHA1),
            "sha256" => Ok(Self::SHA256),
            "sha512" => Ok(Self::SHA512),
            _ => anyhow::bail!("Unsupported HMAC-algorithm"),
        }
    }
}

impl ToString for Algorithm {
    fn to_string(&self) -> String {
        match *self {
            Self::SHA1 => "sha1",
            Self::SHA256 => "sha256",
            Self::SHA512 => "sha512",
        }
        .to_string()
    }
}

impl From<Algorithm> for hmac::Algorithm {
    fn from(h: Algorithm) -> Self {
        match h {
            Algorithm::SHA1 => hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
            Algorithm::SHA256 => hmac::HMAC_SHA256,
            Algorithm::SHA512 => hmac::HMAC_SHA512,
        }
    }
}

impl From<u32> for Algorithm {
    fn from(u: u32) -> Self {
        match u {
            1 => Self::SHA256,
            2 => Self::SHA512,
            _ => Self::default(),
        }
    }
}
