use anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

const DEFAULT_API_URL: &str = "http://localhost:3002";

const DEV_API_URL: &str = "http://localhost:3003";

fn get_api_url() -> String {
    std::env::var("MOMENTRY_API_URL").unwrap_or_else(|_| {
        std::env::var("MOMENTRY_SERVER_PORT")
            .ok()
            .map(|port| format!("http://localhost:{}", port))
            .unwrap_or_else(|| DEFAULT_API_URL.to_string())
    })
}

fn get_api_key() -> Option<String> {
    std::env::var("MOMENTRY_API_KEY").ok()
}

#[derive(Debug, Clone)]
pub struct ApiClient {
    client: Client,
    base_url: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RegisterRequest {
    pub path: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RegisterResponse {
    pub uuid: String,
    pub video_id: i64,
    pub job_id: i64,
    pub file_name: String,
    pub duration: f64,
    pub width: u32,
    pub height: u32,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SearchRequest {
    pub query: String,
    pub limit: Option<usize>,
    pub uuid: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SearchResult {
    pub uuid: String,
    pub chunk_id: String,
    pub chunk_type: String,
    pub start_time: f64,
    pub end_time: f64,
    pub text: String,
    pub score: f32,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SearchResponse {
    pub results: Vec<SearchResult>,
    pub query: String,
}

#[derive(Debug, Deserialize, Serialize)]
#[allow(dead_code)]
pub struct LookupQuery {
    pub path: Option<String>,
    pub uuid: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct LookupResponse {
    pub uuid: String,
    pub file_path: Option<String>,
    pub file_name: Option<String>,
    pub duration: Option<f64>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct VideoInfo {
    pub uuid: String,
    pub file_path: String,
    pub file_name: String,
    pub duration: f64,
    pub width: u32,
    pub height: u32,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct VideosResponse {
    pub videos: Vec<VideoInfo>,
}

impl ApiClient {
    pub fn new() -> Self {
        let url = get_api_url();
        Self {
            client: Client::new(),
            base_url: url,
        }
    }

    #[allow(dead_code)]
    pub fn with_url(url: &str) -> Self {
        Self {
            client: Client::new(),
            base_url: url.to_string(),
        }
    }

    pub async fn register_video(&self, path: &str) -> Result<RegisterResponse> {
        let url = format!("{}/api/v1/register", self.base_url);
        let request = RegisterRequest {
            path: path.to_string(),
        };
        let mut request_builder = self.client.post(&url).json(&request);
        if let Some(key) = get_api_key() {
            request_builder = request_builder.header("X-API-Key", key);
        }
        let response = request_builder.send().await?;
        let status = response.status();
        let result = response.json::<RegisterResponse>().await?;
        if !status.is_success() {
            anyhow::bail!("API request failed with status: {}", status);
        }
        Ok(result)
    }

    pub async fn search_chunks(
        &self,
        query: &str,
        uuid: Option<&str>,
        limit: Option<usize>,
    ) -> Result<SearchResponse> {
        let url = format!("{}/api/v1/search", self.base_url);
        let request = SearchRequest {
            query: query.to_string(),
            limit,
            uuid: uuid.map(|s| s.to_string()),
        };
        let mut request_builder = self.client.post(&url).json(&request);
        if let Some(key) = get_api_key() {
            request_builder = request_builder.header("X-API-Key", key);
        }
        let response = request_builder.send().await?;
        let status = response.status();
        let result = response.json::<SearchResponse>().await?;
        if !status.is_success() {
            anyhow::bail!("API request failed with status: {}", status);
        }
        Ok(result)
    }

    pub async fn lookup_video(&self, uuid: &str) -> Result<LookupResponse> {
        let url = format!("{}/api/v1/lookup?uuid={}", self.base_url, uuid);
        let mut request = self.client.get(&url);
        if let Some(key) = get_api_key() {
            request = request.header("X-API-Key", key);
        }
        let response = request.send().await?;
        let status = response.status();
        if status == 200 {
            let result = response.json::<LookupResponse>().await?;
            if result.uuid.is_empty() {
                anyhow::bail!("影片不存在: {}", uuid);
            }
            Ok(result)
        } else {
            anyhow::bail!("API request failed with status: {}", status);
        }
    }

    pub async fn list_videos(&self) -> Result<Vec<VideoInfo>> {
        let url = format!("{}/api/v1/videos", self.base_url);
        let mut request = self.client.get(&url);
        if let Some(key) = get_api_key() {
            request = request.header("X-API-Key", key);
        }
        let response = request.send().await?;
        let status = response.status();
        let result = response.json::<VideosResponse>().await?;
        if !status.is_success() {
            anyhow::bail!("API request failed with status: {}", status);
        }
        Ok(result.videos)
    }

    pub fn base_url(&self) -> &str {
        &self.base_url
    }
}

impl Default for ApiClient {
    fn default() -> Self {
        Self::new()
    }
}

pub fn find_video_path() -> Option<String> {
    let test_dirs = vec![
        PathBuf::from("/Users/accusys/Movies"),
        PathBuf::from("/Users/accusys/Downloads"),
        PathBuf::from("/Users/accusys/momentry_core_project/test_video"),
        PathBuf::from("."),
    ];

    for dir in test_dirs {
        if dir.exists() {
            if let Ok(entries) = std::fs::read_dir(&dir) {
                for entry in entries.flatten() {
                    let path = entry.path();
                    if let Some(ext) = path.extension() {
                        let ext_str = ext.to_string_lossy().to_lowercase();
                        if matches!(
                            ext_str.as_str(),
                            "mp4" | "mov" | "m4v" | "avi" | "mkv" | "webm"
                        ) {
                            return Some(path.to_string_lossy().to_string());
                        }
                    }
                }
            }
        }
    }
    None
}
