use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::{
    REQWEST_CLIENT, library::track::lyric_data::PlainLyrics, lyrics::synced_lyrics::SyncedLyrics,
};

const LRCLIB_SEARCH_ORIGIN: &str = "https://lrclib.net/api/search";

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchRecord {
    pub id: u64,
    pub name: String,
    pub track_name: String,
    pub artist_name: String,
    pub album_name: String,
    pub duration: Option<f64>,
    pub instrumental: bool,
    pub plain_lyrics: Option<String>,
    pub synced_lyrics: Option<String>,
}

#[derive(Serialize)]
pub struct SearchQuery {
    #[serde(rename = "track_name")]
    track_title: String,

    #[serde(rename = "artist_name")]
    main_artist: String,

    #[serde(rename = "album_name", skip_serializing_if = "Option::is_none")]
    album_title: Option<String>,
}

impl SearchQuery {
    pub fn new(track_title: impl Into<String>, main_artist: impl Into<String>) -> Self {
        Self {
            track_title: track_title.into(),
            main_artist: main_artist.into(),
            album_title: None,
        }
    }

    pub fn album_title(mut self, title: Option<impl Into<String>>) -> Self {
        self.album_title = title.map(std::convert::Into::into);
        self
    }

    pub async fn run(self) -> Result<Vec<SearchRecord>, reqwest::Error> {
        let response = REQWEST_CLIENT
            .get(LRCLIB_SEARCH_ORIGIN)
            .query(&self)
            .send()
            .await?;

        response.json().await
    }
}

impl SearchRecord {
    /// Returns [`true`] if the record is instrumental
    #[must_use]
    pub fn is_instrumental(&self) -> bool {
        self.instrumental
    }

    /// Returns the [`PlainLyrics`] of a song, if any
    #[must_use]
    pub fn plain_lyrics(&self) -> Option<PlainLyrics> {
        self.plain_lyrics
            .as_deref()
            .map(|lyrics| PlainLyrics::from_str(lyrics).unwrap())
    }

    /// Returns the [`SyncedLyrics`] of a song, if any
    #[must_use]
    pub fn synced_lyrics(&self) -> Option<SyncedLyrics> {
        self.synced_lyrics
            .as_deref()
            .and_then(|lyrics| SyncedLyrics::from_synced_lyrics(lyrics).ok())
    }
}
