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;

use crate::models::feed::{FeedRequest, FeedResponse};

/// Configuration for feed batch middleware
#[derive(Debug, Clone)]
pub struct FeedBatchConfig {
    /// Time window (ms) to collect requests before sending
    pub batch_window_ms: u64,
    /// Maximum requests to batch together
    pub max_batch_size: usize,
    /// Maximum requests per second allowed
    pub rate_limit_per_sec: u32,
}

impl Default for FeedBatchConfig {
    fn default() -> Self {
        Self {
            batch_window_ms: 100,
            max_batch_size: 10,
            rate_limit_per_sec: 10,
        }
    }
}

/// Request wrapper for feed batching
#[derive(Clone, Debug)]
pub struct BatchedFeedRequest {
    pub base_url: String,
    pub feed_request: FeedRequest,
    pub api_key: Option<String>,
}

impl BatchedFeedRequest {
    pub fn new(
        base_url: impl Into<String>,
        feed_request: FeedRequest,
        api_key: Option<String>,
    ) -> Self {
        Self {
            base_url: base_url.into(),
            feed_request,
            api_key,
        }
    }
}

#[derive(Debug, Error)]
pub enum FeedBatchError {
    #[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("rate limit exceeded")]
    RateLimited,
}

/// Helper to send a single feed request
async fn send_feed_request<S, SB>(
    client: &mut S,
    base_url: &str,
    feed_request: &FeedRequest,
    api_key: &Option<String>,
) -> Result<FeedResponse, FeedBatchError>
where
    S: Service<Request<Full<Bytes>>, Response = Response<SB>> + Clone,
    S::Future: std::future::Future<Output = Result<Response<SB>, S::Error>>,
    S::Error: std::error::Error + Send + Sync + 'static,
    SB: Body<Data = Bytes> + Send,
    SB::Error: std::error::Error + Send + Sync + 'static,
{
    let url = format!("{}/api/feed/v3", base_url.trim_end_matches('/'));

    let body_bytes = serde_json::to_vec(feed_request)
        .map_err(|e| FeedBatchError::RequestBody(e.to_string()))?;

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

    if let Some(k) = api_key {
        let headers = builder
            .headers_mut()
            .ok_or_else(|| FeedBatchError::BadUrl("Cannot set headers".to_string()))?;
        headers.insert(
            header::AUTHORIZATION,
            format!("Bearer {k}")
                .parse()
                .map_err(|_| FeedBatchError::BadUrl("Invalid auth header".to_string()))?,
        );
        headers.insert(
            "X-Api-Key",
            k.parse()
                .map_err(|_| FeedBatchError::BadUrl("Invalid api key header".to_string()))?,
        );
    }

    let http_request = builder
        .body(Full::new(Bytes::from(body_bytes)))
        .map_err(|e| FeedBatchError::BadUrl(e.to_string()))?;

    let resp = client
        .call(http_request)
        .await
        .map_err(|e| FeedBatchError::Transport(e.to_string()))?;

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

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

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

/// Simple feed request service (non-batching for now)
/// Full batching with queuing would require async-aware state management
#[derive(Clone)]
pub struct FeedRequestService<S> {
    inner: S,
    _config: FeedBatchConfig,
}

impl<S> FeedRequestService<S> {
    pub fn new(inner: S, config: FeedBatchConfig) -> Self {
        Self {
            inner,
            _config: config,
        }
    }

    pub fn with_default_config(inner: S) -> Self {
        Self::new(inner, FeedBatchConfig::default())
    }
}

impl<S, SB> Service<BatchedFeedRequest> for FeedRequestService<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 = FeedResponse;
    type Error = FeedBatchError;
    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| FeedBatchError::Transport(e.to_string()))
    }

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

        Box::pin(async move {
            send_feed_request(&mut client, &base_url, &feed_request, &api_key).await
        })
    }
}

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

    #[test]
    fn test_feed_batch_config_default() {
        let config = FeedBatchConfig::default();
        assert_eq!(config.batch_window_ms, 100);
        assert_eq!(config.max_batch_size, 10);
        assert_eq!(config.rate_limit_per_sec, 10);
    }

    #[test]
    fn test_feed_batch_config_custom() {
        let config = FeedBatchConfig {
            batch_window_ms: 200,
            max_batch_size: 20,
            rate_limit_per_sec: 5,
        };
        assert_eq!(config.batch_window_ms, 200);
        assert_eq!(config.max_batch_size, 20);
        assert_eq!(config.rate_limit_per_sec, 5);
    }

    #[test]
    fn test_batched_feed_request_creation() {
        let req = BatchedFeedRequest::new(
            "https://api.example.com",
            FeedRequest {
                cursor: None,
                limit: 10,
                filters: crate::models::feed::FeedFilters {
                    disliked: "dislike".to_string(),
                    trashed: "trash".to_string(),
                    from_studio_project: crate::models::feed::PresenceFilter {
                        presence: "not".to_string(),
                    },
                    stem: crate::models::feed::PresenceFilter {
                        presence: "not".to_string(),
                    },
                    workspace: crate::models::feed::WorkspaceFilter {
                        presence: "yes".to_string(),
                        workspace_id: uuid::Uuid::new_v4(),
                    },
                },
            },
            Some("test_key".to_string()),
        );

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