/*
* Copyright (c) 2022 Daniél Kerkmann <daniel@kerkmann.dev>
*
* Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
* the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*
* SPDX-License-Identifier: EUPL-1.2
*/

use itertools::join;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;

use crate::utils::*;

/**
 * Authorization request and resposne
 */
#[derive(Debug, Deserialize, Serialize)]
pub struct AuthorizationRequest {
    pub client_id: String,
    pub response_type: String,
    pub redirect_uri: String,
    #[serde(
        deserialize_with = "deserialize_scope",
        serialize_with = "serialize_scope"
    )]
    pub scope: Vec<Scope>,
    pub state: Option<String>,
    pub nonce: Option<String>,
    pub force_verify: Option<bool>,
    #[serde(default, serialize_with = "serialize_claims")]
    pub claims: Claims,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Claims {
    #[serde(default)]
    pub id_token: Claim,
    #[serde(default)]
    pub userinfo: Claim,
}

impl Default for Claims {
    fn default() -> Self {
        ENV.default_claims.clone()
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Claim {
    #[serde(skip_serializing_if = "skip_claim")]
    pub email: bool,
    #[serde(skip_serializing_if = "skip_claim")]
    pub email_verified: bool,
    #[serde(skip_serializing_if = "skip_claim")]
    pub picture: bool,
    #[serde(skip_serializing_if = "skip_claim")]
    pub preferred_username: bool,
    #[serde(skip_serializing_if = "skip_claim")]
    pub updated_at: bool,
}

impl Default for Claim {
    fn default() -> Self {
        Self {
            email: false,
            email_verified: false,
            picture: true,
            preferred_username: true,
            updated_at: true,
        }
    }
}

impl FromStr for Claim {
    type Err = serde_json::Error;

    fn from_str(claim: &str) -> Result<Self, Self::Err> {
        let claim_vec: Vec<&str> = claim.split(',').collect();

        Ok(Claim {
            email: claim_vec.contains(&"email"),
            email_verified: claim_vec.contains(&"email_verified"),
            picture: claim_vec.contains(&"picture"),
            preferred_username: claim_vec.contains(&"preferred_username"),
            updated_at: claim_vec.contains(&"updated_at"),
        })
    }
}

fn skip_claim(state: &bool) -> bool {
    !state
}

/**
 * Token
 */
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenRequest {
    pub client_id: String,
    pub client_secret: String,
    pub grant_type: String,
    pub redirect_uri: String,
    pub code: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct TokenResponse {
    pub access_token: String,
    pub refresh_token: String,
    pub token_type: String,
    pub expires_in: u32,
    pub id_token: String,
    #[serde(serialize_with = "serialize_scope")]
    pub scope: Vec<Scope>,
}

/**
 * Userinfo
 */
#[derive(Debug, Deserialize, Serialize)]
pub struct UserinfoRequest {
    pub access_token: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct UserinfoResponse {
    pub aud: String,
    pub exp: u32,
    pub iat: u32,
    pub iss: String,
    pub sub: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email_verified: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub picture: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated_at: Option<String>,
}

/**
 * Common
 */
#[derive(Debug, Deserialize, Serialize)]
pub enum Scope {
    #[serde(rename = "analytics:read:extensions")]
    AnaylyticsReadExtensions,
    #[serde(rename = "analytics:read:games")]
    AnaylyticsReadGames,
    #[serde(rename = "bits:read")]
    BitsRead,
    #[serde(rename = "channel:edit:commercial")]
    ChannelEditCommercial,
    #[serde(rename = "channel:manage:broadcast")]
    ChannelManageBroadcast,
    #[serde(rename = "channel:manage:extensions")]
    ChannelManageExtension,
    #[serde(rename = "channel:manage:polls")]
    ChannelManagePolls,
    #[serde(rename = "channel:manage:predictions")]
    ChannelManagePredictions,
    #[serde(rename = "channel:manage:raids")]
    ChannelManageRaids,
    #[serde(rename = "channel:manage:redemptions")]
    ChannelManageRedemptions,
    #[serde(rename = "channel:manage:schedule")]
    ChannelManageSchedule,
    #[serde(rename = "channel:manage:videos")]
    ChannelManageVideos,
    #[serde(rename = "channel:read:editors")]
    ChannelReadEditors,
    #[serde(rename = "channel:read:goals")]
    ChannelReadGoals,
    #[serde(rename = "channel:read:hype_train")]
    ChannelReadHypeTrain,
    #[serde(rename = "channel:read:polls")]
    ChannelReadPolls,
    #[serde(rename = "channel:read:predictions")]
    ChannelReadPredictions,
    #[serde(rename = "channel:read:redemptions")]
    ChannelReadRedemptions,
    #[serde(rename = "channel:read:stream_key")]
    ChannelReadStreamKey,
    #[serde(rename = "channel:read:subscriptions")]
    ChannelReadSubscriptions,
    #[serde(rename = "clips:edit")]
    ClipsEdit,
    #[serde(rename = "moderator:manage:banned_users")]
    ModeratorManageBannedUsers,
    #[serde(rename = "moderator:manage:blocked_terms")]
    ModeratorManageBlockedTerms,
    #[serde(rename = "moderator:manage:automod")]
    ModeratorManageAutomod,
    #[serde(rename = "moderator:manage:automod_settings")]
    ModeratorManageAutomodSettings,
    #[serde(rename = "moderator:manage:chat_settings")]
    ModeratorManageChatSettings,
    #[serde(rename = "moderation:read")]
    ModeratorRead,
    #[serde(rename = "moderator:read:automod_settings")]
    ModeratorReadAutomodSettings,
    #[serde(rename = "moderator:read:blocked_terms")]
    ModeratorReadBlockedTerms,
    #[serde(rename = "moderator:read:chat_settings")]
    ModeratorReadChatSettings,
    #[serde(rename = "user:edit")]
    UserEdit,
    #[serde(rename = "user:edit:follows")]
    UserEditFollows,
    #[serde(rename = "user:manage:blocked_users")]
    UserManageBlockedUsers,
    #[serde(rename = "user:read:blocked_users")]
    UserReadBlockedUsers,
    #[serde(rename = "user:read:broadcast")]
    UserReadBroadcast,
    #[serde(rename = "user:read:email")]
    UserReadEmail,
    #[serde(rename = "user:read:follows")]
    UserReadFollows,
    #[serde(rename = "user:read:subscriptions")]
    UserReadSubscriptions,
    #[serde(rename = "openid")]
    OpenID,
}

/**
 * Serde serializer and deserializer
 */
fn serialize_claims<S>(claims: &Claims, s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut claims_string = String::new();
    claims_string += "{";
    claims_string += "\"id_token\":";
    claims_string += serde_json::to_string(&claims.id_token).unwrap().as_str();
    claims_string += ",\"userinfo\":";
    claims_string += serde_json::to_string(&claims.userinfo).unwrap().as_str();
    claims_string += "}";
    s.serialize_str(&claims_string)
}

fn deserialize_scope<'de, D>(deserializer: D) -> Result<Vec<Scope>, D::Error>
where
    D: Deserializer<'de>,
{
    let raw: String = String::deserialize(deserializer)?;
    let scopes: Vec<Scope> = raw
        .split(' ')
        .into_iter()
        .flat_map(|s| {
            serde_json::from_str::<Scope>(serde_json::to_string(&s).unwrap().as_str()).ok()
        })
        .collect();
    Ok(scopes)
}

fn serialize_scope<S>(scopes: &[Scope], s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let scopes: Vec<String> = scopes
        .iter()
        .map(|scope| {
            serde_json::from_str::<String>(serde_json::to_string(&scope).unwrap().as_str()).unwrap()
        })
        .collect();
    let scopes: String = join(scopes, " ");
    s.serialize_str(&scopes)
}
