use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Status of a generation request or clip
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum GenerationStatus {
    #[serde(rename = "pending")]
    Pending,
    #[serde(rename = "processing")]
    Processing,
    #[serde(rename = "complete")]
    Complete,
    #[serde(rename = "error")]
    Error,
}

impl std::fmt::Display for GenerationStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GenerationStatus::Pending => write!(f, "pending"),
            GenerationStatus::Processing => write!(f, "processing"),
            GenerationStatus::Complete => write!(f, "complete"),
            GenerationStatus::Error => write!(f, "error"),
        }
    }
}

/// Status of a generated clip
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ClipStatus {
    #[serde(rename = "submitted")]
    Submitted,
    #[serde(rename = "pending")]
    Pending,
    #[serde(rename = "complete")]
    Complete,
    #[serde(rename = "error")]
    Error,
}

impl std::fmt::Display for ClipStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ClipStatus::Submitted => write!(f, "submitted"),
            ClipStatus::Pending => write!(f, "pending"),
            ClipStatus::Complete => write!(f, "complete"),
            ClipStatus::Error => write!(f, "error"),
        }
    }
}

/// Generation task type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum GenerationTask {
    Generate,
    Continue,
    Infill,
    InfillCover,
    InfillArtist,
    InfillIntro,
    InfillOutro,
    Concat,
    Merge,
    Upsample,
    Lyrics,
    #[serde(rename = "lyrics_infill")]
    LyricsInfill,
    Remix,
}

impl std::fmt::Display for GenerationTask {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GenerationTask::Generate => write!(f, "generate"),
            GenerationTask::Continue => write!(f, "continue"),
            GenerationTask::Infill => write!(f, "infill"),
            GenerationTask::InfillCover => write!(f, "infill_cover"),
            GenerationTask::InfillArtist => write!(f, "infill_artist"),
            GenerationTask::InfillIntro => write!(f, "infill_intro"),
            GenerationTask::InfillOutro => write!(f, "infill_outro"),
            GenerationTask::Concat => write!(f, "concat"),
            GenerationTask::Merge => write!(f, "merge"),
            GenerationTask::Upsample => write!(f, "upsample"),
            GenerationTask::Lyrics => write!(f, "lyrics"),
            GenerationTask::LyricsInfill => write!(f, "lyrics_infill"),
            GenerationTask::Remix => write!(f, "remix"),
        }
    }
}

/// Generation request parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateParams {
    pub prompt: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tags: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub negative_tags: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub continue_clip_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub infill_start_s: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub infill_end_s: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub make_instrumental: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub wait_audio: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub task: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, serde_json::Value>,
}

impl GenerateParams {
    pub fn new(prompt: impl Into<String>) -> Self {
        Self {
            prompt: prompt.into(),
            title: None,
            tags: None,
            negative_tags: None,
            continue_clip_id: None,
            infill_start_s: None,
            infill_end_s: None,
            model: None,
            make_instrumental: None,
            wait_audio: None,
            task: None,
            extra: HashMap::new(),
        }
    }

    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = Some(title.into());
        self
    }

    pub fn with_tags(mut self, tags: impl Into<String>) -> Self {
        self.tags = Some(tags.into());
        self
    }

    pub fn with_model(mut self, model: impl Into<String>) -> Self {
        self.model = Some(model.into());
        self
    }

    pub fn with_instrumental(mut self, make_instrumental: bool) -> Self {
        self.make_instrumental = Some(make_instrumental);
        self
    }

    pub fn with_continue(mut self, clip_id: impl Into<String>) -> Self {
        self.continue_clip_id = Some(clip_id.into());
        self
    }

    pub fn with_task(mut self, task: impl Into<String>) -> Self {
        self.task = Some(task.into());
        self
    }

    pub fn with_infill(mut self, start_s: f32, end_s: f32) -> Self {
        self.infill_start_s = Some(start_s);
        self.infill_end_s = Some(end_s);
        self
    }
}

/// Metadata for generation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationMetadata {
    pub prompt: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tags: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error_message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub credit_cost: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub task: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, serde_json::Value>,
}

/// Generated clip from the API
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneratedClip {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user_id: Option<String>,
    pub status: ClipStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub audio_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub s3_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_s3_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<GenerationMetadata>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub batch_index: Option<i32>,
}

impl GeneratedClip {
    pub fn is_complete(&self) -> bool {
        self.status == ClipStatus::Complete && self.audio_url.is_some()
    }

    pub fn is_error(&self) -> bool {
        self.status == ClipStatus::Error
    }

    pub fn error_message(&self) -> Option<&str> {
        self.metadata
            .as_ref()
            .and_then(|m| m.error_message.as_ref().map(|s| s.as_str()))
    }
}

/// Response from generation endpoint
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationResponse {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request_id: Option<String>,
    pub status: GenerationStatus,
    pub clips: Vec<GeneratedClip>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub batch_size: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<String>,
}

/// Status returned from polling a clip
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PollingStatus {
    pub clip_id: String,
    pub status: ClipStatus,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub audio_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

/// Continue generation parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContinueGenerationParams {
    pub continue_clip_id: String,
    pub prompt: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tags: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, serde_json::Value>,
}

impl ContinueGenerationParams {
    pub fn new(clip_id: impl Into<String>, prompt: impl Into<String>) -> Self {
        Self {
            continue_clip_id: clip_id.into(),
            prompt: prompt.into(),
            title: None,
            tags: None,
            extra: HashMap::new(),
        }
    }
}

/// Infill generation parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfillGenerationParams {
    pub clip_id: String,
    pub start_s: f32,
    pub end_s: f32,
    pub prompt: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, serde_json::Value>,
}

impl InfillGenerationParams {
    pub fn new(clip_id: impl Into<String>, start_s: f32, end_s: f32, prompt: impl Into<String>) -> Self {
        Self {
            clip_id: clip_id.into(),
            start_s,
            end_s,
            prompt: prompt.into(),
            title: None,
            extra: HashMap::new(),
        }
    }
}

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

    #[test]
    fn test_generation_status_display() {
        assert_eq!(GenerationStatus::Pending.to_string(), "pending");
        assert_eq!(GenerationStatus::Complete.to_string(), "complete");
        assert_eq!(GenerationStatus::Error.to_string(), "error");
    }

    #[test]
    fn test_clip_status_display() {
        assert_eq!(ClipStatus::Submitted.to_string(), "submitted");
        assert_eq!(ClipStatus::Pending.to_string(), "pending");
        assert_eq!(ClipStatus::Complete.to_string(), "complete");
        assert_eq!(ClipStatus::Error.to_string(), "error");
    }

    #[test]
    fn test_generate_params_builder() {
        let params = GenerateParams::new("test prompt")
            .with_title("Test Title")
            .with_tags("tag1, tag2")
            .with_instrumental(true);

        assert_eq!(params.prompt, "test prompt");
        assert_eq!(params.title, Some("Test Title".to_string()));
        assert_eq!(params.tags, Some("tag1, tag2".to_string()));
        assert_eq!(params.make_instrumental, Some(true));
    }

    #[test]
    fn test_generated_clip_is_complete() {
        let clip = GeneratedClip {
            id: "test-id".to_string(),
            request_id: None,
            user_id: None,
            status: ClipStatus::Complete,
            audio_url: Some("https://example.com/audio.mp3".to_string()),
            image_url: None,
            s3_id: None,
            image_s3_id: None,
            title: None,
            metadata: None,
            created_at: None,
            model_name: None,
            batch_index: None,
        };

        assert!(clip.is_complete());
    }

    #[test]
    fn test_generated_clip_is_error() {
        let clip = GeneratedClip {
            id: "test-id".to_string(),
            request_id: None,
            user_id: None,
            status: ClipStatus::Error,
            audio_url: None,
            image_url: None,
            s3_id: None,
            image_s3_id: None,
            title: None,
            metadata: None,
            created_at: None,
            model_name: None,
            batch_index: None,
        };

        assert!(clip.is_error());
    }

    #[test]
    fn test_serialization_skip_none_fields() {
        let params = GenerateParams::new("test");
        let json = serde_json::to_value(&params).unwrap();

        // title should not appear in JSON
        assert!(json.get("title").is_none());
    }
}
