use anyhow::{Result, bail};
use async_trait::async_trait;
use mediarium_media_parser::MediaType;
use reqwest::Client;
use serde::Deserialize;
use tracing::debug;

use super::super::provider::MetadataProvider;
use super::super::types::MediaMetadata;
use crate::types::{Provider, ProviderStatus};

const TMDB_API_BASE: &str = "https://api.themoviedb.org/3";
const TMDB_IMAGE_BASE: &str = "https://image.tmdb.org/t/p/w500";

pub struct TmdbProvider {
    client: Client,
    api_key: String,
    enabled: bool,
}

impl TmdbProvider {
    #[must_use]
    pub fn new(api_key: String) -> Self {
        let enabled = !api_key.is_empty();
        Self {
            client: Client::new(),
            api_key,
            enabled,
        }
    }
}

#[async_trait]
impl Provider for TmdbProvider {
    fn key(&self) -> &'static str {
        "tmdb"
    }

    fn name(&self) -> &'static str {
        "TMDB"
    }

    async fn health_check(&self) -> ProviderStatus {
        if !self.enabled {
            return ProviderStatus::Unhealthy;
        }

        let start = std::time::Instant::now();
        let result = self
            .client
            .get(format!("{TMDB_API_BASE}/configuration"))
            .bearer_auth(&self.api_key)
            .send()
            .await;

        match result {
            Ok(response) if response.status().is_success() => ProviderStatus::Healthy {
                latency_ms: start.elapsed().as_millis() as u64,
            },
            _ => ProviderStatus::Unhealthy,
        }
    }
}

#[derive(Debug, Deserialize)]
struct TmdbSearchResponse {
    results: Vec<TmdbResult>,
}

#[derive(Debug, Deserialize)]
struct TmdbResult {
    title: Option<String>,
    name: Option<String>,
    overview: Option<String>,
    poster_path: Option<String>,
    backdrop_path: Option<String>,
    release_date: Option<String>,
    first_air_date: Option<String>,
    vote_average: Option<f64>,
    vote_count: Option<u64>,
    media_type: Option<String>,
}

#[async_trait]
impl MetadataProvider for TmdbProvider {
    fn is_enabled(&self) -> bool {
        self.enabled
    }

    async fn search(&self, query: &str) -> Result<MediaMetadata> {
        debug!("TMDB search: {query}");

        let response = self
            .client
            .get(format!("{TMDB_API_BASE}/search/multi"))
            .bearer_auth(&self.api_key)
            .query(&[("query", query), ("language", "zh-CN")])
            .send()
            .await?;

        if !response.status().is_success() {
            bail!("TMDB API error: {}", response.status());
        }

        let search: TmdbSearchResponse = response.json().await?;
        let result = search.results.into_iter().next();

        let Some(result) = result else {
            bail!("No TMDB results for: {query}");
        };

        let title = result.title.or(result.name).unwrap_or_default();
        let year = result
            .release_date
            .as_deref()
            .or(result.first_air_date.as_deref())
            .and_then(|date| date.split('-').next())
            .and_then(|year| year.parse::<i32>().ok());
        let media_type = if result.media_type.as_deref() == Some("tv") {
            MediaType::Series
        } else {
            MediaType::Movie
        };

        Ok(MediaMetadata {
            title,
            year,
            overview: result.overview,
            poster_url: result
                .poster_path
                .map(|path| format!("{TMDB_IMAGE_BASE}{path}")),
            backdrop_url: result
                .backdrop_path
                .map(|path| format!("{TMDB_IMAGE_BASE}{path}")),
            rating: result.vote_average,
            vote_count: result.vote_count,
            media_type,
            source: "tmdb".to_string(),
        })
    }
}
