use screenshots::Screen;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Mutex;
use std::time::Instant;
use tauri::{Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
use tauri_plugin_shell::{
    process::{CommandChild, CommandEvent},
    ShellExt,
};
use xcap::{Monitor, Window};

struct AgentProcess(Mutex<Option<CommandChild>>);
struct VisionStreamProcess(Mutex<Option<CommandChild>>);

#[derive(Clone, Deserialize, Serialize)]
struct AgentLog {
    level: String,
    message: String,
    timestamp: Option<String>,
}

#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct CaptureResult {
    screenshot_path: String,
    screenshot_width: u32,
    screenshot_height: u32,
    scale_factor: f64,
    source: Option<CaptureSource>,
    screen_list_ms: u128,
    capture_ms: u128,
    save_ms: u128,
    total_ms: u128,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct CaptureSource {
    id: String,
    kind: String,
    label: String,
    app_name: Option<String>,
    title: Option<String>,
    pid: Option<u32>,
    x: i32,
    y: i32,
    width: u32,
    height: u32,
    scale_factor: f64,
}

#[derive(Clone, Deserialize, Serialize)]
struct Region {
    id: String,
    name: String,
    description: Option<String>,
    #[serde(rename = "type")]
    region_type: String,
    bbox_image: [f64; 4],
    bbox_source: Option<[f64; 4]>,
    bbox_screen: [f64; 4],
    #[serde(rename = "scaleFactor")]
    scale_factor: f64,
}

#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct AnnotationFile {
    app: String,
    screenshot_path: String,
    screenshot_width: u32,
    screenshot_height: u32,
    scale_factor: f64,
    source: Option<CaptureSource>,
    regions: Vec<Region>,
    created_at: String,
    updated_at: String,
}

fn data_dir(app: &tauri::AppHandle) -> Result<PathBuf, String> {
    let dir = app
        .path()
        .app_data_dir()
        .map_err(|error| error.to_string())?
        .join("data");
    fs::create_dir_all(&dir).map_err(|error| error.to_string())?;
    Ok(dir)
}

fn regions_path(app: &tauri::AppHandle) -> Result<PathBuf, String> {
    let dir = data_dir(app)?.join("regions");
    fs::create_dir_all(&dir).map_err(|error| error.to_string())?;
    Ok(dir.join("wechat.json"))
}

fn screenshot_path(app: &tauri::AppHandle) -> Result<PathBuf, String> {
    let screenshot_dir = data_dir(app)?.join("screenshots");
    fs::create_dir_all(&screenshot_dir).map_err(|error| error.to_string())?;
    Ok(screenshot_dir.join("WeChat.jpg"))
}

fn save_capture_image(
    width: u32,
    height: u32,
    rgba_bytes: Vec<u8>,
    screenshot_path: PathBuf,
    source: Option<CaptureSource>,
    default_scale_factor: f64,
    screen_list_ms: u128,
    capture_ms: u128,
    total_started_at: Instant,
) -> Result<CaptureResult, String> {
    let save_started_at = Instant::now();
    let rgba_image = image::RgbaImage::from_raw(width, height, rgba_bytes)
        .ok_or_else(|| "invalid screenshot buffer".to_string())?;
    let rgb_image = image::DynamicImage::ImageRgba8(rgba_image).to_rgb8();
    let mut file = fs::File::create(&screenshot_path).map_err(|error| error.to_string())?;
    let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut file, 88);
    encoder
        .encode_image(&rgb_image)
        .map_err(|error| error.to_string())?;
    let save_ms = save_started_at.elapsed().as_millis();
    let total_ms = total_started_at.elapsed().as_millis();
    let scale_factor = source
        .as_ref()
        .map(|item| item.scale_factor)
        .unwrap_or(default_scale_factor);

    println!(
        "capture_source timing: screen_list={}ms capture={}ms save={}ms total={}ms size={}x{} scale={}",
        screen_list_ms, capture_ms, save_ms, total_ms, width, height, scale_factor
    );

    Ok(CaptureResult {
        screenshot_path: screenshot_path.to_string_lossy().to_string(),
        screenshot_width: width,
        screenshot_height: height,
        scale_factor,
        source,
        screen_list_ms,
        capture_ms,
        save_ms,
        total_ms,
    })
}

fn emit_agent_log(app: &tauri::AppHandle, level: &str, message: impl Into<String>) {
    let _ = app.emit(
        "agent-log",
        AgentLog {
            level: level.to_string(),
            message: message.into(),
            timestamp: None,
        },
    );
}

fn emit_agent_line(app: &tauri::AppHandle, fallback_level: &str, line: String) {
    if let Ok(log) = serde_json::from_str::<AgentLog>(&line) {
        let _ = app.emit("agent-log", log);
        return;
    }

    emit_agent_log(app, fallback_level, line);
}

#[tauri::command]
fn start_agent(app: tauri::AppHandle, process: tauri::State<AgentProcess>) -> Result<(), String> {
    let mut process_guard = process.0.lock().map_err(|error| error.to_string())?;

    if process_guard.is_some() {
        emit_agent_log(&app, "warning", "agent already running");
        return Ok(());
    }

    let (mut rx, child) = app
        .shell()
        .sidecar("agent")
        .map_err(|error| error.to_string())?
        .spawn()
        .map_err(|error| error.to_string())?;

    *process_guard = Some(child);
    emit_agent_log(&app, "info", "agent started");

    let app_handle = app.clone();
    tauri::async_runtime::spawn(async move {
        while let Some(event) = rx.recv().await {
            match event {
                CommandEvent::Stdout(line) => {
                    emit_agent_line(
                        &app_handle,
                        "info",
                        String::from_utf8_lossy(&line).trim().to_string(),
                    );
                }
                CommandEvent::Stderr(line) => {
                    emit_agent_line(
                        &app_handle,
                        "error",
                        String::from_utf8_lossy(&line).trim().to_string(),
                    );
                }
                CommandEvent::Error(error) => {
                    emit_agent_log(&app_handle, "error", error);
                }
                CommandEvent::Terminated(payload) => {
                    emit_agent_log(
                        &app_handle,
                        "warning",
                        format!("agent exited with code {:?}", payload.code),
                    );

                    if let Ok(mut process_guard) = app_handle.state::<AgentProcess>().0.lock() {
                        *process_guard = None;
                    }

                    break;
                }
                _ => {}
            }
        }
    });

    Ok(())
}

#[tauri::command]
fn stop_agent(app: tauri::AppHandle, process: tauri::State<AgentProcess>) -> Result<(), String> {
    let mut process_guard = process.0.lock().map_err(|error| error.to_string())?;

    if let Some(child) = process_guard.take() {
        child.kill().map_err(|error| error.to_string())?;
        emit_agent_log(&app, "warning", "agent stopped");
    } else {
        emit_agent_log(&app, "warning", "agent is not running");
    }

    Ok(())
}

#[tauri::command]
fn start_vision_stream(
    app: tauri::AppHandle,
    process: tauri::State<VisionStreamProcess>,
) -> Result<(), String> {
    let mut process_guard = process.0.lock().map_err(|error| error.to_string())?;
    if process_guard.is_some() {
        return Ok(());
    }

    stop_legacy_python_vision_stream();

    let (mut rx, child) = app
        .shell()
        .sidecar("agent")
        .map_err(|error| error.to_string())?
        .args(["vision-stream"])
        .spawn()
        .map_err(|error| error.to_string())?;

    *process_guard = Some(child);

    let app_handle = app.clone();
    tauri::async_runtime::spawn(async move {
        while let Some(event) = rx.recv().await {
            match event {
                CommandEvent::Stdout(line) => {
                    emit_agent_line(
                        &app_handle,
                        "info",
                        String::from_utf8_lossy(&line).trim().to_string(),
                    );
                }
                CommandEvent::Stderr(line) => {
                    emit_agent_line(
                        &app_handle,
                        "error",
                        String::from_utf8_lossy(&line).trim().to_string(),
                    );
                }
                CommandEvent::Error(error) => {
                    emit_agent_log(
                        &app_handle,
                        "error",
                        format!("vision stream error: {error}"),
                    );
                }
                CommandEvent::Terminated(payload) => {
                    emit_agent_log(
                        &app_handle,
                        "warning",
                        format!("vision stream exited with code {:?}", payload.code),
                    );
                    if let Ok(mut process_guard) =
                        app_handle.state::<VisionStreamProcess>().0.lock()
                    {
                        *process_guard = None;
                    }
                    break;
                }
                _ => {}
            }
        }
    });

    Ok(())
}

fn stop_legacy_python_vision_stream() {
    let Ok(cwd) = std::env::current_dir() else {
        return;
    };
    let candidates = [
        cwd.join("wechat_vision"),
        cwd.join("..").join("wechat_vision"),
        cwd.join("..").join("..").join("wechat_vision"),
    ];

    for dir in candidates {
        let pid_file = dir.join("ouptsw").join("wechat_algorithm_live.pid");
        let stop_script = dir.join("stop_wechat_algorithm_live.sh");
        if pid_file.exists() && stop_script.exists() {
            let _ = Command::new("bash")
                .arg(stop_script)
                .current_dir(dir)
                .output();
            return;
        }
    }
}

#[tauri::command]
fn stop_vision_stream(
    _app: tauri::AppHandle,
    process: tauri::State<VisionStreamProcess>,
) -> Result<(), String> {
    let mut process_guard = process.0.lock().map_err(|error| error.to_string())?;
    if let Some(child) = process_guard.take() {
        child.kill().map_err(|error| error.to_string())?;
    }

    Ok(())
}

#[tauri::command]
async fn capture_screen(app: tauri::AppHandle) -> Result<CaptureResult, String> {
    let screenshot_path = screenshot_path(&app)?;

    tauri::async_runtime::spawn_blocking(move || {
        let total_started_at = Instant::now();
        let screen_list_started_at = Instant::now();
        let screen = Screen::all()
            .map_err(|error| error.to_string())?
            .into_iter()
            .next()
            .ok_or_else(|| "no screen found".to_string())?;
        let screen_list_ms = screen_list_started_at.elapsed().as_millis();

        let capture_started_at = Instant::now();
        let image = screen.capture().map_err(|error| error.to_string())?;
        let capture_ms = capture_started_at.elapsed().as_millis();
        let width = image.width();
        let height = image.height();
        save_capture_image(
            width,
            height,
            image.into_raw(),
            screenshot_path,
            None,
            screen.display_info.scale_factor as f64,
            screen_list_ms,
            capture_ms,
            total_started_at,
        )
    })
    .await
    .map_err(|error| error.to_string())?
}

#[tauri::command]
async fn list_capture_sources() -> Result<Vec<CaptureSource>, String> {
    tauri::async_runtime::spawn_blocking(move || {
        let mut sources = Vec::new();

        for (index, monitor) in Monitor::all()
            .map_err(|error| error.to_string())?
            .into_iter()
            .enumerate()
        {
            let width = monitor.width().map_err(|error| error.to_string())?;
            let height = monitor.height().map_err(|error| error.to_string())?;
            let x = monitor.x().map_err(|error| error.to_string())?;
            let y = monitor.y().map_err(|error| error.to_string())?;
            let scale_factor = monitor.scale_factor().map_err(|error| error.to_string())? as f64;

            sources.push(CaptureSource {
                id: format!("display:{index}"),
                kind: "display".to_string(),
                label: format!("桌面 {} · {}x{}", index + 1, width, height),
                app_name: None,
                title: None,
                pid: None,
                x,
                y,
                width,
                height,
                scale_factor,
            });
        }

        if let Ok(windows) = Window::all() {
            for window in windows {
                let width = match window.width() {
                    Ok(width) => width,
                    Err(_) => continue,
                };
                let height = match window.height() {
                    Ok(height) => height,
                    Err(_) => continue,
                };

                if window.is_minimized().unwrap_or(true) || width < 80 || height < 80 {
                    continue;
                }

                let app_name = window.app_name().unwrap_or_default();
                let title = window.title().unwrap_or_default();
                if app_name.is_empty() && title.is_empty() {
                    continue;
                }

                sources.push(CaptureSource {
                    id: format!("window:{}", window.id().map_err(|error| error.to_string())?),
                    kind: "window".to_string(),
                    label: format!("{} · {}", app_name, title),
                    app_name: Some(app_name),
                    title: Some(title),
                    pid: window.pid().ok(),
                    x: window.x().unwrap_or(0),
                    y: window.y().unwrap_or(0),
                    width,
                    height,
                    scale_factor: 1.0,
                });
            }
        }

        Ok(sources)
    })
    .await
    .map_err(|error| error.to_string())?
}

#[tauri::command]
async fn capture_source(app: tauri::AppHandle, source_id: String) -> Result<CaptureResult, String> {
    let screenshot_path = screenshot_path(&app)?;

    tauri::async_runtime::spawn_blocking(move || {
        let total_started_at = Instant::now();
        let screen_list_started_at = Instant::now();

        if let Some(index_text) = source_id.strip_prefix("display:") {
            let index = index_text
                .parse::<usize>()
                .map_err(|error| error.to_string())?;
            let monitors = Monitor::all().map_err(|error| error.to_string())?;
            let monitor = monitors
                .into_iter()
                .nth(index)
                .ok_or_else(|| "display source not found".to_string())?;
            let screen_list_ms = screen_list_started_at.elapsed().as_millis();
            let width = monitor.width().map_err(|error| error.to_string())?;
            let height = monitor.height().map_err(|error| error.to_string())?;
            let scale_factor = monitor.scale_factor().map_err(|error| error.to_string())? as f64;
            let source = CaptureSource {
                id: source_id,
                kind: "display".to_string(),
                label: format!("桌面 {} · {}x{}", index + 1, width, height),
                app_name: None,
                title: None,
                pid: None,
                x: monitor.x().map_err(|error| error.to_string())?,
                y: monitor.y().map_err(|error| error.to_string())?,
                width,
                height,
                scale_factor,
            };

            let capture_started_at = Instant::now();
            let image = monitor.capture_image().map_err(|error| error.to_string())?;
            let capture_ms = capture_started_at.elapsed().as_millis();
            let image_width = image.width();
            let image_height = image.height();
            return save_capture_image(
                image_width,
                image_height,
                image.into_raw(),
                screenshot_path,
                Some(source.clone()),
                source.scale_factor,
                screen_list_ms,
                capture_ms,
                total_started_at,
            );
        }

        if let Some(window_id_text) = source_id.strip_prefix("window:") {
            let window_id = window_id_text
                .parse::<u32>()
                .map_err(|error| error.to_string())?;
            let windows = Window::all().map_err(|error| error.to_string())?;
            let window = windows
                .into_iter()
                .find(|item| item.id().ok() == Some(window_id))
                .ok_or_else(|| "window source not found".to_string())?;
            let screen_list_ms = screen_list_started_at.elapsed().as_millis();
            let window_width = window.width().map_err(|error| error.to_string())?;
            let window_height = window.height().map_err(|error| error.to_string())?;
            let app_name = window.app_name().unwrap_or_default();
            let title = window.title().unwrap_or_default();
            let capture_started_at = Instant::now();
            let image = window.capture_image().map_err(|error| error.to_string())?;
            let capture_ms = capture_started_at.elapsed().as_millis();
            let image_width = image.width();
            let image_height = image.height();
            let scale_factor = if window_width > 0 {
                image_width as f64 / window_width as f64
            } else {
                1.0
            };
            let source = CaptureSource {
                id: source_id,
                kind: "window".to_string(),
                label: format!("{} · {}", app_name, title),
                app_name: Some(app_name),
                title: Some(title),
                pid: window.pid().ok(),
                x: window.x().unwrap_or(0),
                y: window.y().unwrap_or(0),
                width: window_width,
                height: window_height,
                scale_factor,
            };

            return save_capture_image(
                image_width,
                image_height,
                image.into_raw(),
                screenshot_path,
                Some(source.clone()),
                source.scale_factor,
                screen_list_ms,
                capture_ms,
                total_started_at,
            );
        }

        Err("unsupported capture source".to_string())
    })
    .await
    .map_err(|error| error.to_string())?
}

#[tauri::command]
fn save_regions(app: tauri::AppHandle, annotation: AnnotationFile) -> Result<String, String> {
    let path = regions_path(&app)?;
    let json = serde_json::to_string_pretty(&annotation).map_err(|error| error.to_string())?;
    fs::write(&path, json).map_err(|error| error.to_string())?;
    Ok(path.to_string_lossy().to_string())
}

#[tauri::command]
fn load_regions(app: tauri::AppHandle) -> Result<Option<AnnotationFile>, String> {
    let path = regions_path(&app)?;
    if !path.exists() {
        return Ok(None);
    }

    let json = fs::read_to_string(&path).map_err(|error| error.to_string())?;
    serde_json::from_str(&json)
        .map(Some)
        .map_err(|error| error.to_string())
}

#[tauri::command]
fn open_popup_window(app: tauri::AppHandle, route: String) -> Result<(), String> {
    let route = if route.starts_with('/') {
        route
    } else {
        format!("/{route}")
    };
    let label = format!("popup-{}", route.trim_start_matches('/').replace('/', "-"));

    if let Some(window) = app.get_webview_window(&label) {
        if route == "/window/engine-workbench" {
            window
                .set_fullscreen(false)
                .map_err(|error| error.to_string())?;
            window.maximize().map_err(|error| error.to_string())?;
        }
        window.set_focus().map_err(|error| error.to_string())?;
        return Ok(());
    }

    let title = match route.as_str() {
        "/annotate" => "屏幕区域标注",
        "/window/settings" => "设置",
        "/window/engine-workbench" => "微信引擎工作台",
        "/window/engine-logs" => "分身引擎日志",
        "/window/knowledge-create" => "新增知识库",
        "/window/knowledge-detail" => "知识库详情",
        "/window/skill-create" => "skill技能详情",
        "/window/employee-detail" => "员工蒸馏详情",
        "/window/distill-start" => "开始蒸馏",
        _ => "二级窗口",
    };

    let mut builder = WebviewWindowBuilder::new(
        &app,
        label,
        WebviewUrl::App(format!("index.html#{route}").into()),
    )
    .title(title)
    .inner_size(1080.0, 800.0)
    .decorations(false)
    .transparent(true)
    .resizable(true);

    if route == "/annotate" {
        builder = builder.maximized(true).fullscreen(true);
    } else if route == "/window/engine-workbench" {
        builder = builder
            .inner_size(1440.0, 920.0)
            .maximized(true)
            .fullscreen(false);
    }

    builder.build().map_err(|error| error.to_string())?;

    Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(AgentProcess(Mutex::new(None)))
        .manage(VisionStreamProcess(Mutex::new(None)))
        .plugin(tauri_plugin_shell::init())
        .setup(|app| {
            if cfg!(debug_assertions) {
                app.handle().plugin(
                    tauri_plugin_log::Builder::default()
                        .level(log::LevelFilter::Info)
                        .build(),
                )?;
            }
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            capture_source,
            capture_screen,
            list_capture_sources,
            load_regions,
            open_popup_window,
            save_regions,
            start_agent,
            start_vision_stream,
            stop_vision_stream,
            stop_agent
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
