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

use crate::models::audio_download::{AudioDownloadRequest, AudioDownloadResponse, AudioFormat, AudioStream, AudioUrlInfo};

/// Request to get download URLs for all audio formats
#[derive(Clone, Debug)]
pub struct GetAudioDownloadRequest {
    pub base_url: String,
    pub download_request: AudioDownloadRequest,
    pub api_key: String,
}

impl GetAudioDownloadRequest {
    pub fn new(
        base_url: impl Into<String>,
        clip_id: impl Into<String>,
        format: AudioFormat,
        api_key: impl Into<String>,
    ) -> Self {
        Self {
            base_url: base_url.into(),
            download_request: AudioDownloadRequest {
                clip_id: clip_id.into(),
                format,
            },
            api_key: api_key.into(),
        }
    }
}

/// Request to stream audio directly
#[derive(Clone, Debug)]
pub struct StreamAudioRequest {
    pub base_url: String,
    pub clip_id: String,
    pub format: AudioFormat,
    pub api_key: String,
}

impl StreamAudioRequest {
    pub fn new(
        base_url: impl Into<String>,
        clip_id: impl Into<String>,
        format: AudioFormat,
        api_key: impl Into<String>,
    ) -> Self {
        Self {
            base_url: base_url.into(),
            clip_id: clip_id.into(),
            format,
            api_key: api_key.into(),
        }
    }
}

/// Request to get CDN MP3 URL
#[derive(Clone, Debug)]
pub struct GetCdnMp3UrlRequest {
    pub base_url: String,
    pub clip_id: String,
    pub api_key: String,
}

impl GetCdnMp3UrlRequest {
    pub fn new(
        base_url: impl Into<String>,
        clip_id: impl Into<String>,
        api_key: impl Into<String>,
    ) -> Self {
        Self {
            base_url: base_url.into(),
            clip_id: clip_id.into(),
            api_key: api_key.into(),
        }
    }
}

#[derive(Debug, Error)]
pub enum AudioDownloadError {
    #[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),
    #[error("request body: {0}")]
    RequestBody(String),
    #[error("unsupported format")]
    UnsupportedFormat,
}

/// Tower service for getting audio download URLs
/// GET /api/clips/{clip_id}/audio-urls
#[derive(Clone)]
pub struct GetAudioDownload<S> {
    inner: S,
}

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

impl<S, SB> Service<GetAudioDownloadRequest> for GetAudioDownload<S>
where
    S: Service<Request<Full<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 = AudioDownloadResponse;
    type Error = AudioDownloadError;
    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| AudioDownloadError::Transport(e.to_string()))
    }

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

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

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

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

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

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

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

            // Parse response - could be array of URLs or object with format keys
            serde_json::from_slice::<AudioDownloadResponse>(&body_bytes)
                .map_err(|e| AudioDownloadError::Json(e.to_string()))
        })
    }
}

/// Tower service for streaming audio directly
/// GET /api/clips/{clip_id}/audio or similar
#[derive(Clone)]
pub struct StreamAudio<S> {
    inner: S,
}

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

impl<S, SB> Service<StreamAudioRequest> for StreamAudio<S>
where
    S: Service<Request<Full<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 = AudioStream;
    type Error = AudioDownloadError;
    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| AudioDownloadError::Transport(e.to_string()))
    }

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

        // Build URL based on format
        let format_param = match req.format {
            AudioFormat::Mp3 => "mp3",
            AudioFormat::Wav => "wav",
            AudioFormat::Stem => "stem",
        };

        let url = format!(
            "{}/api/clips/{}/audio?format={}",
            req.base_url.trim_end_matches('/'),
            req.clip_id,
            format_param
        );

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

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

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

        let format = req.format;

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

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

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

            Ok(AudioStream::new(body_bytes, format))
        })
    }
}

/// Tower service for getting CDN MP3 URLs
/// GET /api/clips/{clip_id}/cdn-mp3-url
#[derive(Clone)]
pub struct GetCdnMp3Url<S> {
    inner: S,
}

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

impl<S, SB> Service<GetCdnMp3UrlRequest> for GetCdnMp3Url<S>
where
    S: Service<Request<Full<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 = AudioUrlInfo;
    type Error = AudioDownloadError;
    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| AudioDownloadError::Transport(e.to_string()))
    }

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

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

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

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

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

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

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

            // Parse as AudioUrlInfo
            serde_json::from_slice::<AudioUrlInfo>(&body_bytes)
                .map_err(|e| AudioDownloadError::Json(e.to_string()))
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_audio_download_request_creation() {
        let req = GetAudioDownloadRequest::new(
            "https://api.example.com",
            "clip-123",
            AudioFormat::Mp3,
            "test_key",
        );

        assert_eq!(req.base_url, "https://api.example.com");
        assert_eq!(req.download_request.clip_id, "clip-123");
        assert_eq!(req.download_request.format, AudioFormat::Mp3);
        assert_eq!(req.api_key, "test_key");
    }

    #[test]
    fn test_stream_audio_request_creation() {
        let req = StreamAudioRequest::new(
            "https://api.example.com",
            "clip-123",
            AudioFormat::Wav,
            "test_key",
        );

        assert_eq!(req.clip_id, "clip-123");
        assert_eq!(req.format, AudioFormat::Wav);
    }

    #[test]
    fn test_get_cdn_mp3_url_request_creation() {
        let req = GetCdnMp3UrlRequest::new(
            "https://api.example.com",
            "clip-123",
            "test_key",
        );

        assert_eq!(req.clip_id, "clip-123");
    }
}
