use std::str::FromStr;

use derive_more::{derive::From, Display};
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::REDIRECT_URI;

#[derive(
    Clone, Debug, Display, From, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
pub struct RedirectUri(String);

impl RedirectUri {
    pub fn new(uri: &str) -> Self {
        Self(uri.to_string())
    }
}

impl From<&str> for RedirectUri {
    fn from(value: &str) -> Self {
        Self::new(value)
    }
}

#[derive(Clone, Display, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(from = "String")]
pub struct CodeVerifier(String);

impl From<String> for CodeVerifier {
    fn from(value: String) -> Self {
        Self(value)
    }
}

impl From<&str> for CodeVerifier {
    fn from(value: &str) -> Self {
        Self(value.to_string())
    }
}

impl CodeVerifier {
    pub fn new() -> Self {
        let token: String = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(120)
            .map(char::from)
            .collect();

        Self(token)
    }
}

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

#[derive(Debug, Error)]
pub enum CodeChallengeMethodError {
    #[error("Code challenge method is not valid.")]
    Invalid,
    #[error(transparent)]
    Unknown(#[from] anyhow::Error),
    // to be extended as new error scenarios are introduced
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum CodeChallengeMethod {
    #[serde(rename = "plain")]
    Plain,
    #[serde(rename = "S256")]
    Sha256,
}

impl FromStr for CodeChallengeMethod {
    type Err = CodeChallengeMethodError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "plain" => Ok(Self::Plain),
            "S256" => Ok(Self::Sha256),
            _ => Err(CodeChallengeMethodError::Invalid),
        }
    }
}

#[derive(Debug, Error)]
pub enum ResponseTypeError {
    #[error("The response type is not valid.")]
    Invalid,
    #[error(transparent)]
    Unknown(#[from] anyhow::Error),
    // to be extended as new error scenarios are introduced
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum ResponseType {
    #[serde(rename = "code")]
    Code,
}

impl Display for ResponseType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                ResponseType::Code => "code",
            }
        )
    }
}

impl FromStr for ResponseType {
    type Err = ResponseTypeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "code" => Ok(Self::Code),
            _ => Err(ResponseTypeError::Invalid),
        }
    }
}

#[derive(Debug, Error)]
pub enum GrantTypeError {
    #[error("The grant type is not valid.")]
    Invalid,
    #[error(transparent)]
    Unknown(#[from] anyhow::Error),
    // to be extended as new error scenarios are introduced
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum GrantType {
    #[serde(rename = "authorization_code")]
    AuthorizationCode,
}

impl Display for GrantType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                GrantType::AuthorizationCode => "authorization_code",
            }
        )
    }
}

impl FromStr for GrantType {
    type Err = GrantTypeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "authorization_code" => Ok(Self::AuthorizationCode),
            _ => Err(GrantTypeError::Invalid),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthorizeRequest {
    client_id: uuid::Uuid,
    response_type: ResponseType, // Make type (enum:code,)
    state: Option<String>,       // random string for CSRF protection
    code_challenge: String,      // pkce
    code_challenge_method: Option<CodeChallengeMethod>, // Make type (enum:sha256,) hashing algo
    redirect_uri: RedirectUri,   // Make type
    scope: Option<String>,       // space seperated string with permissions
}

impl AuthorizeRequest {
    pub fn new(
        client_id: uuid::Uuid,
        response_type: ResponseType,
        state: Option<String>,
        code_challenge: String,
        code_challenge_method: Option<CodeChallengeMethod>,
        redirect_uri: RedirectUri,
        scope: Option<String>,
    ) -> Self {
        Self {
            client_id,
            response_type,
            state,
            code_challenge,
            code_challenge_method,
            redirect_uri,
            scope,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthorizationResponse {
    code: String,
    state: Option<String>,
}

impl TryFrom<String> for AuthorizationResponse {
    type Error = anyhow::Error;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        let mut parts = s.split('?');
        let Some(protocol) = parts.next() else {
            return Err(anyhow::anyhow!("Invalid AuthorizationResponse"));
        };

        if protocol != REDIRECT_URI {
            return Err(anyhow::anyhow!("Invalid AuthorizationResponse"));
        }

        let Some(qs) = parts.next() else {
            return Err(anyhow::anyhow!("Invalid AuthorizationResponse"));
        };

        if parts.count() > 0 {
            return Err(anyhow::anyhow!("Invalid AuthorizationResponse"));
        }

        Ok(serde_qs::from_str(qs)?)
    }
}

impl AuthorizationResponse {
    pub fn code(&self) -> String {
        self.code.clone()
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthorizationCodeRequest {
    grant_type: GrantType,
    code: String,
    redirect_uri: RedirectUri,
    client_id: uuid::Uuid,
    code_verifier: CodeVerifier,
}

impl AuthorizationCodeRequest {
    pub fn new(
        grant_type: GrantType,
        code: String,
        redirect_uri: RedirectUri,
        client_id: uuid::Uuid,
        code_verifier: CodeVerifier,
    ) -> Self {
        Self {
            grant_type,
            code,
            redirect_uri,
            client_id,
            code_verifier,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AuthorizationCodeResponse {
    token: String,
}

impl AuthorizationCodeResponse {
    pub fn token(&self) -> String {
        self.token.clone()
    }
}
