//! kovi-plugin-web-shot
//!
//! 自动检测消息中的 URL 并发送网页截图（支持长截图）。

// =============================
//          Modules
// =============================

mod config {
    use kovi::toml;
    use kovi::utils::{load_toml_data, save_toml_data};
    use serde::{Deserialize, Serialize};
    use std::path::PathBuf;
    use std::sync::{Arc, RwLock};

    pub static CONFIG: std::sync::OnceLock<Arc<RwLock<Config>>> = std::sync::OnceLock::new();

    pub fn get() -> Arc<RwLock<Config>> {
        CONFIG.get().cloned().expect("Config not initialized")
    }

    const DEFAULT_CONFIG: &str = r#"
# 开关
enabled = true

# 是否只在 @ 机器人时响应（设为 false 则自动检测所有消息）
only_at = false

# 截图最大高度限制 (像素)，防止无限滚动页面导致崩溃
max_height = 5000

# 页面加载超时时间 (秒)
timeout_seconds = 30

# 截图质量 (1-100, 仅对 jpeg/webp 有效)
quality = 80

# 视口宽度
viewport_width = 1280

# 像素比 (2.0 为高清/Retina)
device_scale_factor = 1.0

# 忽略的域名关键词 (如不截图百度)
# ignore_domains = ["baidu.com"]
ignore_domains = []

# 频道控制
[channel]
# 白名单群组 (如果不为空，则只在这些群里生效)
white = []
# 黑名单群组
black = []
"#;

    #[derive(Debug, Serialize, Deserialize, Clone)]
    pub struct ChannelConfig {
        pub white: Vec<String>,
        pub black: Vec<String>,
    }

    #[derive(Debug, Serialize, Deserialize, Clone)]
    pub struct Config {
        pub enabled: bool,
        pub only_at: bool,
        pub max_height: u32,
        pub timeout_seconds: u64,
        pub quality: u8,
        pub viewport_width: u32,
        pub device_scale_factor: f64,
        pub ignore_domains: Vec<String>,
        pub channel: ChannelConfig,

        #[serde(skip)]
        config_path: PathBuf,
    }

    impl Config {
        pub fn load(data_dir: PathBuf) -> Arc<RwLock<Self>> {
            if !data_dir.exists() {
                std::fs::create_dir_all(&data_dir).expect("Failed to create data directory");
            }
            let config_path = data_dir.join("config.toml");

            let default: Config = toml::from_str(DEFAULT_CONFIG).unwrap();
            let mut config = load_toml_data(default, config_path.clone()).unwrap_or_else(|_| {
                let c: Config = toml::from_str(DEFAULT_CONFIG).unwrap();
                c
            });

            config.config_path = config_path;

            Arc::new(RwLock::new(config))
        }

        pub fn save(&self) {
            let _ = save_toml_data(self, &self.config_path);
        }
    }
}

mod core {
    use anyhow::{Result, anyhow};
    use cdp_html_shot::{Browser, CaptureOptions, ImageFormat, Viewport};
    use kovi::tokio::time::{self, Duration};

    /// 核心截图逻辑
    pub async fn capture_url(url: &str, config: &super::config::Config) -> Result<String> {
        let browser = Browser::instance().await;

        // 创建新标签页
        let tab = browser.new_tab().await?;

        // 1. 设置初始视口
        let initial_viewport = Viewport::new(config.viewport_width, 800)
            .with_device_scale_factor(config.device_scale_factor);
        tab.set_viewport(&initial_viewport).await?;

        // 2. 导航到 URL，设置超时
        // 使用 timeout 包裹 goto 操作
        match time::timeout(Duration::from_secs(config.timeout_seconds), tab.goto(url)).await {
            Ok(Ok(_)) => {}
            Ok(Err(e)) => {
                let _ = tab.close().await;
                return Err(anyhow!("Navigate failed: {}", e));
            }
            Err(_) => {
                let _ = tab.close().await;
                return Err(anyhow!("Page load timeout"));
            }
        };

        // 等待一点时间让动态内容加载（可选，更稳健）
        time::sleep(Duration::from_millis(1000)).await;

        // 3. 获取页面实际高度
        // document.documentElement.scrollHeight 通常比 body.scrollHeight 更准确
        let height_js =
            "Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)";
        let page_height = tab.evaluate(height_js).await?.as_f64().unwrap_or(800.0) as u32;

        // 4. 限制最大高度，防止内存溢出
        let final_height = page_height.min(config.max_height).max(100);

        // 5. 如果页面高度大于初始视口，调整视口进行长截图
        let capture_viewport = Viewport::new(config.viewport_width, final_height)
            .with_device_scale_factor(config.device_scale_factor);

        tab.set_viewport(&capture_viewport).await?;

        // 给浏览器一点时间重新布局
        if page_height > 800 {
            time::sleep(Duration::from_millis(500)).await;
        }

        // 6. 截图配置
        // 使用 JPEG 以减小图片体积，或者 PNG 保证质量
        let format = if config.quality >= 100 {
            ImageFormat::Png
        } else {
            ImageFormat::Jpeg
        };

        let opts = CaptureOptions::new()
            .with_viewport(capture_viewport)
            .with_format(format)
            .with_quality(config.quality)
            .with_full_page(true); // captureBeyondViewport

        // 7. 执行截图
        let base64_data = tab.screenshot(opts).await;

        // 8. 关闭标签页
        let _ = tab.close().await;

        base64_data.map_err(|e| anyhow!("Screenshot failed: {}", e))
    }
}

mod utils {
    use regex::Regex;
    use std::sync::OnceLock;

    pub static URL_REGEX: OnceLock<Regex> = OnceLock::new();

    pub fn extract_url(text: &str) -> Option<String> {
        let re = URL_REGEX.get_or_init(|| {
            // 简单的 URL 匹配，匹配 http/https 开头
            Regex::new(r"https?://[^\s\u4e00-\u9fa5]+").expect("Invalid Regex")
        });

        re.find(text).map(|m| m.as_str().to_string())
    }

    pub fn should_process(group_id: Option<&i64>, white: &[String], black: &[String]) -> bool {
        let gid = match group_id {
            Some(id) => id.to_string(),
            None => return true, // 私聊默认处理，或者根据需求修改
        };

        if black.contains(&gid) {
            return false;
        }

        if !white.is_empty() && !white.contains(&gid) {
            return false;
        }

        true
    }
}

// =============================
//      Main Plugin Logic
// =============================

use kovi::{Message, PluginBuilder};

#[kovi::plugin]
async fn main() {
    let bot = PluginBuilder::get_runtime_bot();
    let data_dir = bot.get_data_path();

    // 初始化配置
    let config_lock = config::Config::load(data_dir.clone());
    config::CONFIG.set(config_lock.clone()).ok();

    // 初始化浏览器实例 (单例)
    cdp_html_shot::Browser::instance().await;

    println!("[WebShot] Plugin loaded. Waiting for URLs...");

    PluginBuilder::on_msg(move |event| {
        let config_lock = config_lock.clone();

        async move {
            let config = {
                let guard = config_lock.read().unwrap();
                guard.clone()
            };

            if !config.enabled {
                return;
            }

            // 群组黑白名单过滤
            if !utils::should_process(
                event.group_id.as_ref(),
                &config.channel.white,
                &config.channel.black,
            ) {
                return;
            }

            // 仅 @机器人 时响应
            if config.only_at {
                let message = &event.message;
                let first_segment = match message.get_from_index(0) {
                    Some(seg) => seg,
                    None => return,
                };
                if first_segment.type_ != "at"
                    || first_segment.data["qq"]
                        .as_str()
                        .unwrap()
                        .parse::<i64>()
                        .unwrap()
                        != event.self_id
                {
                    return;
                }
            }

            // 获取文本内容
            let text = match event.borrow_text() {
                Some(t) => t,
                None => return,
            };

            // 提取 URL
            if let Some(url) = utils::extract_url(text) {
                // 检查忽略域名
                for ignore in &config.ignore_domains {
                    if url.contains(ignore) {
                        return;
                    }
                }

                // 简单的防自我触发（如果机器人自己发链接）
                if event.user_id == event.self_id {
                    return;
                }

                // 告知用户正在截图 (可选，防止用户以为没反应)
                // event.reply("📷 正在获取网页截图...");

                match core::capture_url(&url, &config).await {
                    Ok(base64_img) => {
                        let msg = Message::new()
                            .add_reply(event.message_id) // 引用原消息
                            .add_text(format!("🔗 链接: {}\n", url))
                            .add_image(&format!("base64://{}", base64_img));
                        event.reply(msg);
                    }
                    Err(e) => {
                        // 失败时是否发送消息取决于需求，避免刷屏通常不发或发简短提示
                        eprintln!("[WebShot] Error capturing {}: {}", url, e);
                        let msg = Message::new()
                            .add_reply(event.message_id)
                            .add_text(format!("❌ 截图失败: {}", e));
                        event.reply(msg);
                    }
                }
            }
        }
    });

    // 插件卸载/退出时的清理
    PluginBuilder::drop(move || {
        // 读取配置并保存
        let config_lock = config::get();
        async move {
            let config = {
                let guard = config_lock.read().unwrap();
                guard.clone()
            };
            config.save();

            // 关闭浏览器
            if let Err(e) = cdp_html_shot::Browser::instance().await.close_async().await {
                eprintln!("[WebShot] Error closing browser: {}", e);
            }
        }
    });
}
