// Copyright 2025 SkyReels SDK Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::error::{Error, Result};
use crate::client::SkyReelsClient;
use crate::text2video::{Mode, TaskStatus, SubmitResponse, TaskResponse};
use serde::Serialize;

/// Image-to-video generation module
///
/// This module provides functionality to generate videos from images and text prompts using the SkyReels API.
///
/// # Example
///
/// ```rust,no_run
/// use skyreels::{SkyReelsClient, Mode};
///
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = SkyReelsClient::new("your_api_key");
///
/// // Create and submit a task
/// let request = client.image2video()
///     .submit_request(
///         "Slowly pull the camera back and then cut to a frontal close up shot",
///         "https://example.com/image.jpg"
///     )
///     .duration(5)
///     .sound(true)
///     .mode(Mode::Std)
///     .build();
///
/// let task_id = client.image2video().submit(request).await?;
///
/// // Query task status
/// let status = client.image2video()
///     .get_task(&task_id)
///     .await?;
///
/// # Ok(())
/// # }
/// ```

/// Builder for image-to-video task submission
#[derive(Debug, Clone, Serialize)]
pub struct Image2VideoRequest {
    api_key: String,
    prompt: String,
    first_frame_image: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    duration: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    sound: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    mode: Option<String>,
}

impl Image2VideoRequest {
    /// Create a new image-to-video request builder
    pub fn new(
        api_key: impl Into<String>,
        prompt: impl Into<String>,
        first_frame_image: impl Into<String>,
    ) -> Self {
        Self {
            api_key: api_key.into(),
            prompt: prompt.into(),
            first_frame_image: first_frame_image.into(),
            duration: None,
            sound: None,
            mode: None,
        }
    }

    /// Set the video duration (3-15 seconds)
    pub fn duration(mut self, duration: i32) -> Self {
        if duration < 3 || duration > 15 {
            tracing::warn!("Duration {} is out of range [3, 15], clamping", duration);
            self.duration = Some(duration.clamp(3, 15));
        } else {
            self.duration = Some(duration);
        }
        self
    }

    /// Set whether to include sound effects
    pub fn sound(mut self, sound: bool) -> Self {
        self.sound = Some(sound);
        self
    }

    /// Set the quality/performance mode
    pub fn mode(mut self, mode: Mode) -> Self {
        self.mode = Some(mode.to_string());
        self
    }

    /// Build the request
    pub fn build(self) -> Self {
        self
    }
}

/// Image-to-video API handler
pub struct Image2Video<'a> {
    pub(crate) client: &'a SkyReelsClient,
}

impl<'a> Image2Video<'a> {
    /// Create a new image-to-video handler
    pub(crate) fn new(client: &'a SkyReelsClient) -> Self {
        Self { client }
    }

    /// Create a new image-to-video request builder
    pub fn submit_request(
        &self,
        prompt: impl Into<String>,
        first_frame_image: impl Into<String>,
    ) -> Image2VideoRequest {
        Image2VideoRequest::new(
            self.client.api_key_sync(),
            prompt,
            first_frame_image,
        )
    }

    /// Submit an image-to-video task directly
    pub async fn submit(&self, request: Image2VideoRequest) -> Result<String> {
        let base_url = self.client.base_url_sync();
        let url = format!("{}/video/image2video/submit", base_url);

        tracing::debug!("Submitting image-to-video task to: {}", url);

        let response = self.client
            .http_client()
            .post(&url)
            .json(&request)
            .send()
            .await?;

        let status = response.status();
        let response_text = response.text().await?;

        // Output raw JSON response for debugging
        tracing::debug!("Raw API response (submit): {}", response_text);

        if !status.is_success() {
            return Err(Error::api_error(
                status.as_u16(),
                format!("{}: {}", status.as_u16(), response_text),
            ));
        }

        let submit_response: SubmitResponse = serde_json::from_str(&response_text)
            .map_err(|e| Error::ResponseParseError(format!("Failed to parse response: {}", e)))?;

        // Log parsed response for debugging
        tracing::debug!("Parsed submit response: task_id={}, code={}, status={}",
            submit_response.task_id, submit_response.code, submit_response.status);

        if submit_response.code != 200 {
            return Err(Error::SubmissionFailed(submit_response.msg));
        }

        tracing::info!("Task submitted successfully: {}", submit_response.task_id);

        Ok(submit_response.task_id)
    }

    /// Query task status by task ID
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// # use skyreels::SkyReelsClient;
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let client = SkyReelsClient::new("your_api_key");
    ///
    /// let status = client.image2video()
    ///     .get_task("task_id_here")
    ///     .await?;
    ///
    /// println!("Status: {}", status.get_status());
    /// if let Some(video) = status.get_video_data() {
    ///     println!("Video URL: {}", video.video_url);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_task(&self, task_id: &str) -> Result<TaskResponse> {
        let base_url = self.client.base_url_sync();
        let url = format!("{}/video/image2video/task/{}", base_url, task_id);

        tracing::debug!("Querying task status: {}", url);

        let response = self.client
            .http_client()
            .get(&url)
            .send()
            .await?;

        let status = response.status();
        let response_text = response.text().await?;

        // Output raw JSON response for debugging
        tracing::debug!("Raw API response (query): {}", response_text);

        if !status.is_success() {
            return Err(Error::api_error(
                status.as_u16(),
                format!("{}: {}", status.as_u16(), response_text),
            ));
        }

        let task_response: TaskResponse = serde_json::from_str(&response_text)
            .map_err(|e| Error::ResponseParseError(format!("Failed to parse response: {}", e)))?;

        // Log parsed response for debugging
        tracing::debug!("Parsed task response: task_id={}, status={}, code={}",
            task_response.task_id, task_response.status, task_response.code);

        if let Some(ref video_data) = task_response.data {
            tracing::debug!("Video data: url={}, duration={}, resolution={}",
                video_data.video_url, video_data.duration, video_data.resolution);
        }

        tracing::info!("Task {} status: {}", task_id, task_response.get_status());

        Ok(task_response)
    }

    /// Wait for task completion (polling)
    ///
    /// This method polls the task status until it completes or fails.
    ///
    /// # Parameters
    ///
    /// * `task_id` - The task ID to wait for
    /// * `poll_interval_secs` - Polling interval in seconds (default: 5)
    /// * `max_attempts` - Maximum number of polling attempts (default: 120)
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// # use skyreels::SkyReelsClient;
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let client = SkyReelsClient::new("your_api_key");
    ///
    /// let request = client.image2video()
    ///     .submit_request("A beautiful sunset", "https://example.com/image.jpg")
    ///     .build();
    ///
    /// let task_id = client.image2video().submit(request).await?;
    ///
    /// let result = client.image2video()
    ///     .wait_for_completion(&task_id, 5, 120)
    ///     .await?;
    ///
    /// if let Some(video) = result.get_video_data() {
    ///     println!("Video ready: {}", video.video_url);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn wait_for_completion(
        &self,
        task_id: &str,
        poll_interval_secs: u64,
        max_attempts: usize,
    ) -> Result<TaskResponse> {
        let mut attempts = 0;

        loop {
            attempts += 1;

            let response = self.get_task(task_id).await?;
            let status = response.get_status();

            if status.is_completed() {
                if status == TaskStatus::Success {
                    tracing::info!("Task {} completed successfully", task_id);
                    return Ok(response);
                } else {
                    return Err(Error::QueryFailed(format!(
                        "Task {} failed: {}",
                        task_id, response.msg
                    )));
                }
            }

            if attempts >= max_attempts {
                return Err(Error::QueryFailed(format!(
                    "Task {} did not complete after {} attempts",
                    task_id, max_attempts
                )));
            }

            tracing::debug!(
                "Task {} is {}, waiting {} seconds... (attempt {}/{})",
                task_id,
                status,
                poll_interval_secs,
                attempts,
                max_attempts
            );

            tokio::time::sleep(tokio::time::Duration::from_secs(poll_interval_secs)).await;
        }
    }
}

// Helper methods for the builder pattern
impl Image2VideoRequest {
    /// Submit the request
    pub async fn submit(self) -> Result<String> {
        // This is a convenience method that will be called through the client
        Err(Error::InvalidParameter(
            "Use client.image2video().submit(request) instead".to_string(),
        ))
    }
}

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

    #[test]
    fn test_request_creation() {
        let request = Image2VideoRequest::new(
            "test_key",
            "A beautiful sunset",
            "https://example.com/image.jpg"
        );
        assert_eq!(request.prompt, "A beautiful sunset");
        assert_eq!(request.first_frame_image, "https://example.com/image.jpg");
    }

    #[test]
    fn test_duration_clamping() {
        let request = Image2VideoRequest::new(
            "test_key",
            "test",
            "https://example.com/image.jpg"
        ).duration(20);
        assert_eq!(request.duration, Some(15));

        let request2 = Image2VideoRequest::new(
            "test_key",
            "test",
            "https://example.com/image.jpg"
        ).duration(1);
        assert_eq!(request2.duration, Some(3));
    }
}
