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

use derive_more::derive::{Display, From};
#[cfg(feature = "ssr")]
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use super::user::User;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Client {
    id: uuid::Uuid,
    user_id: uuid::Uuid,
    name: ClientName,
    secret: ClientSecret,
    redirect_uri: RedirectUri,
}

impl Client {
    pub fn new(
        id: uuid::Uuid,
        user_id: uuid::Uuid,
        name: ClientName,
        secret: ClientSecret,
        redirect_uri: RedirectUri,
    ) -> Self {
        Self {
            id,
            user_id,
            name,
            secret,
            redirect_uri,
        }
    }

    pub fn id(&self) -> uuid::Uuid {
        self.id
    }

    pub fn name(&self) -> &ClientName {
        &self.name
    }

    pub fn redirect_uri(&self) -> &RedirectUri {
        &self.redirect_uri
    }
}

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

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

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

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

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

#[cfg(feature = "ssr")]
impl ClientSecret {
    pub fn new() -> Self {
        let token: String = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(32)
            .map(char::from)
            .collect();

        Self(token)
    }
}

#[cfg(feature = "ssr")]
impl Default for ClientSecret {
    fn default() -> Self {
        Self::new()
    }
}

#[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())
    }
}

#[cfg(feature = "ssr")]
#[derive(Debug, Error)]
pub enum CreateAuthorizationCodeError {
    #[error(transparent)]
    Unknown(#[from] anyhow::Error),
    // to be extended as new error scenarios are introduced
}

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

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

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

#[cfg(feature = "ssr")]
impl AuthorizationCode {
    pub fn new() -> Self {
        let token: String = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(64)
            .map(char::from)
            .collect();

        Self(token)
    }
}

#[cfg(feature = "ssr")]
impl Default for AuthorizationCode {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AuthorizedClient {
    client: Client,
    user: User,
}

#[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, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum CodeChallengeMethod {
    #[default]
    #[serde(rename = "plain")]
    Plain,
    #[serde(rename = "S256")]
    Sha256,
}

impl Display for CodeChallengeMethod {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                CodeChallengeMethod::Plain => "plain",
                CodeChallengeMethod::Sha256 => "S256",
            }
        )
    }
}

impl FromStr for CodeChallengeMethod {
    type Err = crate::domain::api::models::oauth::CodeChallengeMethodError;

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

impl TryFrom<String> for CodeChallengeMethod {
    type Error = crate::domain::api::models::oauth::CodeChallengeMethodError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::from_str(&value)
    }
}

#[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 = crate::domain::api::models::oauth::ResponseTypeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "code" => Ok(Self::Code),
            _ => Err(crate::domain::api::models::oauth::ResponseTypeError::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,
        }
    }

    pub fn client_id(&self) -> uuid::Uuid {
        self.client_id
    }

    pub fn response_type(&self) -> ResponseType {
        self.response_type.clone()
    }

    pub fn state(&self) -> Option<String> {
        self.state.clone()
    }

    pub fn code_challenge(&self) -> String {
        self.code_challenge.clone()
    }

    pub fn code_challenge_method(&self) -> Option<CodeChallengeMethod> {
        self.code_challenge_method.clone()
    }

    pub fn redirect_uri(&self) -> RedirectUri {
        self.redirect_uri.clone()
    }

    pub fn scope(&self) -> Option<String> {
        self.scope.clone()
    }
}

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

impl AuthorizationResponse {
    pub fn new(code: AuthorizationCode, state: Option<String>) -> Self {
        Self { code, state }
    }

    pub fn code(&self) -> AuthorizationCode {
        self.code.clone()
    }

    pub fn state(&self) -> Option<String> {
        self.state.clone()
    }
}

#[derive(Debug, Error)]
pub enum TokenError {
    #[error("Invalid Token Request")]
    InvalidRequest,
    #[error(transparent)]
    Unknown(#[from] anyhow::Error),
    // to be extended as new error scenarios are introduced
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenSubject {
    #[serde(rename = "a")]
    user_id: uuid::Uuid,
    #[serde(rename = "b")]
    client_id: uuid::Uuid,
    #[serde(skip)]
    code_challenge: String,
    #[serde(skip)]
    code_challenge_method: CodeChallengeMethod,
}

impl TokenSubject {
    pub fn new(
        user_id: uuid::Uuid,
        client_id: uuid::Uuid,
        code_challenge: String,
        code_challenge_method: CodeChallengeMethod,
    ) -> Self {
        Self {
            user_id,
            client_id,
            code_challenge,
            code_challenge_method,
        }
    }

    pub fn user_id(&self) -> uuid::Uuid {
        self.user_id
    }

    pub fn client_id(&self) -> uuid::Uuid {
        self.client_id
    }

    pub fn code_challenge(&self) -> String {
        self.code_challenge.clone()
    }

    pub fn code_challenge_method(&self) -> CodeChallengeMethod {
        self.code_challenge_method.clone()
    }
}
