use serde::{Serialize, Deserialize};
use url::Url;

mod scopes;
pub use self::scopes::{Scope, Scopes};
mod pkce;
pub use self::pkce::{PKCEMethod, PKCEVerifier, PKCEChallenge};

#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum IntrospectionEndpointAuthMethod {
    Bearer,
    #[serde(rename = "snake_case")]
    ClientSecretPost,
    #[serde(rename = "snake_case")]
    ClientSecretBasic,
    #[serde(rename = "snake_case")]
    TlsClientAuth,
    #[serde(rename = "snake_case")]
    SelfSignedTlsClientAuth
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RevocationEndpointAuthMethod {
    None
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseType {
    Code
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GrantType {
    AuthorizationCode,
    RefreshToken
}

/// OAuth 2.0 Authorization Server Metadata in application to the IndieAuth protocol.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Metadata {
    /// The server's issuer identifier. The issuer identifier is a URL
    /// that uses the "https" scheme and has no query or fragment
    /// components. The identifier MUST be a prefix of the
    /// `indieauth-metadata` URL.
    pub issuer: Url,
    /// The Authorization Endpoint
    pub authorization_endpoint: Url,
    /// The Token Endpoint
    pub token_endpoint: Url,
    /// The Introspection Endpoint
    pub introspection_endpoint: Url,
    /// JSON array containing a list of client authentication methods supported by this introspection endpoint.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub introspection_endpoint_auth_methods_supported: Option<Vec<IntrospectionEndpointAuthMethod>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub revocation_endpoint: Option<Url>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub revocation_endpoint_auth_methods_supported: Option<Vec<RevocationEndpointAuthMethod>>,
    // Note: Scopes isn't used here because this field should be
    // serialized as a list, not as a string
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scopes_supported: Option<Vec<Scope>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_types_supported: Option<Vec<ResponseType>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub grant_types_supported: Option<Vec<GrantType>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub service_documentation: Option<Url>,
    pub code_challenge_methods_supported: Vec<PKCEMethod>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authorization_response_iss_parameter_supported: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub userinfo_endpoint: Option<Url>
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Profile {
    pub name: String,
    pub url: Url,
    pub photo: Url,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct State(String);
impl State {
    fn new() -> Self {
        use rand::{Rng, distributions::Alphanumeric};
        let bytes = rand::thread_rng()
            .sample_iter(&Alphanumeric)
            .take(128)
            .collect::<Vec<u8>>();
        Self(String::from_utf8(bytes).unwrap())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthorizationRequest {
    pub response_type: ResponseType,
    pub client_id: Url,
    pub redirect_uri: Url,
    pub state: State,
    #[serde(flatten)]
    pub code_challenge: PKCEChallenge,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scope: Option<Scopes>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub me: Option<Url>
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthorizationResponse {
    pub code: String,
    pub state: State,
    pub iss: Url
}

#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "grant_type")]
#[serde(rename_all = "snake_case")]
pub enum GrantRequest {
    AuthorizationCode {
        code: String,
        client_id: Url,
        redirect_uri: Url,
        code_verifier: PKCEVerifier
    },
    RefreshToken {
        refresh_token: String,
        client_id: url::Url,
        scope: Option<Scopes>
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GrantResponse {
    AccessToken {
        me: Url,
        #[serde(skip_serializing_if = "Option::is_none")]
        profile: Option<Profile>,
        access_token: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        expires_in: Option<u64>,
        #[serde(skip_serializing_if = "Option::is_none")]
        refresh_token: Option<String>
    },
    ProfileUrl {
        me: Url,
        #[serde(skip_serializing_if = "Option::is_none")]
        profile: Option<Profile>
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RequestMaybeAuthorizationEndpoint {
    Authorization(AuthorizationRequest),
    Grant(GrantRequest)
}

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

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenData {
    pub me: Url,
    pub client_id: Url,
    pub scope: Scopes,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub exp: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub iat: Option<u64>
}

impl TokenData {
    pub fn expired(&self) -> bool {
        use std::time::{Duration, SystemTime, UNIX_EPOCH};
        
        self.exp
            .map(|exp| SystemTime::now()
                 .duration_since(UNIX_EPOCH)
                 .unwrap_or(Duration::ZERO)
                 .as_secs() >= exp)
            .unwrap_or_default()
    }

    pub fn expires_at(&self) -> Option<std::time::SystemTime> {
        self.exp.map(|time| {
            std::time::UNIX_EPOCH + std::time::Duration::from_secs(time)
        })
    }
    
    pub fn issued_at(&self) -> Option<std::time::SystemTime> {
        self.iat.map(|time| {
            std::time::UNIX_EPOCH + std::time::Duration::from_secs(time)
        })
    }
}

// I don't like this type, because it could've been represented
// internally by Option<TokenData>. But the IndieAuth standard
// requires the "active" field to be present. I can't do anything
// about it.
#[derive(Debug, Serialize, Deserialize)]
pub struct TokenIntrospectionResponse {
    active: bool,
    #[serde(flatten)]
    #[serde(skip_serializing_if = "Option::is_none")]
    data: Option<TokenData>
}
// These wrappers and impls should take care of making use of this
// type as painless as possible.
impl TokenIntrospectionResponse {
    pub fn inactive() -> Self {
        Self { active: false, data: None }
    }
    pub fn active(data: TokenData) -> Self {
        Self { active: true, data: Some(data) }
    }

    pub fn is_active(&self) -> bool {
        self.active
    }

    pub fn data(&self) -> Option<&TokenData> {
        if !self.active {
            return None
        }
        self.data.as_ref()
    }
}
impl Default for TokenIntrospectionResponse {
    fn default() -> Self {
        Self::inactive()
    }
}
impl From<Option<TokenData>> for TokenIntrospectionResponse {
    fn from(data: Option<TokenData>) -> Self {
        Self { active: data.is_some(), data }
    }
}
impl From<TokenIntrospectionResponse> for Option<TokenData> {
    fn from(response: TokenIntrospectionResponse) -> Option<TokenData> {
        response.data
    }
}

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

// TODO rework in accordance with https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
// turns out I got some values wrong
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "error")]
pub enum IndieAuthError {
    InvalidRequest,
    InvalidToken,
    InsufficientScope,
}

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

    #[test]
    fn test_serialize_indieauth_error() {
        assert_eq!(
            serde_json::to_value(IndieAuthError::InvalidRequest).unwrap(),
            json!({"error": "invalid_request"})
        );
        assert_eq!(
            serde_json::to_value(IndieAuthError::InvalidToken).unwrap(),
            json!({"error": "invalid_token"})
        );
        assert_eq!(
            serde_json::to_value(IndieAuthError::InsufficientScope).unwrap(),
            json!({"error": "insufficient_scope"})
        );
    }

    #[test]
    fn test_serialize_deserialize_grant_request() {
        let authorization_code: GrantRequest = GrantRequest::AuthorizationCode {
            client_id: "https://kittybox.fireburn.ru/".parse().unwrap(),
            redirect_uri: "https://kittybox.fireburn.ru/.kittybox/login/redirect".parse().unwrap(),
            code_verifier: PKCEVerifier("helloworld".to_string()),
            code: "hithere".to_owned()
        };
        let serialized = serde_urlencoded::to_string(&[
            ("grant_type", "authorization_code"),
            ("code", "hithere"),
            ("client_id", "https://kittybox.fireburn.ru/"),
            ("redirect_uri", "https://kittybox.fireburn.ru/.kittybox/login/redirect"),
            ("code_verifier", "helloworld"),
        ]).unwrap();

        let deserialized = serde_urlencoded::from_str(&serialized).unwrap();

        assert_eq!(authorization_code, deserialized);

        assert_eq!(
            serialized,
            serde_urlencoded::to_string(authorization_code).unwrap()
        )
    }
}