#[derive(Clone, Debug, Default)]
pub struct Api {}

macro_rules! url {
    ($path:literal) => {
        concat!("https://api.sumup.com/v1.0", $path)
    };
    ($path:literal, $( $param:expr ),+ ) => {
        &vec![
            url!($path).to_string(),
            $( $param.to_string() ),+
        ].join("/")
    };
}

impl Api {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn token(&self, payload: impl serde::Serialize) -> crate::Result<crate::AccessToken> {
        ureq::post(url!("/token"))
            .send_json(payload)?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn checkout_create(
        &self,
        payload: impl serde::Serialize,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Checkout> {
        ureq::post(url!("/checkouts"))
            .set("Authorization", &access_token.bearer())
            .send_json(payload)?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn checkout_get(
        &self,
        id: &str,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Checkout> {
        ureq::get(url!("/checkouts", id))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn checkout_reference_id(
        &self,
        reference_id: &str,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Checkout> {
        ureq::get(&format!(
            "{}?checkout_reference={}",
            url!("/checkouts"),
            reference_id
        ))
        .set("Authorization", &access_token.bearer())
        .call()?
        .into_json()
        .map_err(crate::Error::from)
    }

    pub fn checkout_delete(&self, id: &str, access_token: &crate::AccessToken) -> crate::Result {
        ureq::delete(url!("/checkouts", id))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)?;

        Ok(())
    }

    pub fn checkout_update(
        &self,
        id: &str,
        payload: impl serde::Serialize,
        access_token: &crate::AccessToken,
    ) -> crate::Result {
        ureq::put(url!("/checkouts", id))
            .set("Authorization", &access_token.bearer())
            .send_json(payload)?
            .into_json()
            .map_err(crate::Error::from)?;

        Ok(())
    }

    pub fn customer_create(
        &self,
        payload: impl serde::Serialize,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Customer> {
        ureq::post(url!("/customers"))
            .set("Authorization", &access_token.bearer())
            .send_json(payload)?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn customer_update(
        &self,
        id: u32,
        payload: impl serde::Serialize,
        access_token: &crate::AccessToken,
    ) -> crate::Result {
        ureq::put(url!("/customers", id))
            .set("Authorization", &access_token.bearer())
            .send_json(payload)?
            .into_json()
            .map_err(crate::Error::from)?;

        Ok(())
    }

    pub fn customer_get(
        &self,
        id: u32,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Customer> {
        ureq::get(url!("/customers", id))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn customer_payment_instruments(
        &self,
        id: u32,
        access_token: &crate::AccessToken,
    ) -> crate::Result<Vec<String>> {
        ureq::get(url!("/customers", id, "payment-instruments"))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn customer_delete_payment_instruments(
        &self,
        id: u32,
        card_token: &str,
        access_token: &crate::AccessToken,
    ) -> crate::Result {
        ureq::delete(url!("/customers", id, "payment-instruments", card_token))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn profile_get(&self, access_token: &crate::AccessToken) -> crate::Result<crate::Profile> {
        ureq::get(url!("/me/merchant-profile"))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn profile_update(
        &self,
        profile: &crate::Profile,
        access_token: &crate::AccessToken,
    ) -> crate::Result {
        ureq::put(url!("/me/merchant-profile"))
            .set("Authorization", &access_token.bearer())
            .send_json(profile)?
            .into_json()
            .map_err(crate::Error::from)?;

        Ok(())
    }

    pub fn profile_doing_business_as_get(
        &self,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::DoingBusinessAs> {
        ureq::get(url!("/me/merchant-profile/doing-business-as"))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn profile_doing_business_as_update(
        &self,
        dba: &crate::DoingBusinessAs,
        access_token: &crate::AccessToken,
    ) -> crate::Result {
        ureq::put(url!("/me/merchant-profile/doing-business-as"))
            .set("Authorization", &access_token.bearer())
            .send_json(dba)?
            .into_json()
            .map_err(crate::Error::from)?;

        Ok(())
    }

    pub fn payouts_list(
        &self,
        filter: &crate::services::payouts::Filter,
        access_token: &crate::AccessToken,
    ) -> crate::Result<Vec<crate::Payout>> {
        ureq::get(&format!(
            "{}?{}",
            url!("/v0.1/me/financials/payouts"),
            filter.to_string()
        ))
        .set("Authorization", &access_token.bearer())
        .call()?
        .into_json()
        .map_err(crate::Error::from)
    }

    pub fn transactions_list(
        &self,
        filter: &crate::services::payouts::Filter,
        access_token: &crate::AccessToken,
    ) -> crate::Result<Vec<crate::Transaction>> {
        ureq::get(&format!(
            "{}?{}",
            url!("/v0.1/me/financials/transactions"),
            filter.to_string()
        ))
        .set("Authorization", &access_token.bearer())
        .call()?
        .into_json()
        .map_err(crate::Error::from)
    }

    pub fn transactions_get(
        &self,
        id: u32,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Transaction> {
        ureq::get(&format!("{}?id={id}", url!("/me/transactions")))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn transactions_get_by_internal_id(
        &self,
        internal_id: u32,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Transaction> {
        ureq::get(&format!(
            "{}?internal_id={internal_id}",
            url!("/me/transactions")
        ))
        .set("Authorization", &access_token.bearer())
        .call()?
        .into_json()
        .map_err(crate::Error::from)
    }

    pub fn transactions_get_by_code(
        &self,
        transaction_code: &str,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Transaction> {
        ureq::get(&format!(
            "{}?transaction_code={transaction_code}",
            url!("/me/transactions")
        ))
        .set("Authorization", &access_token.bearer())
        .call()?
        .into_json()
        .map_err(crate::Error::from)
    }

    pub fn transactions_history(
        &self,
        filter: &crate::services::transactions::Filter,
        access_token: &crate::AccessToken,
    ) -> crate::Result<Vec<crate::Transaction>> {
        ureq::get(&format!(
            "{}?{}",
            url!("/v0.1/me/financials/payouts"),
            filter.to_string()
        ))
        .set("Authorization", &access_token.bearer())
        .call()?
        .into_json()
        .map_err(crate::Error::from)
    }

    pub fn transactions_refund(
        &self,
        id: u32,
        payload: impl serde::Serialize,
        access_token: &crate::AccessToken,
    ) -> crate::Result {
        ureq::get(url!("/v0.1/me/refund", id))
            .set("Authorization", &access_token.bearer())
            .send_json(payload)?
            .into_json()
            .map_err(crate::Error::from)
    }

    pub fn transactions_get_receipt(
        &self,
        id: u32,
        merchant_id: u32,
        access_token: &crate::AccessToken,
    ) -> crate::Result<crate::Receipt> {
        ureq::get(&format!("{}?mid={merchant_id}", url!("/receipts", id)))
            .set("Authorization", &access_token.bearer())
            .call()?
            .into_json()
            .map_err(crate::Error::from)
    }
}
