// 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::text2video::Text2Video;
use crate::image2video::Image2Video;
use crate::omni::Omni;
use std::sync::Arc;
use tokio::sync::RwLock;

/// SkyReels API client
///
/// This is the main entry point for interacting with the SkyReels API.
///
/// # Example
///
/// ```rust,no_run
/// use skyreels::SkyReelsClient;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     // Create a client with your API key
///     let client = SkyReelsClient::new("your_api_key");
///
///     // Use the text-to-video API
///     let request = client.text2video()
///         .submit_request("A beautiful sunset over the ocean")
///         .duration(5)
///         .build();
///
///     let task_id = client.text2video().submit(request).await?;
///
///     println!("Task submitted: {}", task_id);
///
///     Ok(())
/// }
/// ```
#[derive(Clone)]
pub struct SkyReelsClient {
    api_key: Arc<RwLock<String>>,
    base_url: Arc<RwLock<String>>,
    http_client: reqwest::Client,
}

impl SkyReelsClient {
    /// Create a new SkyReels client with the default API base URL
    ///
    /// # Arguments
    ///
    /// * `api_key` - Your SkyReels API key
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use skyreels::SkyReelsClient;
    ///
    /// let client = SkyReelsClient::new("your_api_key");
    /// ```
    pub fn new(api_key: impl Into<String>) -> Self {
        Self::with_base_url(api_key, crate::DEFAULT_BASE_URL)
    }

    /// Create a new SkyReels client with a custom base URL
    ///
    /// # Arguments
    ///
    /// * `api_key` - Your SkyReels API key
    /// * `base_url` - Custom API base URL (e.g., for testing or proxy)
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use skyreels::SkyReelsClient;
    ///
    /// let client = SkyReelsClient::with_base_url(
    ///     "your_api_key",
    ///     "https://custom.api.example.com"
    /// );
    /// ```
    pub fn with_base_url(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
        Self {
            api_key: Arc::new(RwLock::new(api_key.into())),
            base_url: Arc::new(RwLock::new(base_url.into())),
            http_client: reqwest::Client::builder()
                .build()
                .expect("Failed to create HTTP client"),
        }
    }

    /// Get the current API key
    pub async fn api_key(&self) -> String {
        self.api_key.read().await.clone()
    }

    /// Get a synchronous reference to the API key (for internal use)
    pub(crate) fn api_key_sync(&self) -> String {
        // This is a blocking read for use in synchronous contexts
        // In async contexts, use the async api_key() method
        tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(async {
                self.api_key.read().await.clone()
            })
        })
    }

    /// Update the API key
    pub async fn set_api_key(&self, new_key: impl Into<String>) {
        let mut key = self.api_key.write().await;
        *key = new_key.into();
    }

    /// Get the current base URL
    pub async fn base_url(&self) -> String {
        self.base_url.read().await.clone()
    }

    /// Get a synchronous reference to the base URL (for internal use)
    pub(crate) fn base_url_sync(&self) -> String {
        tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(async {
                self.base_url.read().await.clone()
            })
        })
    }

    /// Update the base URL
    pub async fn set_base_url(&self, new_url: impl Into<String>) {
        let mut url = self.base_url.write().await;
        *url = new_url.into();
    }

    /// Get the underlying HTTP client
    pub(crate) fn http_client(&self) -> &reqwest::Client {
        &self.http_client
    }

    /// Access the text-to-video API
    ///
    /// # 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");
    ///
    /// // Submit a task
    /// let request = client.text2video()
    ///     .submit_request("A beautiful sunset")
    ///     .build();
    ///
    /// let task_id = client.text2video().submit(request).await?;
    ///
    /// // Query task status
    /// let status = client.text2video()
    ///     .get_task(&task_id)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn text2video(&self) -> Text2Video<'_> {
        Text2Video::new(self)
    }

    /// Access the image-to-video API
    ///
    /// # 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");
    ///
    /// // Submit a task
    /// let request = client.image2video()
    ///     .submit_request("Camera zooms in", "https://example.com/image.jpg")
    ///     .build();
    ///
    /// let task_id = client.image2video().submit(request).await?;
    ///
    /// // Query task status
    /// let status = client.image2video()
    ///     .get_task(&task_id)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn image2video(&self) -> Image2Video<'_> {
        Image2Video::new(self)
    }

    /// Access the omni video API
    ///
    /// The omni API provides comprehensive video generation capabilities with support for:
    /// - Keyframe video generation
    /// - Subject reference
    /// - Video motion reference
    /// - Video editing (subject/background replacement, object removal/insertion, etc.)
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// # use skyreels::{SkyReelsClient, ReferenceImageType, ReferenceVideoType, Mode};
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let client = SkyReelsClient::new("your_api_key");
    ///
    /// // Submit a keyframe video generation task
    /// let request = client.omni()
    ///     .submit_request("The King @image1 searches for a destination @image2")
    ///     .add_reference_image(
    ///         "@image1",
    ///         ReferenceImageType::Keyframe,
    ///         vec!["https://example.com/frame1.jpg".to_string()],
    ///         Some(0)
    ///     )
    ///     .add_reference_image(
    ///         "@image2",
    ///         ReferenceImageType::Keyframe,
    ///         vec!["https://example.com/frame2.jpg".to_string()],
    ///         Some(3)
    ///     )
    ///     .duration(5)
    ///     .build();
    ///
    /// let task_id = client.omni().submit(request).await?;
    ///
    /// // Query task status
    /// let status = client.omni()
    ///     .get_task(&task_id)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn omni(&self) -> Omni<'_> {
        Omni::new(self)
    }

    /// Get SDK version
    pub fn version(&self) -> &'static str {
        crate::VERSION
    }
}

impl fmt::Debug for SkyReelsClient {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("SkyReelsClient")
            .field("api_key", &"<REDACTED>")
            .field("base_url", &"<BASE_URL>")
            .field("version", &self.version())
            .finish()
    }
}

use std::fmt;

/// Extension trait to convert Text2VideoRequest to actual submission
pub trait Text2VideoRequestExt {
    /// Submit the request using the client
    fn submit_with_client(&self, client: &SkyReelsClient) -> impl std::future::Future<Output = crate::Result<String>> + Send;
}

impl Text2VideoRequestExt for crate::text2video::Text2VideoRequest {
    fn submit_with_client(&self, client: &SkyReelsClient) -> impl std::future::Future<Output = crate::Result<String>> + Send {
        let url = format!("{}/video/text2video/submit", client.base_url_sync());

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

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

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

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

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

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

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

            Ok(submit_response.task_id)
        }
    }
}

/// Builder implementation for Text2VideoRequest
pub struct Text2VideoBuilder<'a> {
    client: &'a SkyReelsClient,
    request: crate::text2video::Text2VideoRequest,
}

impl<'a> Text2VideoBuilder<'a> {
    pub(crate) fn new(client: &'a SkyReelsClient, prompt: String) -> Self {
        let api_key = client.api_key_sync();
        Self {
            client,
            request: crate::text2video::Text2VideoRequest::new(api_key, prompt),
        }
    }

    /// Set the video duration (3-15 seconds)
    pub fn duration(mut self, duration: i32) -> Self {
        self.request = self.request.duration(duration);
        self
    }

    /// Set the aspect ratio
    pub fn aspect_ratio(mut self, ratio: crate::text2video::AspectRatio) -> Self {
        self.request = self.request.aspect_ratio(ratio);
        self
    }

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

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

    /// Submit the request
    pub async fn submit(self) -> crate::Result<String> {
        self.request.submit_with_client(self.client).await
    }
}

impl Text2Video<'_> {
    /// Create a new builder for text-to-video submission
    pub fn create_builder(&self, prompt: impl Into<String>) -> Text2VideoBuilder<'_> {
        Text2VideoBuilder::new(self.client, prompt.into())
    }
}

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

    #[test]
    fn test_client_creation() {
        let client = SkyReelsClient::new("test_key");
        assert_eq!(client.version(), crate::VERSION);
    }

    #[test]
    fn test_client_with_custom_base_url() {
        let client = SkyReelsClient::with_base_url("test_key", "https://custom.api.com");
        assert_eq!(client.version(), crate::VERSION);
    }

    #[tokio::test]
    async fn test_api_key_update() {
        let client = SkyReelsClient::new("initial_key");
        assert_eq!(client.api_key().await, "initial_key");

        client.set_api_key("new_key").await;
        assert_eq!(client.api_key().await, "new_key");
    }

    #[tokio::test]
    async fn test_base_url_update() {
        let client = SkyReelsClient::new("test_key");
        assert_eq!(client.base_url().await, crate::DEFAULT_BASE_URL);

        client.set_base_url("https://custom.api.com").await;
        assert_eq!(client.base_url().await, "https://custom.api.com");
    }
}
