use std::collections::BTreeMap;

use gloo::console;
use wasm_bindgen::JsValue;
use yew::{html, Component, Context, Html, Properties};
use yew_router::prelude::Link;

use super::prelude::*;
use crate::components::admin_menu::{Entity, EntityType, GetError};
use crate::router::AdminRoute;
use kanidmd_web_ui_shared::constants::{CSS_CELL, CSS_TABLE};

#[cfg(debug_assertions)]
use serde::Serialize;

impl From<GetError> for AdminListOAuth2Msg {
    fn from(ge: GetError) -> Self {
        AdminListOAuth2Msg::Failed {
            emsg: ge.err,
            kopid: None,
        }
    }
}

pub struct AdminListOAuth2 {
    state: ListViewState,
}

// callback messaging for this confused pile of crab-bait
pub enum AdminListOAuth2Msg {
    /// When the server responds and we need to update the page
    Responded {
        response: BTreeMap<String, Entity>,
    },
    Failed {
        emsg: String,
        kopid: Option<String>,
    },
}

enum ListViewState {
    /// waiting for the page to load
    Loading,
    /// server has responded
    Responded { response: BTreeMap<String, Entity> },
    /// failed to pull the details
    #[allow(dead_code)]
    Failed {
        // TODO: use this
        emsg: String,
        kopid: Option<String>,
    },
    #[allow(dead_code)]
    /// Not authorized to pull the details
    NotAuthorized {}, // TODO: use this
}

#[derive(PartialEq, Properties, Eq)]
pub struct AdminListOAuth2Props {
    // for filtering and pagination
    // #[allow(dead_code)]
    // search: Option<String>,
    // #[allow(dead_code)]
    // page: Option<u32>,
}

/// Pulls all OAuth2 RPs from the backend and returns a HashMap
/// with the "name" field being the keys, for easy human-facing sortability.
pub async fn get_entities() -> Result<AdminListOAuth2Msg, GetError> {
    // TODO: the actual pulling and turning into a BTreeMap across the admin systems could *probably* be rolled up into one function? The result object differs but all the query bits are the same.
    let mut oauth2_objects = BTreeMap::new();

    // we iterate over these endpoints
    let endpoints = [("/v1/oauth2", EntityType::OAuth2RP)];

    for (endpoint, object_type) in endpoints {
        let (_, _, value, _) = match do_request(endpoint, RequestMethod::GET, None::<JsValue>).await
        {
            Ok(val) => val,
            Err(error) => {
                return Err(GetError {
                    err: format!("{:?}", error),
                })
            }
        };

        let data: Vec<Entity> = match serde_wasm_bindgen::from_value(value) {
            Ok(value) => value,
            Err(error) => {
                return Err(GetError {
                    err: format!("Failed to grab the oauth2 data into JSON: {:?}", error),
                });
            }
        };

        for entity in data.into_iter() {
            let mut new_entity = entity.to_owned();
            new_entity.object_type = object_type.clone();

            // first we try the uuid and if that isn't there oh no.
            #[allow(clippy::expect_used)]
            let entity_id = entity
                .attrs
                .uuid
                .first()
                .map(|e| e.to_owned())
                .unwrap_or(String::from("Unknown entity name!"));
            oauth2_objects.insert(entity_id, new_entity);
        }
    }

    Ok(AdminListOAuth2Msg::Responded {
        response: oauth2_objects,
    })
}

impl Component for AdminListOAuth2 {
    type Message = AdminListOAuth2Msg;
    type Properties = AdminListOAuth2Props;

    fn create(ctx: &Context<Self>) -> Self {
        // TODO: work out the querystring thing so we can just show x number of elements

        // start pulling the data on startup
        ctx.link().send_future(async move {
            match get_entities().await {
                Ok(v) => v,
                Err(v) => v.into(),
            }
        });
        AdminListOAuth2 {
            state: ListViewState::Loading,
        }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            AdminListOAuth2Msg::Responded { response } => {
                // TODO: do we paginate here?
                #[cfg(debug_assertions)]
                for key in response.keys() {
                    let j = response
                        .get(key)
                        .and_then(|k| {
                            k.serialize(&serde_wasm_bindgen::Serializer::json_compatible())
                                .ok()
                        })
                        .and_then(|jsv| js_sys::JSON::stringify(&jsv).ok().map(|s| s.into()))
                        .unwrap_or_else(|| "Failed to dump response key".to_string());
                    console::log!("response: {}", j);
                }
                self.state = ListViewState::Responded { response };
                return true;
            }
            AdminListOAuth2Msg::Failed { emsg, kopid } => {
                console::log!("emsg: {:?}", emsg);
                console::log!("kopid: {:?}", kopid);
            }
        }
        false
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        match &self.state {
            ListViewState::Loading => {
                html! {"Waiting on the OAuth2 data to load..."}
            }

            ListViewState::Responded { response } => {
                let scope_col = "col";
                html! {
                    <>

                      {do_page_header("OAuth2")}

                      { alpha_warning_banner() }
                <div id={"accountlist"}>
                  <table class={CSS_TABLE}>
                  <thead>
                    <tr>
                      <th scope={scope_col}>{"Display Name"}</th>
                      <th scope={scope_col}>{"Username"}</th>
                      <th scope={scope_col}>{"Description"}</th>
                    </tr>
                  </thead>

                  {
                    response.keys().map(|uuid| {
                        #[allow(clippy::expect_used)]
                      let oauth2_object: &Entity = response.get(uuid).expect("Couldn't get oauth2 key when it was just in the iter...");

                        let display_name: String = match oauth2_object.attrs.displayname.first() {
                          Some(value) => value.to_string(),
                          None => String::from(""),
                        };

                        let description: String = match oauth2_object.attrs.description.first() {
                          Some(value) => value.to_string(),
                          None => String::from(""),
                        };

                        let uuid: String = match oauth2_object.attrs.uuid.first() {
                            Some(value) => value.to_string(),
                            None => {
                                console::error!("Config without a UUID?", format!("{:?}", oauth2_object).to_string());
                                String::from("Unknown UUID!")
                            }
                        };

                        let rs_link = match oauth2_object.attrs.oauth2_rs_name.first() {
                            Some(id_or_name) => {
                                html!{
                                    <Link<AdminRoute> to={AdminRoute::ViewOAuth2RP{id_or_name: id_or_name.clone()}}>
                                    {display_name}
                                    </Link<AdminRoute>>
                                }
                            },
                            None => {
                                html!{{display_name}}
                            }
                        };

                        html!{
                          <tr key={uuid.clone()}>
                          <th scope={scope_col} class={CSS_CELL}>
                          {rs_link}
                            </th>
                          <td class={CSS_CELL}>{uuid}</td>
                          <td class={CSS_CELL}>{description}</td>
                          </tr>
                        }
                    }).collect::<Html>()
                  }
                  </table>
                  </div>
                  </>
                }
            }
            ListViewState::Failed { emsg, kopid } => {
                console::error!("Failed to pull details", format!("{:?}", kopid));
                html!(
                    <>
                    {do_alert_error("Failed to Query OAuth2", Some(emsg), false)}
                    </>
                )
            }
            ListViewState::NotAuthorized {} => {
                do_alert_error("You're not authorized to see this page!", None, false)
            }
        }
    }
}

impl From<GetError> for AdminViewOAuth2Msg {
    fn from(ge: GetError) -> Self {
        AdminViewOAuth2Msg::Failed {
            emsg: ge.err,
            kopid: None,
        }
    }
}

// callback messaging for this confused pile of crab-bait
pub enum AdminViewOAuth2Msg {
    /// When the server responds and we need to update the page
    Responded {
        response: Entity,
    },
    Failed {
        emsg: String,
        kopid: Option<String>,
    },
}

#[derive(PartialEq, Eq, Properties)]
pub struct AdminViewOAuth2Props {
    pub id_or_name: String,
}

enum ViewState {
    /// waiting for the page to load
    Loading,
    /// server has responded
    Responded { response: Entity },
    /// failed to pull the details
    #[allow(dead_code)]
    Failed {
        // TODO: use this
        emsg: String,
        kopid: Option<String>,
    },
    #[allow(dead_code)]
    /// Not authorized to pull the details
    NotAuthorized {}, // TODO: use this
}

pub struct AdminViewOAuth2 {
    state: ViewState,
}

impl Component for AdminViewOAuth2 {
    type Message = AdminViewOAuth2Msg;
    type Properties = AdminViewOAuth2Props;

    fn create(ctx: &Context<Self>) -> Self {
        let rs_name = ctx.props().id_or_name.clone();

        // start pulling the data on startup
        ctx.link().send_future(async move {
            match get_oauth2_rp(&rs_name).await {
                Ok(v) => v,
                Err(v) => v.into(),
            }
        });
        AdminViewOAuth2 {
            state: ViewState::Loading,
        }
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        match &self.state {
            ViewState::Loading => {
                html! {"Waiting on the OAuth2 data to load..."}
            }

            ViewState::Responded { response } => {
                let oauth2_object: &Entity = response;

                let display_name: String = match oauth2_object.attrs.displayname.first() {
                    Some(value) => value.to_string(),
                    None => String::from("!error getting display name!"),
                };

                let description: String = match oauth2_object.attrs.description.first() {
                    Some(value) => value.to_string(),
                    None => String::from(""),
                };
                let oauth2_rs_name: String = match oauth2_object.attrs.oauth2_rs_name.first() {
                    Some(value) => value.to_string(),
                    None => String::from("!error getting oauth2_rs_name!"),
                };
                let oauth2_rs_origin: String = match oauth2_object.attrs.oauth2_rs_origin.first() {
                    Some(value) => value.to_string(),
                    None => String::from("!error getting oauth2_rs_origin!"),
                };
                let uuid: String = match oauth2_object.attrs.uuid.first() {
                    Some(value) => value.to_string(),
                    None => {
                        console::error!("Config without a UUID?", format!("{:?}", oauth2_object));
                        String::from("Unknown UUID!")
                    }
                };
                html! {
                  <>

                  {do_page_header(display_name.as_str())}
                  {alpha_warning_banner()}

                  <p>{"UUID: "}{uuid}</p>
                  <p>{description}</p>
                  <p>{"RS Name: "}{oauth2_rs_name}</p>
                  <p>{"Origin: "}{oauth2_rs_origin}</p>
                  </>
                }
            }
            ViewState::Failed { emsg, kopid } => {
                console::error!("Failed to pull details", format!("{:?}", kopid));
                html!(
                    <>
                    {do_alert_error("Failed to Query OAuth2", Some(emsg), false)}
                    </>
                )
            }
            ViewState::NotAuthorized {} => {
                do_alert_error("You're not authorized to see this page!", None, false)
            }
        }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            AdminViewOAuth2Msg::Responded { response } => {
                // TODO: do we paginate here?
                /*
                // Seems broken
                #[cfg(debug_assertions)]
                for key in response.keys() {
                    console::log!(
                        "response: {:?}",
                        serde_json::to_string(response.get(key).unwrap()).unwrap()
                    );
                }
                */
                self.state = ViewState::Responded { response };
            }
            AdminViewOAuth2Msg::Failed { emsg, kopid } => {
                console::log!("emsg: {:?}", &emsg);
                console::log!("kopid: {:?}", kopid.to_owned());
                self.state = ViewState::Failed { emsg, kopid };
            }
        }
        true
    }
}

pub async fn get_oauth2_rp(rs_name: &str) -> Result<AdminViewOAuth2Msg, GetError> {
    let endpoint = format!("/v1/oauth2/{}", rs_name);
    let (_, _, value, _) = match do_request(&endpoint, RequestMethod::GET, None::<JsValue>).await {
        Ok(val) => val,
        Err(error) => {
            return Err(GetError {
                err: format!("{:?}", error),
            })
        }
    };

    let data: Entity = match serde_wasm_bindgen::from_value(value) {
        Ok(value) => {
            console::log!(format!("{:?}", value));
            value
        }
        Err(error) => {
            //TODO: turn this into an error, and handle when we aren't authorized. The server doesn't seem to be sending back anything nice for this, which is.. painful.
            console::log!(
                "Failed to grab the OAuth2 RP data into JSON:",
                format!("{:?}", error)
            );
            return Err(GetError {
                err: format!("Failed to grab the OAuth2 RP data into JSON: {:?}", error),
            });
        }
    };

    Ok(AdminViewOAuth2Msg::Responded { response: data })
}
