use anyhow::{Context, Result, anyhow};
use url::Url;
use serde::{Serialize, Deserialize};
use fully_pub::fully_pub;

use thiserror::Error;

#[derive(Error, Debug)]
enum APIClientError {
    #[error("Received non-normal status code from API")]
    InvalidStatusCode
}

#[fully_pub]
#[derive(Clone, Serialize, Deserialize, Debug)]
struct WebSession {
    jwt: String
}

pub enum LoginError {
    TransportFailure(reqwest::Error)
}

#[derive(Debug)]
#[fully_pub]
struct Client {
    client: reqwest::Client,
    base_url: Url,
}

#[derive(Serialize, Debug)]
#[fully_pub]
struct LoginPayload {
    email: String,
    password: String
}

impl Default for Client {
    fn default() -> Self {
        Client {
            client: Client::get_base_client_builder()
                .build()
                .expect("reqwest client to be built"),
            base_url: Url::parse("https://api.helloasso.com/v5/")
                .expect("Valid helloasso API base URL")
        }
    }
}

impl Client {

    fn get_base_client_builder() -> reqwest::ClientBuilder {
        let mut default_headers = reqwest::header::HeaderMap::new();
        default_headers.insert("Accept", "application/json".parse().unwrap());

        let proxy = reqwest::Proxy::https("https://localhost:8999").unwrap();
        reqwest::Client::builder()
                .proxy(proxy)
                .default_headers(default_headers)
    }

    pub async fn login(&mut self, payload: LoginPayload) -> Result<AuthentifiedClient> {
        let mut login_commons_headers = reqwest::header::HeaderMap::new();
        login_commons_headers.insert(
            "Origin",
            "https://auth.helloasso.com".parse().expect("Header value to be OK")
        );

        let res = self.client.get(self.base_url.join("auth/antiforgerytoken")?)
            .headers(login_commons_headers.clone())
            .send().await?;
        let antiforgerytoken: String = res.json().await?;

        let res = self.client.post(self.base_url.join("auth/login")?)
            .json(&payload)
            .headers(login_commons_headers.clone())
            .header("x-csrf-token", antiforgerytoken)
            .send()
            .await?;

        if res.status() != 200 {
            return Err(anyhow!("Unexpected status code from login"));
        }

        fn get_jwt_from_cookies_headers(headers: &reqwest::header::HeaderMap) -> Option<String> {
            for (name_opt, value_raw) in headers {
                let name = String::from(name_opt.as_str());
                if name.to_lowercase() != "set-cookie" {
                    continue
                }
                let value = String::from(value_raw.to_str().unwrap());
                if value.starts_with("tm5-HelloAsso") {
                    let jwt = value.split("tm5-HelloAsso=").nth(1)?.split(";").nth(0)?.trim().to_string();
                    return Some(jwt);
                }
            }
            None
        }

        let jwt = get_jwt_from_cookies_headers(&res.headers())
            .context("Failed to find or parse JWT from login response")?;

        let session = WebSession { jwt };

        Ok(self.authentified_client(session))
    }

    pub fn authentified_client(&self, session: WebSession) -> AuthentifiedClient {
        AuthentifiedClient::new(self.base_url.clone(), session)
    }
}

#[derive(Debug, Clone)]
#[fully_pub]
struct AuthentifiedClient {
    session: WebSession,
    client: reqwest::Client,
    base_url: Url
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[fully_pub]
struct PaginationMeta {
    continuation_token: String,
    page_index: u64,
    page_size: u64,
    total_count: u64,
    total_pages: u64
}

#[derive(Debug, Serialize, Deserialize)]
struct PaginationCapsule {
    data: serde_json::Value,
    pagination: PaginationMeta
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[fully_pub]
struct CustomFieldAnswer {
    answer: String,
    id: u64,
    name: String
    // missing type, it's probably always TextInput, if not, serde will fail to parse
}


#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[fully_pub]
struct PayerUserDetails {
    country: String,
    email: String,
    first_name: String,
    last_name: String
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[fully_pub]
struct UserDetails {
    first_name: String,
    last_name: String
}


// #[derive(Debug, Serialize, Deserialize)]
// #[serde(rename_all = "camelCase")]
// struct OrderDetails {
//     date: 
//     form_
// }

impl AuthentifiedClient {
    /// each time we need to change the token, we will need to rebuild the client
    pub fn new(base_url: Url, session: WebSession) -> Self {
        let mut auth_headers = reqwest::header::HeaderMap::new();
        auth_headers.insert("Authorization", format!("Bearer {}", session.jwt).parse().unwrap());
        
        AuthentifiedClient {
            base_url,
            session,
            client: Client::get_base_client_builder()
                .default_headers(auth_headers)
                .build()
                .expect("reqwest client to be built")
        }
    }

    pub async fn verify_auth(&self) -> Result<bool> {
        let res = self.client
            .get(self.base_url.join("agg/user")?)
            .send().await?;
        return Ok(res.status() == 200);
    }

    pub async fn get_user_details(&self) -> Result<serde_json::Value> {
        let res = self.client
            .get(self.base_url.join("agg/user")?)
            .send().await?;
        if res.status() != 200 {
            return Err(APIClientError::InvalidStatusCode.into());
        }
        let user_details: serde_json::Value = res.json().await?;

        Ok(user_details)
    }

    async fn simple_fetch(&self, path: String) -> Result<serde_json::Value> {
        let res = self.client
            .get(self.base_url.join(path.as_str())?)
            .send().await?;
        if res.status() != 200 {
            return Err(APIClientError::InvalidStatusCode.into());
        }
        let details: serde_json::Value = res.json().await?;

        Ok(details)
    }

    pub async fn fetch_with_pagination(&self, path: String) -> Result<Vec<serde_json::Value>> {
        let mut data: Vec<serde_json::Value> = vec![];
        let mut continuation_token: Option<String> = None;

        loop {
            let mut url = self.base_url.join(path.as_str())?;
            if let Some(token) = &continuation_token {
                url.query_pairs_mut().append_pair("continuationToken", token);
            }
            let res = self.client
                .get(url)
                .send().await?;
            if res.status() != 200 {
                return Err(APIClientError::InvalidStatusCode.into());
            }
            let capsule: PaginationCapsule = res.json().await?;

            // handle pagination
            // merge into "data", "pagination" is the key that hold details

            let page_items = match capsule.data {
                serde_json::Value::Array(inner) => inner,
                _ => {
                    return Err(anyhow!("Unexpected json value in data bundle"));
                }
            };
            if page_items.len() == 0 {
                return Ok(data);
            }
            data.extend(page_items);
            if capsule.pagination.page_index == capsule.pagination.total_pages {
                return Ok(data);
            }
            continuation_token = Some(capsule.pagination.continuation_token);
        }
    }

    pub fn organization(&self, slug: &str) -> Organization {
        Organization { client: self.clone(), slug: slug.to_string() }
    }
}

#[derive(Debug, Clone)]
#[fully_pub]
struct Organization {
    client: AuthentifiedClient,
    slug: String
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[fully_pub]
enum MembershipMode {
    #[serde(rename = "Individuel")]
    Individual,
    #[serde(rename = "Couple")]
    Couple,
    #[serde(rename = "Individuel bienfaiteur")]
    BenefactorIndividual,
    #[serde(rename = "Couple bienfaiteur")]
    BenefactorCouple,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[fully_pub]
struct OrderDetails {
    id: u64,
    // #[serde(with = "date_format")]
    // date: DateTime<Utc>
    date: String
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[fully_pub]
struct FormAnswer {
    amount: u64,
    
    #[serde(rename = "name")]
    mode: MembershipMode,
    
    #[serde(rename = "payer")]
    payer_user: PayerUserDetails,

    order: OrderDetails,

    #[serde(rename = "user")]
    user: UserDetails,
    
    id: u64,
    custom_fields: Vec<CustomFieldAnswer>
}

impl Organization {
    pub async fn get_details(&self) -> Result<serde_json::Value> {
        let details = self.client.simple_fetch(format!("organizations/{}", self.slug)).await?;
        Ok(details)
    }

    pub async fn get_form_answers(&self, form_slug: String) -> Result<Vec<FormAnswer>> {
        let data = self.client.fetch_with_pagination(
            format!("organizations/{}/forms/Membership/{}/participants?withDetails=true", self.slug, form_slug)
        ).await?;
        let mut answers: Vec<FormAnswer> = vec![];
        for entry in data {
            answers.push(serde_json::from_value(entry).context("Cannot parse FormAnswer")?)
        }
        Ok(answers)
    }
}