use super::Message;
use super::library::ShowLibraryItem;
use crate::database::Database;
use crate::tmdb::TmdbClient;
use crate::ui::shared::tmdb::TmdbResultDisplay;
use iced::{Element, Task};
use std::path::PathBuf;

#[derive(Debug, Clone)]
pub(crate) struct ShowTmdbSearchResultItem {
    pub(crate) tmdb_id: u32,
    pub(crate) title: String,
    #[allow(dead_code)]
    pub(crate) original_name: String,
    pub(crate) first_air_date: String,
    pub(crate) poster_image: Option<Vec<u8>>,
}

impl TmdbResultDisplay for ShowTmdbSearchResultItem {
    fn title(&self) -> &str {
        &self.title
    }
    fn year_str(&self) -> &str {
        if self.first_air_date.len() >= 4 {
            &self.first_air_date[..4]
        } else {
            &self.first_air_date
        }
    }
    fn poster_image(&self) -> Option<&[u8]> {
        self.poster_image.as_deref()
    }
}

pub(crate) struct ShowTmdbMatcherState {
    pub(crate) show_index: usize,
    pub(crate) search_query: String,
    pub(crate) search_results: Vec<ShowTmdbSearchResultItem>,
    pub(crate) searching: bool,
    pub(crate) matching: bool,
    pub(crate) search_expanded: bool,
    pub(crate) error: Option<String>,
}

impl ShowTmdbMatcherState {
    pub(crate) fn new(show_index: usize, title: &str, has_tmdb_id: bool) -> Self {
        Self {
            show_index,
            search_query: title.to_string(),
            search_results: Vec::new(),
            searching: false,
            matching: false,
            search_expanded: !has_tmdb_id,
            error: None,
        }
    }
}

// Data returned from the async match task
#[derive(Debug, Clone)]
pub(crate) struct ShowMatchData {
    pub(crate) tmdb_id: u32,
    pub(crate) original_name: String,
    pub(crate) release_year: Option<i64>,
    pub(crate) description: Option<String>,
    pub(crate) rating: Option<f64>,
    pub(crate) poster: Option<(String, Vec<u8>)>,
    pub(crate) backdrop: Option<(String, Vec<u8>)>,
    pub(crate) seasons: Vec<SeasonMatchData>,
}

#[derive(Debug, Clone)]
pub(crate) struct SeasonMatchData {
    pub(crate) season_number: i64,
    pub(crate) tmdb_id: u32,
    pub(crate) name: String,
    pub(crate) release_year: Option<i64>,
    pub(crate) description: Option<String>,
    pub(crate) rating: Option<f64>,
    pub(crate) poster: Option<(String, Vec<u8>)>,
    pub(crate) episodes: Vec<EpisodeMatchData>,
}

#[derive(Debug, Clone)]
pub(crate) struct EpisodeMatchData {
    pub(crate) episode_number: i64,
    pub(crate) tmdb_id: u32,
    pub(crate) name: String,
}

pub(crate) fn update(
    state: &mut ShowTmdbMatcherState,
    msg: Message,
    tmdb_api_token: &Option<String>,
) -> Task<crate::ui::Message> {
    match msg {
        Message::TmdbSearchQueryChanged(text) => {
            state.search_query = text;
            Task::none()
        }
        Message::TmdbToggleSearch => {
            state.search_expanded = !state.search_expanded;
            Task::none()
        }
        Message::TmdbSearchSubmit => {
            if state.search_query.trim().is_empty() {
                return Task::none();
            }
            state.searching = true;
            state.search_results.clear();
            state.error = None;

            let query = state.search_query.clone();
            let token = tmdb_api_token.clone().unwrap_or_default();

            Task::perform(
                async move {
                    let mut client = TmdbClient::new(token);
                    let results = client.search_tv(&query, None).map_err(|e| e.to_string())?;

                    let img_config = client.image_configuration().map_err(|e| e.to_string())?;

                    let items: Vec<ShowTmdbSearchResultItem> = results
                        .into_iter()
                        .map(|r| {
                            let poster_image = r
                                .get_poster_url(&img_config)
                                .and_then(|url| client.download_image(&url).ok())
                                .filter(|bytes| !bytes.is_empty());
                            ShowTmdbSearchResultItem {
                                tmdb_id: r.id,
                                title: r.name,
                                original_name: r.original_name,
                                first_air_date: r.first_air_date.unwrap_or_default(),
                                poster_image,
                            }
                        })
                        .collect();

                    Ok(items)
                },
                |result| crate::ui::Message::Show(Message::TmdbSearchCompleted(result)),
            )
        }
        Message::TmdbSearchCompleted(result) => {
            state.searching = false;
            match result {
                Ok(results) => state.search_results = results,
                Err(e) => state.error = Some(e),
            }
            Task::none()
        }
        Message::TmdbSelectMatch(idx) => {
            let result = state.search_results[idx].clone();
            state.matching = true;
            state.error = None;

            let token = tmdb_api_token.clone().unwrap_or_default();
            let series_id = result.tmdb_id;

            Task::perform(
                async move {
                    let mut client = TmdbClient::new(token);
                    let details = client.tv_details(series_id).map_err(|e| e.to_string())?;

                    let img_config = client.image_configuration().map_err(|e| e.to_string())?;

                    let poster = details.get_poster_url(&img_config).and_then(|url| {
                        let bytes = client.download_image(&url).ok()?;
                        if bytes.is_empty() {
                            return None;
                        }
                        let filename = details
                            .poster_path
                            .as_deref()
                            .unwrap_or("poster.jpg")
                            .to_string();
                        Some((filename, bytes))
                    });

                    let backdrop = details.get_backdrop_url(&img_config).and_then(|url| {
                        let bytes = client.download_image(&url).ok()?;
                        if bytes.is_empty() {
                            return None;
                        }
                        let filename = details
                            .backdrop_path
                            .as_deref()
                            .unwrap_or("backdrop.jpg")
                            .to_string();
                        Some((filename, bytes))
                    });

                    let mut seasons = Vec::new();
                    for tmdb_season in &details.seasons {
                        let season_poster =
                            tmdb_season.get_poster_url(&img_config).and_then(|url| {
                                let bytes = client.download_image(&url).ok()?;
                                if bytes.is_empty() {
                                    return None;
                                }
                                let filename = tmdb_season
                                    .poster_path
                                    .as_deref()
                                    .unwrap_or("poster.jpg")
                                    .to_string();
                                Some((filename, bytes))
                            });

                        let season_details = client
                            .season_details(series_id, tmdb_season.season_number)
                            .map_err(|e| e.to_string())?;

                        let episodes: Vec<EpisodeMatchData> = season_details
                            .episodes
                            .iter()
                            .map(|ep| EpisodeMatchData {
                                episode_number: ep.episode_number as i64,
                                tmdb_id: ep.id,
                                name: ep.name.clone(),
                            })
                            .collect();

                        seasons.push(SeasonMatchData {
                            season_number: tmdb_season.season_number as i64,
                            tmdb_id: tmdb_season.id,
                            name: tmdb_season.name.clone(),
                            release_year: tmdb_season.release_year().map(|y| y as i64),
                            description: tmdb_season.overview.clone(),
                            rating: tmdb_season.vote_average,
                            poster: season_poster,
                            episodes,
                        });
                    }

                    let release_year = details.release_year().map(|y| y as i64);
                    Ok(ShowMatchData {
                        tmdb_id: details.id,
                        original_name: details.original_name,
                        release_year,
                        description: details.overview,
                        rating: details.vote_average,
                        poster,
                        backdrop,
                        seasons,
                    })
                },
                |result| crate::ui::Message::Show(Message::TmdbMatchCompleted(result)),
            )
        }
        _ => Task::none(),
    }
}

/// Apply match data to database. Called from mod.rs on ShowTmdbMatchCompleted(Ok(data)).
pub(crate) fn apply_match(
    data: &ShowMatchData,
    show_id: i64,
    db: &mut Database,
    show_item: &mut ShowLibraryItem,
) {
    // Update show metadata
    if let Err(e) = db.update_show_tmdb(
        show_id,
        data.tmdb_id as i64,
        &data.original_name,
        data.release_year,
        data.description.as_deref(),
        data.rating,
    ) {
        log::error!("failed to update show tmdb: {e}");
        return;
    }

    show_item.tmdb_id = Some(data.tmdb_id as i64);
    show_item.title = data.original_name.clone();

    // Season metadata and episodes
    let db_seasons = db.find_seasons_by_show(show_id).unwrap_or_default();
    let mut seasons_data: Vec<(i64, Option<(String, Vec<u8>)>)> = Vec::new();

    for db_season in &db_seasons {
        let tmdb_season = data
            .seasons
            .iter()
            .find(|s| s.season_number == db_season.season_number);

        let tmdb_season = match tmdb_season {
            Some(s) => s,
            None => {
                seasons_data.push((db_season.id, None));
                continue;
            }
        };

        if let Err(e) = db.update_season_tmdb(
            db_season.id,
            tmdb_season.tmdb_id as i64,
            &tmdb_season.name,
            tmdb_season.release_year,
            tmdb_season.description.as_deref(),
            tmdb_season.rating,
        ) {
            log::error!("failed to update season tmdb: {e}");
            seasons_data.push((db_season.id, None));
            continue;
        }

        seasons_data.push((db_season.id, tmdb_season.poster.clone()));

        let db_episodes = db.find_episodes_by_season(db_season.id).unwrap_or_default();
        for db_episode in &db_episodes {
            let tmdb_episode = tmdb_season
                .episodes
                .iter()
                .find(|e| e.episode_number == db_episode.episode_number);

            if let Some(ep) = tmdb_episode {
                if let Err(e) = db.update_episode_tmdb(ep.tmdb_id as i64, &ep.name, db_episode.id) {
                    log::error!("failed to update episode tmdb: {e}");
                }
            }
        }
    }

    let cache_dir = dirs::cache_dir()
        .unwrap_or_else(|| PathBuf::from(".cache"))
        .join("myplayer")
        .join("images");

    match crate::image_store::store_show_images(
        db,
        show_id,
        data.poster.clone(),
        data.backdrop.clone(),
        &seasons_data,
        &cache_dir,
    ) {
        Ok((cover_path, backdrop_path)) => {
            if let Some(path) = cover_path {
                show_item.cover_path = Some(path);
            }
            if let Some(path) = backdrop_path {
                show_item.backdrop_path = Some(path);
            }
        }
        Err(e) => log::error!("failed to store show images: {e}"),
    }
}

pub(crate) fn view<'a>(state: &'a ShowTmdbMatcherState) -> Element<'a, crate::ui::Message> {
    crate::ui::shared::tmdb::tmdb_search_section(
        state.search_expanded,
        state.searching,
        state.matching,
        "Matching... Fetching show details, seasons, and episodes.",
        &state.search_query,
        "Search TV show...",
        &state.search_results,
        &state.error,
        crate::ui::Message::Show(Message::TmdbToggleSearch),
        |s| crate::ui::Message::Show(Message::TmdbSearchQueryChanged(s)),
        crate::ui::Message::Show(Message::TmdbSearchSubmit),
        |i| crate::ui::Message::Show(Message::TmdbSelectMatch(i)),
    )
}
