// 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 serde::{Deserialize, Serialize};

/// Text-to-video generation module
///
/// This module provides functionality to generate videos from text prompts using the SkyReels API.
///
/// # Example
///
/// ```rust,no_run
/// use skyreels::{SkyReelsClient, AspectRatio, 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.text2video()
///     .submit_request("A beautiful sunset over the ocean")
///     .aspect_ratio(AspectRatio::Wide16x9)
///     .duration(5)
///     .sound(true)
///     .mode(Mode::Std)
///     .build();
///
/// let task_id = client.text2video().submit(request).await?;
///
/// // Query task status
/// let status = client.text2video()
///     .get_task(&task_id)
///     .await?;
///
/// # Ok(())
/// # }
/// ```

/// Supported aspect ratios for video generation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AspectRatio {
    /// 16:9 - Landscape/widescreen
    #[serde(rename = "16:9")]
    Wide16x9,
    /// 4:3 - Standard landscape
    #[serde(rename = "4:3")]
    Standard4x3,
    /// 1:1 - Square
    #[serde(rename = "1:1")]
    Square1x1,
    /// 9:16 - Portrait/mobile
    #[serde(rename = "9:16")]
    Portrait9x16,
    /// 3:4 - Standard portrait
    #[serde(rename = "3:4")]
    Standard3x4,
}

impl Default for AspectRatio {
    fn default() -> Self {
        AspectRatio::Wide16x9
    }
}

impl fmt::Display for AspectRatio {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AspectRatio::Wide16x9 => write!(f, "16:9"),
            AspectRatio::Standard4x3 => write!(f, "4:3"),
            AspectRatio::Square1x1 => write!(f, "1:1"),
            AspectRatio::Portrait9x16 => write!(f, "9:16"),
            AspectRatio::Standard3x4 => write!(f, "3:4"),
        }
    }
}

impl std::str::FromStr for AspectRatio {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s {
            "16:9" => Ok(AspectRatio::Wide16x9),
            "4:3" => Ok(AspectRatio::Standard4x3),
            "1:1" => Ok(AspectRatio::Square1x1),
            "9:16" => Ok(AspectRatio::Portrait9x16),
            "3:4" => Ok(AspectRatio::Standard3x4),
            _ => Err(Error::invalid_parameter(format!(
                "Invalid aspect ratio: {}. Must be one of: 16:9, 4:3, 1:1, 9:16, 3:4",
                s
            ))),
        }
    }
}

/// Quality/performance mode for video generation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
    /// Fast mode - quicker generation
    Fast,
    /// Standard mode - balanced speed and quality
    Std,
    /// Professional mode - highest quality
    Pro,
}

impl Default for Mode {
    fn default() -> Self {
        Mode::Std
    }
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Mode::Fast => write!(f, "fast"),
            Mode::Std => write!(f, "std"),
            Mode::Pro => write!(f, "pro"),
        }
    }
}

impl std::str::FromStr for Mode {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "fast" => Ok(Mode::Fast),
            "std" => Ok(Mode::Std),
            "pro" => Ok(Mode::Pro),
            _ => Err(Error::invalid_parameter(format!(
                "Invalid mode: {}. Must be one of: fast, std, pro",
                s
            ))),
        }
    }
}

/// Task status for video generation
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
    /// Task has been submitted
    Submitted,
    /// Task is waiting in queue
    Pending,
    /// Task is being processed
    Running,
    /// Task completed successfully
    Success,
    /// Task failed
    Failed,
    /// Unknown status
    Unknown,
}

impl fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TaskStatus::Submitted => write!(f, "submitted"),
            TaskStatus::Pending => write!(f, "pending"),
            TaskStatus::Running => write!(f, "running"),
            TaskStatus::Success => write!(f, "success"),
            TaskStatus::Failed => write!(f, "failed"),
            TaskStatus::Unknown => write!(f, "unknown"),
        }
    }
}

impl std::str::FromStr for TaskStatus {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "submitted" => Ok(TaskStatus::Submitted),
            "pending" => Ok(TaskStatus::Pending),
            "running" => Ok(TaskStatus::Running),
            "success" => Ok(TaskStatus::Success),
            "failed" => Ok(TaskStatus::Failed),
            _ => Ok(TaskStatus::Unknown),
        }
    }
}

impl TaskStatus {
    /// Check if the task is still in progress
    pub fn is_in_progress(&self) -> bool {
        matches!(self, TaskStatus::Submitted | TaskStatus::Pending | TaskStatus::Running)
    }

    /// Check if the task has completed (successfully or failed)
    pub fn is_completed(&self) -> bool {
        matches!(self, TaskStatus::Success | TaskStatus::Failed)
    }
}

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

impl Text2VideoRequest {
    /// Create a new text-to-video request builder
    pub fn new(api_key: impl Into<String>, prompt: impl Into<String>) -> Self {
        Self {
            api_key: api_key.into(),
            prompt: prompt.into(),
            duration: None,
            aspect_ratio: 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 the aspect ratio
    pub fn aspect_ratio(mut self, ratio: AspectRatio) -> Self {
        self.aspect_ratio = Some(ratio.to_string());
        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
    }
}

/// Response from task submission
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubmitResponse {
    pub task_id: String,
    pub msg: String,
    pub code: u16,
    pub status: String,
    #[serde(default)]
    pub data: Option<serde_json::Value>,
    #[serde(default)]
    pub trace_id: String,
}

impl SubmitResponse {
    /// Convert response to JSON string for debugging
    pub fn to_json(&self) -> Result<String> {
        serde_json::to_string_pretty(self)
            .map_err(|e| Error::ResponseParseError(format!("Failed to serialize to JSON: {}", e)))
    }

    /// Pretty print the response as JSON
    pub fn pretty_print(&self) -> Result<String> {
        self.to_json()
    }
}

/// Video data returned when task is successful
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VideoData {
    pub video_url: String,
    pub duration: f64,  // API returns floating point values like 5.0
    pub resolution: String,
}

/// Response from task query
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TaskResponse {
    pub task_id: String,
    pub msg: String,
    pub code: u16,
    pub status: String,
    #[serde(default)]
    pub data: Option<VideoData>,
    #[serde(default)]
    pub trace_id: String,
}

impl TaskResponse {
    /// Get the task status as an enum
    pub fn get_status(&self) -> TaskStatus {
        self.status.parse().unwrap_or(TaskStatus::Unknown)
    }

    /// Check if the task completed successfully
    pub fn is_success(&self) -> bool {
        self.get_status() == TaskStatus::Success && self.data.is_some()
    }

    /// Get the video data if available
    pub fn get_video_data(&self) -> Option<&VideoData> {
        self.data.as_ref()
    }

    /// Convert response to JSON string for debugging
    pub fn to_json(&self) -> Result<String> {
        serde_json::to_string_pretty(self)
            .map_err(|e| Error::ResponseParseError(format!("Failed to serialize to JSON: {}", e)))
    }

    /// Pretty print the response as JSON
    pub fn pretty_print(&self) -> Result<String> {
        self.to_json()
    }
}

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

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

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

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

        tracing::debug!("Submitting text-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.text2video()
    ///     .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/text2video/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.text2video()
    ///     .submit_request("A beautiful sunset")
    ///     .build();
    ///
    /// let task_id = client.text2video().submit(request).await?;
    ///
    /// let result = client.text2video()
    ///     .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 Text2VideoRequest {
    /// 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.text2video().submit(request) instead".to_string(),
        ))
    }
}

use std::fmt;

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

    #[test]
    fn test_aspect_ratio_from_str() {
        assert_eq!("16:9".parse::<AspectRatio>().unwrap(), AspectRatio::Wide16x9);
        assert_eq!("4:3".parse::<AspectRatio>().unwrap(), AspectRatio::Standard4x3);
        assert!("16:10".parse::<AspectRatio>().is_err());
    }

    #[test]
    fn test_mode_from_str() {
        assert_eq!("fast".parse::<Mode>().unwrap(), Mode::Fast);
        assert_eq!("std".parse::<Mode>().unwrap(), Mode::Std);
        assert_eq!("pro".parse::<Mode>().unwrap(), Mode::Pro);
        assert!("invalid".parse::<Mode>().is_err());
    }

    #[test]
    fn test_task_status_display() {
        assert_eq!(TaskStatus::Success.to_string(), "success");
        assert_eq!(TaskStatus::Running.to_string(), "running");
    }

    #[test]
    fn test_task_status_helpers() {
        assert!(TaskStatus::Running.is_in_progress());
        assert!(!TaskStatus::Success.is_in_progress());
        assert!(TaskStatus::Success.is_completed());
        assert!(!TaskStatus::Running.is_completed());
    }
}
