use crate::{
    lyrics_api::{Response, lrclib::fetch_lrclib, response::ResponseError},
    metadata::TrackMetadata,
};
use log::warn;
use reqwest::blocking::Client;
use scraper::{Html, Selector};
use serde::Deserialize;

// Inspired by https://github.com/moehmeni/syncedlyrics/blob/main/syncedlyrics/providers/genius.py

#[derive(Debug, Deserialize)]
struct GeniusApiResponse {
    response: GeniusResponseBody,
}

#[derive(Debug, Deserialize)]
struct GeniusResponseBody {
    sections: Vec<GeniusSection>,
}

#[derive(Debug, Deserialize)]
struct GeniusSection {
    hits: Vec<GeniusHit>,
}

#[derive(Debug, Deserialize)]
struct GeniusHit {
    result: GeniusResult,
}

#[derive(Debug, Deserialize)]
struct GeniusResult {
    url: String,
}

const SEARCH_ENDPOINT: &str = "https://genius.com/api/search/multi";

pub fn fetch_genius(metadata: &TrackMetadata) -> Result<Response, ResponseError> {
    let search_term = build_search_term(metadata);

    let client = Client::builder()
        .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36")
        .build()
        .map_err(|e| ResponseError::new(format!("Failed to build HTTP client: {e}")))?;

    let response = client
        .get(SEARCH_ENDPOINT)
        .query(&[("per_page", "5"), ("q", &search_term)])
        .header("Cookie", "obuid=e3ee67e0-7df9-4181-8324-d977c6dc9250")
        .send()
        .map_err(|e| ResponseError::new(format!("Search request error: {e}")))?;

    if !response.status().is_success() {
        return match response.status().as_u16() {
            404 => {
                warn!(
                    "No plain lyrics found on the genius.com, switch to LRCLIB for {}",
                    metadata.path().display()
                );
                fetch_lrclib(metadata, true)
            }
            code => Err(ResponseError::new(format!(
                "Unexpected status code: {code}"
            ))),
        };
    }

    let api_resp: GeniusApiResponse = response
        .json()
        .map_err(|e| ResponseError::new(format!("Failed to parse search JSON: {e}")))?;

    let song_url = api_resp
        .response
        .sections
        .get(1)
        .and_then(|sec| sec.hits.first())
        .map(|hit| hit.result.url.clone())
        .ok_or_else(|| ResponseError::new("No search results found".to_string()))?;

    let page_resp = client
        .get(&song_url)
        .send()
        .map_err(|e| ResponseError::new(format!("Page request error: {e}")))?;

    if !page_resp.status().is_success() {
        warn!(
            "No plain lyrics found on the genius.com, switch to LRCLIB for {}",
            metadata.path().display()
        );
        return fetch_lrclib(metadata, true);
    }

    let html = page_resp
        .text()
        .map_err(|e| ResponseError::new(format!("Failed to read page body: {e}")))?;

    let lyrics = scrape_lyrics(&html);

    if lyrics.is_empty() {
        warn!(
            "No plain lyrics found on the genius.com, switch to LRCLIB for {}",
            metadata.path().display()
        );
        fetch_lrclib(metadata, true)
    } else {
        Ok(Response::UnsyncedLyrics(lyrics))
    }
}

fn build_search_term(metadata: &TrackMetadata) -> String {
    let mut term = metadata.title().to_string();
    if let Some(artist) = metadata.artist() {
        term = format!("{artist} {term}");
    }
    term
}

fn scrape_lyrics(html: &str) -> String {
    let document = Html::parse_document(html);
    let selector = Selector::parse("div[data-lyrics-container]").expect("invalid selector");

    let mut lyrics = String::new();

    for element in document.select(&selector) {
        let text = element
            .text()
            .map(|t| t.trim())
            .filter(|t| !t.is_empty())
            .collect::<Vec<_>>()
            .join("\n");

        if !lyrics.is_empty() && !text.is_empty() {
            lyrics.push('\n');
        }
        lyrics.push_str(&text);
    }

    lyrics = lyrics.replace("\n[", "\n\n[");

    lyrics
}
