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 uuid::Uuid;

use crate::models::clips_listing::{
    ClipPromptsResponse, DirectChildrenByUserResponse, GetSongsByIdsResponse, SimilarClipsResponse,
    TrashedClipsResponse,
};

// =============================================================================
// GET SIMILAR CLIPS
// =============================================================================

#[derive(Clone, Debug)]
pub struct GetSimilarClipsRequest {
    pub base_url: String,
    pub clip_id: Uuid,
    pub count: Option<i32>,
    pub api_key: Option<String>,
}

#[derive(Debug, Error)]
pub enum GetSimilarClipsError {
    #[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),
}

#[derive(Clone)]
pub struct GetSimilarClips<S> {
    inner: S,
}

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

impl<S, SB> Service<GetSimilarClipsRequest> for GetSimilarClips<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 = SimilarClipsResponse;
    type Error = GetSimilarClipsError;
    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| GetSimilarClipsError::Transport(e.to_string()))
    }

    fn call(&mut self, req: GetSimilarClipsRequest) -> Self::Future {
        let client = self.inner.clone();
        let mut url = format!(
            "{}/api/clips/get_similar?id={}",
            req.base_url.trim_end_matches('/'),
            req.clip_id
        );

        if let Some(count) = req.count {
            url.push_str(&format!("&count={}", count));
        }

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

        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(GetSimilarClipsError::BadUrl(e.to_string())) });
            }
        };

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

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

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

            serde_json::from_slice::<SimilarClipsResponse>(&body_bytes)
                .map_err(|e| GetSimilarClipsError::Json(e.to_string()))
        })
    }
}

// =============================================================================
// GET SONGS BY IDS
// =============================================================================

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

#[derive(Debug, Error)]
pub enum GetSongsByIdsError {
    #[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),
}

#[derive(Clone)]
pub struct GetSongsById<S> {
    inner: S,
}

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

impl<S, SB> Service<GetSongsByIdsRequest> for GetSongsById<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 = GetSongsByIdsResponse;
    type Error = GetSongsByIdsError;
    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| GetSongsByIdsError::Transport(e.to_string()))
    }

    fn call(&mut self, req: GetSongsByIdsRequest) -> Self::Future {
        let client = self.inner.clone();
        let ids_str = req
            .clip_ids
            .iter()
            .map(|id| id.to_string())
            .collect::<Vec<_>>()
            .join(",");

        let url = format!(
            "{}/api/clips/get_songs_by_ids?ids={}",
            req.base_url.trim_end_matches('/'),
            ids_str
        );

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

        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(GetSongsByIdsError::BadUrl(e.to_string())) });
            }
        };

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

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

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

            serde_json::from_slice::<GetSongsByIdsResponse>(&body_bytes)
                .map_err(|e| GetSongsByIdsError::Json(e.to_string()))
        })
    }
}

// =============================================================================
// GET TRASHED CLIPS
// =============================================================================

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

#[derive(Debug, Error)]
pub enum GetTrashedClipsError {
    #[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),
}

#[derive(Clone)]
pub struct GetTrashedClips<S> {
    inner: S,
}

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

impl<S, SB> Service<GetTrashedClipsRequest> for GetTrashedClips<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 = TrashedClipsResponse;
    type Error = GetTrashedClipsError;
    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| GetTrashedClipsError::Transport(e.to_string()))
    }

    fn call(&mut self, req: GetTrashedClipsRequest) -> Self::Future {
        let client = self.inner.clone();
        let mut url = format!(
            "{}/api/clips/trashed_v2",
            req.base_url.trim_end_matches('/')
        );

        let mut params = Vec::new();
        if let Some(page) = req.page {
            params.push(format!("page={}", page));
        }
        if let Some(cursor) = req.cursor {
            params.push(format!("cursor={}", cursor));
        }
        if let Some(size) = req.page_size {
            params.push(format!("page_size={}", size));
        }

        if !params.is_empty() {
            url.push('?');
            url.push_str(&params.join("&"));
        }

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

        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(GetTrashedClipsError::BadUrl(e.to_string())) });
            }
        };

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

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

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

            serde_json::from_slice::<TrashedClipsResponse>(&body_bytes)
                .map_err(|e| GetTrashedClipsError::Json(e.to_string()))
        })
    }
}

// =============================================================================
// GET CLIP PROMPTS
// =============================================================================

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

#[derive(Debug, Error)]
pub enum GetClipPromptsError {
    #[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),
}

#[derive(Clone)]
pub struct GetClipPrompts<S> {
    inner: S,
}

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

impl<S, SB> Service<GetClipPromptsRequest> for GetClipPrompts<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 = ClipPromptsResponse;
    type Error = GetClipPromptsError;
    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| GetClipPromptsError::Transport(e.to_string()))
    }

    fn call(&mut self, req: GetClipPromptsRequest) -> Self::Future {
        let client = self.inner.clone();
        let mut url = format!(
            "{}/api/clips/clip_prompts",
            req.base_url.trim_end_matches('/')
        );

        let mut params = Vec::new();
        if let Some(page) = req.page {
            params.push(format!("page={}", page));
        }
        if let Some(per_page) = req.per_page {
            params.push(format!("per_page={}", per_page));
        }
        if let Some(filter_type) = req.filter_prompt_type {
            params.push(format!("filter_prompt_type={}", filter_type));
        }

        if !params.is_empty() {
            url.push('?');
            url.push_str(&params.join("&"));
        }

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

        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(GetClipPromptsError::BadUrl(e.to_string())) });
            }
        };

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

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

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

            serde_json::from_slice::<ClipPromptsResponse>(&body_bytes)
                .map_err(|e| GetClipPromptsError::Json(e.to_string()))
        })
    }
}

// =============================================================================
// GET DIRECT CHILDREN BY USER
// =============================================================================

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

#[derive(Debug, Error)]
pub enum GetDirectChildrenByUserError {
    #[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),
}

#[derive(Clone)]
pub struct GetDirectChildrenByUser<S> {
    inner: S,
}

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

impl<S, SB> Service<GetDirectChildrenByUserRequest> for GetDirectChildrenByUser<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 = DirectChildrenByUserResponse;
    type Error = GetDirectChildrenByUserError;
    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| GetDirectChildrenByUserError::Transport(e.to_string()))
    }

    fn call(&mut self, req: GetDirectChildrenByUserRequest) -> Self::Future {
        let client = self.inner.clone();
        let mut url = format!(
            "{}/api/clips/direct_children_by_user?clip_id={}",
            req.base_url.trim_end_matches('/'),
            req.clip_id
        );

        if let Some(page) = req.page {
            url.push_str(&format!("&page={}", page));
        }
        if let Some(size) = req.page_size {
            url.push_str(&format!("&page_size={}", size));
        }

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

        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(GetDirectChildrenByUserError::BadUrl(e.to_string()))
                });
            }
        };

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

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

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

            serde_json::from_slice::<DirectChildrenByUserResponse>(&body_bytes)
                .map_err(|e| GetDirectChildrenByUserError::Json(e.to_string()))
        })
    }
}
