use std::str::FromStr;
use std::sync::Arc;

use leptos::*;
use leptos_router::*;
use serde::{Deserialize, Serialize};

use crate::domain::api::prelude::*;
use crate::domain::leptos::app::components::alert::Alert as AlertView;

use crate::domain::leptos::app::pages::auth::auth_base::AuthBase;

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

#[server]
async fn authorize_action(form: AuthorizeQuery) -> Result<(), ServerFnError<String>> {
    use crate::domain::api::prelude::*;
    use crate::domain::leptos::check_user;

    let app = use_context::<AppService>().unwrap();

    let Some(client) = app.find_client_by_id(form.client_id()).await else {
        return Err(ServerFnError::WrappedServerError(
            "Invalid Client ID".to_string(),
        ));
    };

    let Some(user) = check_user().await? else {
        return Err(ServerFnError::WrappedServerError("No user".to_string()));
    };

    let response: AuthorizationResponse = app
        .generate_authorization_code(&user, form.into())
        .await
        .map_err(|e| format!("{}", e))?;

    let qs = serde_qs::to_string(&response).map_err(|e| format!("{}", e))?;

    leptos_axum::redirect(&format!("{}?{}", client.redirect_uri(), qs));

    Ok(())
}

impl IntoAttribute for CodeChallengeMethod {
    fn into_attribute(self) -> Attribute {
        Attribute::String(self.to_string().into())
    }

    fn into_attribute_boxed(self: Box<Self>) -> Attribute {
        Attribute::String(self.to_string().into())
    }
}

// Make the types and validators for specific shit like response type and code challenge method
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct AuthorizeQuery {
    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 From<AuthorizeQuery> for AuthorizeRequest {
    fn from(value: AuthorizeQuery) -> Self {
        Self::new(
            value.client_id(),
            value.response_type(),
            value.state(),
            value.code_challenge(),
            value.code_challenge_method(),
            value.redirect_uri(),
            value.scope(),
        )
    }
}

impl AuthorizeQuery {
    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()
    }
}

impl Params for AuthorizeQuery {
    fn from_map(map: &ParamsMap) -> Result<Self, ParamsError> {
        let client_id: uuid::Uuid = match map.get("client_id") {
            Some(c) => uuid::Uuid::from_str(c).map_err(|e| ParamsError::Params(Arc::new(e)))?,
            None => return Err(ParamsError::MissingParam("client_id".to_string())),
        };

        let response_type: ResponseType = match map.get("response_type") {
            Some(c) => ResponseType::from_str(c).map_err(|e| ParamsError::Params(Arc::new(e)))?,
            None => return Err(ParamsError::MissingParam("response_type".to_string())),
        };

        let state = map.get("state").cloned();

        let Some(code_challenge) = map.get("code_challenge").cloned() else {
            return Err(ParamsError::MissingParam("code_challenge".to_string()));
        };

        let code_challenge_method = map
            .get("code_challenge_method")
            .map(|c| CodeChallengeMethod::from_str(c).map_err(|e| ParamsError::Params(Arc::new(e))))
            .transpose()?;

        let redirect_uri: RedirectUri = match map.get("redirect_uri") {
            Some(c) => RedirectUri::new(c),
            None => return Err(ParamsError::MissingParam("redirect_uri".to_string())),
        };

        let scope = map.get("scope").cloned();

        Ok(Self {
            client_id,
            response_type,
            state,
            code_challenge,
            code_challenge_method,
            redirect_uri,
            scope,
        })
    }
}

#[server]
async fn get_client(client_id: uuid::Uuid) -> Result<Client, ServerFnError<String>> {
    use crate::domain::api::prelude::*;

    let app = use_context::<AppService>().unwrap();

    let Some(client) = app.find_client_by_id(client_id).await else {
        return Err(ServerFnError::WrappedServerError(
            "Invalid Client ID".to_string(),
        ));
    };

    Ok(client)
}

/// Renders the home page of your application.
#[component]
pub fn AuthorizePage(user: RwSignal<Option<User>>) -> impl IntoView {
    let submit = Action::<AuthorizeAction, _>::server();
    let response = submit.value().read_only();

    let query = use_query::<AuthorizeQuery>();

    let client_signal = create_rw_signal(None::<Client>);

    let client_resource = create_local_resource(
        || (),
        move |_| async move {
            let query = query.get_untracked().map_err(|e| format!("{}", e))?;
            let client = get_client(query.client_id())
                .await
                .map_err(|e| format!("{}", e))?;

            if client.redirect_uri() != &query.redirect_uri() {
                return Err("Invalid redirect uri".to_string());
            }

            client_signal.set(Some(client));

            Ok::<(), String>(())
        },
    );

    view! {
        <AuthBase>

            <Suspense>
                <Show when=move || response.get().is_some_and(|e| e.is_err())>
                    <AlertView alert={Alert::Error}>
                        { move || if let Some(Err(e)) = response.get() {
                            {format!("{}", e)}.into_view()
                        } else {
                            ().into_view()
                        }}
                    </AlertView>
                </Show>
            </Suspense>

            <Show when=move || query.get().is_err()>
                <AlertView alert={Alert::Error}>
                    { move || if let Err(e) = query.get().map_err(|e| { leptos::logging::warn!("{:#?}", e); "Invalid parameters".to_string() }) {
                        {e.to_string()}.into_view()
                    } else {
                        ().into_view()
                    }}
                </AlertView>
            </Show>

            <Suspense>
                <Show when=move || client_resource.get().is_some_and(|e| e.is_err())>
                    <AlertView alert={Alert::Error}>
                        { move || if let Some(Err(e)) = client_resource.get() {
                            {e.to_string()}.into_view()
                        } else {
                            ().into_view()
                        }}
                    </AlertView>
                </Show>
            </Suspense>

            <Show when=move || query.get().is_ok() && client_resource.get().is_some_and(|e| e.is_ok())>
                <ActionForm action=submit class="w-full">
                    <div class="flex flex-col gap-2">
                        <div class="text-center text-sm">
                            "Signed in as "{move || user.get().unwrap().email().to_string()}
                        </div>

                        <Show when=move || response().is_some_and(|e| e.is_ok())>
                            <AlertView alert={Alert::Success}>
                                "You can now close this window."
                            </AlertView>
                        </Show>

                        <Show when=move || response().is_none()>
                            <div class="text-center text-sm">
                                <strong>{move || client_signal().unwrap().name().to_string() }</strong>" is requesting access to your account"
                            </div>

                            <div>
                                <input type="hidden" name="form[client_id]" value=move || query().unwrap().client_id().to_string() />
                                <input type="hidden" name="form[response_type]" value=move || query().unwrap().response_type().to_string() />
                                <input type="hidden" name="form[state]" value=move || query().unwrap().state() />
                                <input type="hidden" name="form[code_challenge]" value=move || query().unwrap().code_challenge().to_string() />
                                <input type="hidden" name="form[code_challenge_method]" value=move || query().unwrap().code_challenge_method() />
                                <input type="hidden" name="form[redirect_uri]" value=move || query().unwrap().redirect_uri().to_string() />
                                <input type="hidden" name="form[scope]" value=move || query().unwrap().scope() />
                                <input type="submit" value="Authorize" class="btn btn-primary btn-block" />
                            </div>

                            <div class="text-center text-sm">
                                "Not "{move || user.get().unwrap().email().to_string()}"? "<a href={move || {
                                    use base64::prelude::*;
                                    format!("/auth/logout?c={}", BASE64_URL_SAFE_NO_PAD.encode(format!("{}{}", (leptos_router::use_location().pathname)(), (leptos_router::use_location().query)().to_query_string())))
                                }} class="link">"Logout"</a>"!"
                            </div>
                        </Show>
                    </div>
                </ActionForm>
            </Show>

        </AuthBase>

    }
}
