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::generation::{
    GenerateParams, GenerationResponse, InfillGenerationParams,
    PollingStatus, ContinueGenerationParams,
};

/// Request to submit a generation
#[derive(Clone, Debug)]
pub struct SubmitGenerationRequest {
    pub base_url: String,
    pub params: GenerateParams,
    pub api_key: String,
}

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

/// Request to poll generation status
#[derive(Clone, Debug)]
pub struct PollGenerationRequest {
    pub base_url: String,
    pub clip_id: String,
    pub api_key: String,
}

impl PollGenerationRequest {
    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(),
        }
    }
}

/// Request to batch poll multiple clips
#[derive(Clone, Debug)]
pub struct BatchPollRequest {
    pub base_url: String,
    pub clip_ids: Vec<String>,
    pub api_key: String,
}

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

/// Request to continue generation
#[derive(Clone, Debug)]
pub struct ContinueGenerationRequest {
    pub base_url: String,
    pub params: ContinueGenerationParams,
    pub api_key: String,
}

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

/// Request to perform infill generation
#[derive(Clone, Debug)]
pub struct InfillGenerationRequest {
    pub base_url: String,
    pub params: InfillGenerationParams,
    pub api_key: String,
}

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

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

/// Tower service for submitting generation requests
/// POST /api/generate/v2
#[derive(Clone)]
pub struct SubmitGeneration<S> {
    inner: S,
}

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

impl<S, SB> Service<SubmitGenerationRequest> for SubmitGeneration<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 = GenerationResponse;
    type Error = GenerationError;
    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| GenerationError::Transport(e.to_string()))
    }

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

        // Serialize request body
        let body_bytes = match serde_json::to_vec(&req.params) {
            Ok(b) => b,
            Err(e) => {
                return Box::pin(
                    async move { Err(GenerationError::RequestBody(e.to_string())) },
                );
            }
        };

        let mut builder = Request::builder()
            .method("POST")
            .uri(url)
            .header(header::CONTENT_TYPE, "application/json");

        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::from(body_bytes))) {
            Ok(r) => r,
            Err(e) => {
                return Box::pin(async move { Err(GenerationError::BadUrl(e.to_string())) });
            }
        };

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

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

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

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

/// Tower service for polling generation status
/// GET /api/feed/v2?ids=<clip-id>
/// Note: Uses feed/v2 endpoint with ids parameter, not individual clip endpoint
#[derive(Clone)]
pub struct PollGeneration<S> {
    inner: S,
}

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

impl<S, SB> Service<PollGenerationRequest> for PollGeneration<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 = PollingStatus;
    type Error = GenerationError;
    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| GenerationError::Transport(e.to_string()))
    }

    fn call(&mut self, req: PollGenerationRequest) -> Self::Future {
        let client = self.inner.clone();
        // Use feed/v2 endpoint with ids parameter - this is how clients poll for generation status
        let url = format!(
            "{}/api/feed/v2?ids={}",
            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(GenerationError::BadUrl(e.to_string())) });
            }
        };

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

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

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

            // Feed v2 returns a FeedResponse with clips array
            let feed_response: crate::models::feed::FeedResponse = serde_json::from_slice(&body_bytes)
                .map_err(|e| GenerationError::Json(e.to_string()))?;

            // Get the first clip from the response (should only be one since we queried by ID)
            let clip = feed_response.clips.into_iter().next()
                .ok_or_else(|| GenerationError::Json("No clips in response".to_string()))?;

            // Map feed clip status string to ClipStatus enum
            // IMPORTANT: Check for CDN URL, not just any audio_url
            // - Streaming URLs (audiopipe) appear during generation
            // - CDN URLs (cdn1.suno.ai) appear when complete
            let has_cdn_url = clip.audio_url.contains("cdn") && !clip.audio_url.is_empty();

            let clip_status = if has_cdn_url && clip.status == "complete" {
                crate::models::generation::ClipStatus::Complete
            } else {
                match clip.status.as_str() {
                    "complete" => crate::models::generation::ClipStatus::Complete,
                    "error" => crate::models::generation::ClipStatus::Error,
                    "submitted" => crate::models::generation::ClipStatus::Submitted,
                    "streaming" => crate::models::generation::ClipStatus::Pending,
                    "queued" => crate::models::generation::ClipStatus::Pending,
                    _ => crate::models::generation::ClipStatus::Pending,
                }
            };

            Ok(PollingStatus {
                clip_id: clip.id.to_string(),
                status: clip_status,
                // Only return CDN URLs, not streaming URLs
                audio_url: if has_cdn_url { Some(clip.audio_url) } else { None },
                error: None,
            })
        })
    }
}

/// Tower service for batch polling multiple clips
/// GET /api/feed/v2?ids=<comma-separated-ids>
#[derive(Clone)]
pub struct BatchPoll<S> {
    inner: S,
}

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

impl<S, SB> Service<BatchPollRequest> for BatchPoll<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 = Vec<PollingStatus>;
    type Error = GenerationError;
    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| GenerationError::Transport(e.to_string()))
    }

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

        // Use feed/v2 with comma-separated IDs - efficient batch polling
        let ids_param = req.clip_ids.join(",");
        let url = format!(
            "{}/api/feed/v2?ids={}",
            req.base_url.trim_end_matches('/'),
            ids_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(GenerationError::BadUrl(e.to_string())) });
            }
        };

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

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

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

            // Feed v2 returns FeedResponse with clips array
            let feed_response: crate::models::feed::FeedResponse = serde_json::from_slice(&body_bytes)
                .map_err(|e| GenerationError::Json(e.to_string()))?;

            // Convert feed clips to PollingStatus
            let results = feed_response.clips.into_iter().map(|clip| {
                // Check for CDN URL (not streaming audiopipe URL)
                let has_cdn_url = clip.audio_url.contains("cdn") && !clip.audio_url.is_empty();

                let clip_status = if has_cdn_url && clip.status == "complete" {
                    crate::models::generation::ClipStatus::Complete
                } else {
                    match clip.status.as_str() {
                        "complete" => crate::models::generation::ClipStatus::Complete,
                        "error" => crate::models::generation::ClipStatus::Error,
                        "submitted" => crate::models::generation::ClipStatus::Submitted,
                        "streaming" => crate::models::generation::ClipStatus::Pending,
                        "queued" => crate::models::generation::ClipStatus::Pending,
                        _ => crate::models::generation::ClipStatus::Pending,
                    }
                };

                PollingStatus {
                    clip_id: clip.id.to_string(),
                    status: clip_status,
                    // Only return CDN URLs, not streaming URLs
                    audio_url: if has_cdn_url { Some(clip.audio_url) } else { None },
                    error: None,
                }
            }).collect();

            Ok(results)
        })
    }
}

/// Tower service for continuing generation
/// POST /api/generate/v2 with continue_clip_id in the params
/// Note: Continue uses the same endpoint as regular generation
#[derive(Clone)]
pub struct ContinueGeneration<S> {
    inner: S,
}

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

impl<S, SB> Service<ContinueGenerationRequest> for ContinueGeneration<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 = GenerationResponse;
    type Error = GenerationError;
    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| GenerationError::Transport(e.to_string()))
    }

    fn call(&mut self, req: ContinueGenerationRequest) -> Self::Future {
        let client = self.inner.clone();
        // Continue uses the same v2 endpoint, just with continue_clip_id param
        let url = format!(
            "{}/api/generate/v2",
            req.base_url.trim_end_matches('/')
        );

        // Convert ContinueGenerationParams to GenerateParams with continue_clip_id
        let mut gen_params = GenerateParams::new(&req.params.prompt);
        if let Some(title) = &req.params.title {
            gen_params = gen_params.with_title(title);
        }
        if let Some(tags) = &req.params.tags {
            gen_params = gen_params.with_tags(tags.clone());
        }
        gen_params = gen_params
            .with_continue(&req.params.continue_clip_id)
            .with_task("extend");

        let body_bytes = match serde_json::to_vec(&gen_params) {
            Ok(b) => b,
            Err(e) => {
                return Box::pin(
                    async move { Err(GenerationError::RequestBody(e.to_string())) },
                );
            }
        };

        let mut builder = Request::builder()
            .method("POST")
            .uri(url)
            .header(header::CONTENT_TYPE, "application/json");

        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::from(body_bytes))) {
            Ok(r) => r,
            Err(e) => {
                return Box::pin(async move { Err(GenerationError::BadUrl(e.to_string())) });
            }
        };

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

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

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

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

/// Tower service for infill generation
/// POST /api/generate/v2 with infill_start_s and infill_end_s parameters
/// Note: Infill uses the same endpoint as regular generation
#[derive(Clone)]
pub struct InfillGeneration<S> {
    inner: S,
}

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

impl<S, SB> Service<InfillGenerationRequest> for InfillGeneration<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 = GenerationResponse;
    type Error = GenerationError;
    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| GenerationError::Transport(e.to_string()))
    }

    fn call(&mut self, req: InfillGenerationRequest) -> Self::Future {
        let client = self.inner.clone();
        // Infill uses the same v2 endpoint with infill params
        let url = format!(
            "{}/api/generate/v2",
            req.base_url.trim_end_matches('/')
        );

        // Build GenerateParams with infill parameters
        let mut gen_params = GenerateParams::new(&req.params.prompt);
        gen_params = gen_params
            .with_continue(&req.params.clip_id)
            .with_infill(req.params.start_s, req.params.end_s)
            .with_task("infill");

        if let Some(title) = &req.params.title {
            gen_params = gen_params.with_title(title);
        }

        let body_bytes = match serde_json::to_vec(&gen_params) {
            Ok(b) => b,
            Err(e) => {
                return Box::pin(
                    async move { Err(GenerationError::RequestBody(e.to_string())) },
                );
            }
        };

        let mut builder = Request::builder()
            .method("POST")
            .uri(url)
            .header(header::CONTENT_TYPE, "application/json");

        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::from(body_bytes))) {
            Ok(r) => r,
            Err(e) => {
                return Box::pin(async move { Err(GenerationError::BadUrl(e.to_string())) });
            }
        };

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

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

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

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

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

    #[test]
    fn test_submit_generation_request_creation() {
        let params = GenerateParams::new("test prompt");
        let req = SubmitGenerationRequest::new(
            "https://api.example.com",
            params,
            "test_key",
        );

        assert_eq!(req.base_url, "https://api.example.com");
        assert_eq!(req.api_key, "test_key");
    }

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

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

    #[test]
    fn test_batch_poll_request_creation() {
        let clip_ids = vec![
            "clip-1".to_string(),
            "clip-2".to_string(),
            "clip-3".to_string(),
        ];
        let req = BatchPollRequest::new(
            "https://api.example.com",
            clip_ids.clone(),
            "test_key",
        );

        assert_eq!(req.clip_ids.len(), 3);
        assert_eq!(req.clip_ids[0], "clip-1");
    }
}
