/*
 * Author: Ruben Fiszel
 * Copyright: Windmill Labs, Inc 2023
 * This file and its contents are licensed under the AGPLv3 License.
 * Please see the included NOTICE for copyright information and
 * LICENSE-AGPL for a copy of the license.
 */
#![allow(non_snake_case)]

#[cfg(feature = "saml")]
use axum::response::Redirect;
use axum::{routing::post, Router};
#[cfg(feature = "saml")]
use axum::{Extension, Form};
#[cfg(feature = "saml")]
use std::sync::Arc;

#[cfg(feature = "saml")]
use samael::metadata::{ContactPerson, ContactType, EntityDescriptor};
#[cfg(feature = "saml")]
use samael::service_provider::{ServiceProvider, ServiceProviderBuilder};

use serde::Deserialize;
#[cfg(feature = "saml")]
use tower_cookies::Cookies;
#[cfg(feature = "saml")]
use windmill_common::error::{Error, Result};

#[cfg(feature = "saml")]
use crate::db::DB;
#[cfg(feature = "saml")]
use crate::users::login_externally;
#[cfg(feature = "saml")]
use crate::BASE_URL;

#[cfg(feature = "saml")]
#[derive(Clone)]
pub struct ServiceProviderExt(pub Option<ServiceProvider>);

#[cfg(not(feature = "saml"))]
pub struct ServiceProviderExt();

#[cfg(feature = "saml")]
use windmill_common::ee::{get_license_plan, LicensePlan};

pub struct SamlSsoLogin(pub Option<String>);

#[cfg(feature = "saml")]
pub async fn build_sp_extension() -> anyhow::Result<(ServiceProviderExt, SamlSsoLogin)> {
    if let Some(url_metadata) = std::env::var("SAML_METADATA").ok() {
        //todo restrict for non ee

        let resp = reqwest::get(url_metadata).await?.text().await?;
        let idp_metadata: EntityDescriptor = samael::metadata::de::from_str(&resp)?;

        // let pub_key = openssl::x509::X509::from_pem("")?;
        // let private_key = openssl::rsa::Rsa::private_key_from_pem("")?;

        let url = idp_metadata
            .idp_sso_descriptors
            .clone()
            .unwrap_or_default()
            .get(0)
            .and_then(|x| x.single_sign_on_services.get(0).map(|x| x.location.clone()));

        let sp = ServiceProviderBuilder::default()
            .entity_id("windmill".to_string())
            // .key(private_key)
            // .certificate(pub_key)
            .allow_idp_initiated(true)
            .contact_person(ContactPerson {
                sur_name: Some("Ruben Fiszel <ruben@windmill.dev>".to_string()),
                contact_type: Some(ContactType::Technical.value().to_string()),
                ..ContactPerson::default()
            })
            .idp_metadata(idp_metadata)
            .acs_url(format!("{}/api/saml/acs", BASE_URL.read().await.clone()))
            .build()?;

        tracing::info!("SAML Configured, sso login link at: {:?}", url);
        Ok((ServiceProviderExt(Some(sp)), SamlSsoLogin(url)))
    } else {
        Ok((ServiceProviderExt(None), SamlSsoLogin(None)))
    }
}

pub fn global_service() -> Router {
    Router::new().route("/acs", post(acs))
}

#[derive(Deserialize)]
pub struct SamlForm {
    pub SAMLResponse: Option<String>,
}

#[cfg(feature = "saml")]
pub async fn acs(
    Extension(db): Extension<DB>,
    cookies: Cookies,
    Extension(se): Extension<Arc<ServiceProviderExt>>,
    Form(s): Form<SamlForm>,
) -> Result<Redirect> {
    if matches!(get_license_plan().await, LicensePlan::Pro) {
        return Err(Error::BadRequest(
            "SAML not available in the pro plan".to_string(),
        ));
    };
    if let Some(sp_m) = &se.0 {
        let sp = sp_m.clone();
        if let Some(encoded_resp) = s.SAMLResponse {
            tracing::info!("{:?}", encoded_resp);
            let t = sp.parse_base64_response(&encoded_resp, None).map_err(|e| {
                Error::BadRequest(format!("Error parsing acs request as base64: {e:?}"))
            })?;

            if let Some(email) = t.subject.and_then(|x| x.name_id.map(|x| x.value)) {
                login_externally(db, &email, "saml".to_string(), cookies, None, None).await?;
                Ok(Redirect::to("/"))
            } else {
                Err(Error::BadRequest(
                    "email not found in saml response".to_string(),
                ))
            }
        } else {
            Err(Error::BadRequest("SAMLResponse not found".to_string()))
        }
    } else {
        Err(Error::BadConfig("SAML not configured".to_string()))
    }
}

#[cfg(not(feature = "saml"))]
pub async fn acs() -> String {
    "SAML available only in enterprise version".to_string()
}
