use super::constants::{ProfileMenuItems, Urls};
use super::errors::HtmxError;
use super::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
use super::navbar::NavbarCtx;
use crate::https::errors::WebError;
use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
use crate::https::middleware::KOpId;
use crate::https::ServerState;
use askama::Template;
use askama_axum::IntoResponse;
use axum::extract::{Query, State};
use axum::http::Uri;
use axum::response::Response;
use axum::Extension;
use axum_extra::extract::cookie::CookieJar;
use axum_extra::extract::Form;
use axum_htmx::{HxEvent, HxPushUrl, HxResponseTrigger};
use futures_util::TryFutureExt;
use kanidm_proto::attribute::Attribute;
use kanidm_proto::constants::{ATTR_DISPLAYNAME, ATTR_MAIL};
use kanidm_proto::internal::UserAuthToken;
use kanidm_proto::scim_v1::server::{ScimEffectiveAccess, ScimPerson};
use kanidm_proto::scim_v1::ScimMail;
use kanidmd_lib::filter::{f_id, Filter};
use kanidmd_lib::prelude::f_and;
use kanidmd_lib::prelude::FC;
use serde::Deserialize;
use serde::Serialize;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;

#[derive(Template)]
#[template(path = "user_settings.html")]
pub(crate) struct ProfileView {
    navbar_ctx: NavbarCtx,
    profile_partial: ProfilePartialView,
}

#[derive(Template, Clone)]
#[template(path = "user_settings_profile_partial.html")]
struct ProfilePartialView {
    menu_active_item: ProfileMenuItems,
    can_rw: bool,
    person: ScimPerson,
    scim_effective_access: ScimEffectiveAccess,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct SaveProfileQuery {
    #[serde(rename = "name")]
    account_name: String,
    #[serde(rename = "displayname")]
    display_name: String,
    #[serde(rename = "email_index")]
    emails_indexes: Vec<u16>,
    #[serde(rename = "emails[]")]
    emails: Vec<String>,
    // radio buttons are used to pick a primary index, remove causes holes, map back into [emails] using [emails_indexes]
    primary_email_index: u16,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct ProfileAttributes {
    account_name: String,
    display_name: String,
    emails: Vec<ScimMail>,
}

#[derive(Template, Clone)]
#[template(path = "user_settings/profile_changes_partial.html")]
struct ProfileChangesPartialView {
    menu_active_item: ProfileMenuItems,
    can_rw: bool,
    person: ScimPerson,
    new_attrs: ProfileAttributes,
}

#[derive(Template, Clone)]
#[template(path = "user_settings/form_email_entry_partial.html")]
pub(crate) struct FormEmailEntryListPartial {
    can_edit: bool,
    value: String,
    primary: bool,
    index: u16,
}

impl Display for ProfileAttributes {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{self:?}")
    }
}

pub(crate) async fn view_profile_get(
    State(state): State<ServerState>,
    Extension(kopid): Extension<KOpId>,
    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
    DomainInfo(domain_info): DomainInfo,
) -> Result<ProfileView, WebError> {
    let uat: UserAuthToken = state
        .qe_r_ref
        .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
        .await?;

    let (scim_person, scim_effective_access) =
        crate::https::views::admin::persons::get_person_info(
            uat.uuid,
            state,
            &kopid,
            client_auth_info.clone(),
        )
        .await?;

    let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);

    let can_rw = uat.purpose_readwrite_active(time);

    Ok(ProfileView {
        navbar_ctx: NavbarCtx { domain_info },

        profile_partial: ProfilePartialView {
            menu_active_item: ProfileMenuItems::UserProfile,
            can_rw,
            person: scim_person,
            scim_effective_access,
        },
    })
}

pub(crate) async fn view_profile_diff_start_save_post(
    State(state): State<ServerState>,
    Extension(kopid): Extension<KOpId>,
    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
    DomainInfo(domain_info): DomainInfo,
    // Form must be the last parameter because it consumes the request body
    Form(query): Form<SaveProfileQuery>,
) -> axum::response::Result<Response> {
    let uat: UserAuthToken = state
        .qe_r_ref
        .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
        .await?;

    let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
    let can_rw = uat.purpose_readwrite_active(time);
    // TODO: A bit overkill to request scimEffectiveAccess here.
    let (scim_person, _) = crate::https::views::admin::persons::get_person_info(
        uat.uuid,
        state,
        &kopid,
        client_auth_info.clone(),
    )
    .await?;

    let primary_index = query
        .emails_indexes
        .iter()
        .position(|ei| ei == &query.primary_email_index)
        .unwrap_or(0);
    let new_mails = query
        .emails
        .iter()
        .enumerate()
        .map(|(ei, email)| ScimMail {
            primary: ei == primary_index,
            value: email.to_string(),
        })
        .collect();

    let profile_view = ProfileChangesPartialView {
        menu_active_item: ProfileMenuItems::UserProfile,
        can_rw,
        person: scim_person,
        new_attrs: ProfileAttributes {
            account_name: query.account_name,
            display_name: query.display_name,
            emails: new_mails,
        },
    };

    Ok((
        HxPushUrl(Uri::from_static("/ui/profile/diff")),
        profile_view,
    )
        .into_response())
}

pub(crate) async fn view_profile_diff_confirm_save_post(
    State(state): State<ServerState>,
    Extension(kopid): Extension<KOpId>,
    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
    DomainInfo(domain_info): DomainInfo,
    // Form must be the last parameter because it consumes the request body
    Form(mut new_attrs): Form<ProfileAttributes>,
) -> axum::response::Result<Response> {
    let uat: UserAuthToken = state
        .qe_r_ref
        .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
        .await?;
    dbg!(&new_attrs);

    let filter = filter_all!(f_and!([f_id(uat.uuid.to_string().as_str())]));

    state
        .qe_w_ref
        .handle_setattribute(
            client_auth_info.clone(),
            uat.uuid.to_string(),
            ATTR_DISPLAYNAME.to_string(),
            vec![new_attrs.display_name],
            filter.clone(),
            kopid.eventid,
        )
        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
        .await?;

    new_attrs
        .emails
        .sort_by_key(|sm| if sm.primary { 0 } else { 1 });
    let email_addresses = new_attrs.emails.into_iter().map(|sm| sm.value).collect();
    state
        .qe_w_ref
        .handle_setattribute(
            client_auth_info.clone(),
            uat.uuid.to_string(),
            ATTR_MAIL.to_string(),
            email_addresses,
            filter.clone(),
            kopid.eventid,
        )
        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))
        .await?;

    // TODO: These are normally not permitted, user should be prevented from changing non modifiable fields in the UI though
    // state
    //     .qe_w_ref
    //     .handle_setattribute(
    //         client_auth_info.clone(),
    //         uat.uuid.to_string(),
    //         ATTR_EMAIL.to_string(),
    //         vec![new_attrs.email.unwrap_or("".to_string())],
    //         filter.clone(),
    //         kopid.eventid,
    //     )
    //     .map_err(|op_err| HtmxError::new(&kopid, op_err))
    //     .await?;
    //
    // state
    //     .qe_w_ref
    //     .handle_setattribute(
    //         client_auth_info.clone(),
    //         uat.uuid.to_string(),
    //         ATTR_NAME.to_string(),
    //         vec![new_attrs.account_name],
    //         filter.clone(),
    //         kopid.eventid,
    //     )
    //     .map_err(|op_err| HtmxError::new(&kopid, op_err))
    //     .await?;

    // TODO: Calling this here returns the old attributes
    match view_profile_get(
        State(state),
        Extension(kopid),
        VerifiedClientInformation(client_auth_info),
        DomainInfo(domain_info),
    )
    .await
    {
        Ok(pv) => Ok(pv.into_response()),
        Err(e) => Ok(e.into_response()),
    }
}

#[derive(Deserialize)]
pub(crate) struct AddEmailQuery {
    // the last email index is passed so we can return an incremented id
    email_index: Option<u16>,
}

// Sends the user a new email input to fill in :)
pub(crate) async fn view_new_email_entry_partial(
    State(_state): State<ServerState>,
    VerifiedClientInformation(_client_auth_info): VerifiedClientInformation,
    Extension(_kopid): Extension<KOpId>,
    Query(email_query): Query<AddEmailQuery>,
) -> axum::response::Result<Response> {
    let add_email_trigger =
        HxResponseTrigger::after_swap([HxEvent::new("addEmailSwapped".to_string())]);
    Ok((
        add_email_trigger,
        FormEmailEntryListPartial {
            can_edit: true,
            value: "".to_string(),
            primary: email_query.email_index.is_none(),
            index: email_query.email_index.map(|i| i + 1).unwrap_or(0),
        },
    )
        .into_response())
}

pub(crate) async fn view_profile_unlock_get(
    State(state): State<ServerState>,
    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
    DomainInfo(domain_info): DomainInfo,
    Extension(kopid): Extension<KOpId>,
    jar: CookieJar,
) -> Result<Response, HtmxError> {
    let uat: UserAuthToken = state
        .qe_r_ref
        .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
        .await
        .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;

    let display_ctx = LoginDisplayCtx {
        domain_info,
        oauth2: None,
        reauth: Some(Reauth {
            username: uat.spn,
            purpose: ReauthPurpose::ProfileSettings,
        }),
        error: None,
    };

    Ok(super::login::view_reauth_get(
        state,
        client_auth_info,
        kopid,
        jar,
        Urls::Profile.as_ref(),
        display_ctx,
    )
    .await)
}