use bytes::Bytes;
use http::{Request, Response, StatusCode, header};
use http_body::Body;
use http_body_util::{BodyExt, Empty};
use thiserror::Error;
use tower::{Service, ServiceExt};

use crate::models::profiles_listing::{
    BlockedUsersResponse, FollowersResponse, FollowingResponse, ListenHistoryResponse,
    PinnedClipsResponse, RecentClipsResponse, RemixesInspiredResponse, TopClipsResponse,
    UserProfileResponse,
};

// Macro to reduce boilerplate for GET services
macro_rules! impl_get_service {
    ($service:ident, $request:ident, $error:ident, $response:ident, $endpoint:expr) => {
        #[derive(Clone)]
        pub struct $service<S> {
            inner: S,
        }

        impl<S> $service<S> {
            pub fn new(inner: S) -> Self {
                Self { inner }
            }
        }

        impl<S, SB> Service<$request> for $service<S>
        where
            S: Service<Request<Empty<Bytes>>, Response = Response<SB>> + Clone + Send + 'static,
            S::Future: Send + 'static,
            S::Error: std::error::Error + Send + Sync + 'static,
            SB: Body<Data = Bytes> + Send + 'static,
            SB::Error: std::error::Error + Send + Sync + 'static,
        {
            type Response = $response;
            type Error = $error;
            type Future =
                futures_util::future::BoxFuture<'static, Result<Self::Response, Self::Error>>;

            fn poll_ready(
                &mut self,
                cx: &mut std::task::Context<'_>,
            ) -> std::task::Poll<Result<(), Self::Error>> {
                self.inner
                    .poll_ready(cx)
                    .map_err(|e| $error::Transport(e.to_string()))
            }

            fn call(&mut self, req: $request) -> Self::Future {
                let client = self.inner.clone();
                let url = $endpoint(&req);

                let mut builder = Request::builder().method("GET").uri(&url);

                if let Some(k) = req.api_key.clone() {
                    let headers = builder.headers_mut().expect("request builder usable");
                    headers.insert(
                        header::AUTHORIZATION,
                        format!("Bearer {k}").parse().expect("valid Authorization"),
                    );
                    headers.insert("X-Api-Key", k.parse().expect("valid X-Api-Key"));
                }

                let request = match builder.body(Empty::new()) {
                    Ok(r) => r,
                    Err(e) => return Box::pin(async move { Err($error::BadUrl(e.to_string())) }),
                };

                Box::pin(async move {
                    let resp = client
                        .oneshot(request)
                        .await
                        .map_err(|e| $error::Transport(e.to_string()))?;

                    let status = resp.status();
                    let body_bytes = resp
                        .into_body()
                        .collect()
                        .await
                        .map_err(|_| $error::ReadBody)?
                        .to_bytes();

                    if !status.is_success() {
                        let head = String::from_utf8_lossy(&body_bytes)
                            .chars()
                            .take(400)
                            .collect::<String>();
                        return Err($error::Http {
                            status,
                            body_head: head,
                        });
                    }

                    serde_json::from_slice::<$response>(&body_bytes)
                        .map_err(|e| $error::Json(e.to_string()))
                })
            }
        }
    };
}

// =============================================================================
// GET USER PROFILE
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetUserProfileRequest {
    pub base_url: String,
    pub handle: String,
    pub page: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetUserProfileError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetUserProfile,
    GetUserProfileRequest,
    GetUserProfileError,
    UserProfileResponse,
    |req: &GetUserProfileRequest| {
        let mut url = format!(
            "{}/api/profiles/{}",
            req.base_url.trim_end_matches('/'),
            &req.handle
        );
        if let Some(page) = req.page {
            url.push_str(&format!("?page={}", page));
        }
        url
    }
);

// =============================================================================
// GET RECENT CLIPS
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetRecentClipsRequest {
    pub base_url: String,
    pub handle: String,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetRecentClipsError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetRecentClips,
    GetRecentClipsRequest,
    GetRecentClipsError,
    RecentClipsResponse,
    |req: &GetRecentClipsRequest| {
        format!(
            "{}/api/profiles/{}/recent_clips",
            req.base_url.trim_end_matches('/'),
            &req.handle
        )
    }
);

// =============================================================================
// GET FOLLOWERS
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetFollowersRequest {
    pub base_url: String,
    pub handle: String,
    pub page: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetFollowersError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetFollowers,
    GetFollowersRequest,
    GetFollowersError,
    FollowersResponse,
    |req: &GetFollowersRequest| {
        let mut url = format!(
            "{}/api/profiles/{}/followers",
            req.base_url.trim_end_matches('/'),
            &req.handle
        );
        if let Some(page) = req.page {
            url.push_str(&format!("?page={}", page));
        }
        url
    }
);

// =============================================================================
// GET FOLLOWING
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetFollowingRequest {
    pub base_url: String,
    pub handle: String,
    pub page: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetFollowingError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetFollowing,
    GetFollowingRequest,
    GetFollowingError,
    FollowingResponse,
    |req: &GetFollowingRequest| {
        let mut url = format!(
            "{}/api/profiles/{}/following",
            req.base_url.trim_end_matches('/'),
            &req.handle
        );
        if let Some(page) = req.page {
            url.push_str(&format!("?page={}", page));
        }
        url
    }
);

// =============================================================================
// GET TOP CLIPS
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetTopClipsRequest {
    pub base_url: String,
    pub size: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetTopClipsError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetTopClips,
    GetTopClipsRequest,
    GetTopClipsError,
    TopClipsResponse,
    |req: &GetTopClipsRequest| {
        let mut url = format!(
            "{}/api/profiles/top_clips",
            req.base_url.trim_end_matches('/')
        );
        if let Some(size) = req.size {
            url.push_str(&format!("?size={}", size));
        }
        url
    }
);

// =============================================================================
// GET PINNED CLIPS
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetPinnedClipsRequest {
    pub base_url: String,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetPinnedClipsError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetPinnedClips,
    GetPinnedClipsRequest,
    GetPinnedClipsError,
    PinnedClipsResponse,
    |req: &GetPinnedClipsRequest| {
        format!(
            "{}/api/profiles/pinned-clips",
            req.base_url.trim_end_matches('/')
        )
    }
);

// =============================================================================
// GET REMIXES INSPIRED
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetRemixesInspiredRequest {
    pub base_url: String,
    pub handle: String,
    pub page: Option<i32>,
    pub page_size: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetRemixesInspiredError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetRemixesInspired,
    GetRemixesInspiredRequest,
    GetRemixesInspiredError,
    RemixesInspiredResponse,
    |req: &GetRemixesInspiredRequest| {
        let mut url = format!(
            "{}/api/profiles/{}/remixes-inspired",
            req.base_url.trim_end_matches('/'),
            &req.handle
        );
        let mut params = Vec::new();
        if let Some(page) = req.page {
            params.push(format!("page={}", page));
        }
        if let Some(page_size) = req.page_size {
            params.push(format!("page_size={}", page_size));
        }
        if !params.is_empty() {
            url.push('?');
            url.push_str(&params.join("&"));
        }
        url
    }
);

// =============================================================================
// GET BLOCKED USERS
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetBlockedUsersRequest {
    pub base_url: String,
    pub page: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetBlockedUsersError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetBlockedUsers,
    GetBlockedUsersRequest,
    GetBlockedUsersError,
    BlockedUsersResponse,
    |req: &GetBlockedUsersRequest| {
        let mut url = format!(
            "{}/api/profiles/blocked_users",
            req.base_url.trim_end_matches('/')
        );
        if let Some(page) = req.page {
            url.push_str(&format!("?page={}", page));
        }
        url
    }
);

// =============================================================================
// GET LISTEN HISTORY
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetListenHistoryRequest {
    pub base_url: String,
    pub handle: String,
    pub cursor: Option<String>,
    pub limit: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetListenHistoryError {
    #[error("bad url: {0}")]
    BadUrl(String),
    #[error("transport: {0}")]
    Transport(String),
    #[error("http {status}, head: {body_head}")]
    Http {
        status: StatusCode,
        body_head: String,
    },
    #[error("read body")]
    ReadBody,
    #[error("json: {0}")]
    Json(String),
}

impl_get_service!(
    GetListenHistory,
    GetListenHistoryRequest,
    GetListenHistoryError,
    ListenHistoryResponse,
    |req: &GetListenHistoryRequest| {
        let mut url = format!(
            "{}/api/profiles/{}/listen-history",
            req.base_url.trim_end_matches('/'),
            &req.handle
        );
        let mut params = Vec::new();
        if let Some(cursor) = &req.cursor {
            params.push(format!("cursor={}", cursor));
        }
        if let Some(limit) = req.limit {
            params.push(format!("limit={}", limit));
        }
        if !params.is_empty() {
            url.push('?');
            url.push_str(&params.join("&"));
        }
        url
    }
);
