use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use reqwest::header::{HeaderMap, CONTENT_TYPE, COOKIE};
use reqwest::{Client, Response};
use scraper::{Html, Selector};
use std::error::Error;
use std::io;
use std::ops::Index;
use std::time::Duration;
use tokio::sync::{Mutex};
use std::sync::Arc;
use tokio::{task, time};
use std::collections::{BTreeMap, VecDeque};
extern crate log;

pub mod wclip;
pub mod sys_res;
pub struct ClipboardSync {
    user_name: String,
    password: String,
    web: Client,
    ip: String,
    cookies: String,
    clip: wclip::Wclip,
    //last message magic
    cache_magic: u8,
}

const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42";

fn quote(input: &String) -> String {
    percent_encode(input.as_bytes(), NON_ALPHANUMERIC).to_string()
}

pub struct ClipboardMsgs {
    pub msgs: Vec<String>,
}

async fn msg_sync(ctx:Arc<Mutex<ClipboardSync>>){
    let mut ctx = ctx.lock().await;
    loop{
        let _ = ctx.update_msg().await;
        time::sleep(Duration::new(10, 0)).await;
    }
}
pub async fn start_msg_sync(ctx:Arc<Mutex<ClipboardSync>>) -> Result<task::JoinHandle<()>, Box<dyn Error>>{
    let handle = task::spawn(msg_sync(ctx));
    Ok(handle)
}

enum Msg <'a>{
    Msg(&'a str),
    Sub((u8, u8, u8, &'a str)),
}

impl ClipboardSync {
    pub fn new(ip: &str, user_name: &str, password: &str) -> ClipboardSync {
        ClipboardSync {
            user_name: user_name.to_string(),
            password: password.to_string(),
            web: Client::new(),
            ip: ip.to_string(),
            cookies: "".to_string(),
            clip: wclip::Wclip::new(),
            cache_magic: 0xff,
        }
    }


    async fn verify(&self) -> Result<Response, Box<dyn Error>> {
        // 开始验证
        let security_check_url = format!("http://{}/SSWeb/rd/j_security_check", self.ip);
        let mut headers = HeaderMap::new();
        headers.insert("Host", "ssweb".parse().unwrap());
        headers.insert("Connection", "keep-alive".parse().unwrap());
        headers.insert("Cache-Control", "max-age=0".parse().unwrap());
        headers.insert("Origin", "http://ssweb".parse().unwrap());
        headers.insert("DNT", "1".parse().unwrap());
        headers.insert("Upgrade-Insecure-Requests", "1".parse().unwrap());
        headers.insert(
            "Content-Type",
            "application/x-www-form-urlencoded".parse().unwrap(),
        );
        headers.insert("User-Agent", USER_AGENT.parse().unwrap());
        headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse().unwrap());
        headers.insert(
            "Referer",
            "http://ssweb/SSWeb/rd/copy_paste.jsp".parse().unwrap(),
        );
        headers.insert("Accept-Encoding", "gzip, deflate".parse().unwrap());
        headers.insert(
            "Accept-Language",
            "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
                .parse()
                .unwrap(),
        );
        headers.insert("Cookie", self.cookies.parse().unwrap());
        let contents = format!(
            "j_username={}&j_password={}",
            quote(&self.user_name),
            quote(&self.password)
        );
        let resp = self
            .web
            .post(security_check_url)
            .headers(headers)
            .body(contents)
            .send()
            .await?;
        Ok(resp)
    }
    fn get_cookies(response: &Response) -> String {
        let mut session = String::new();
        let headers: &HeaderMap = response.headers();
        if let Some(header_value) = headers.get("Set-Cookie") {
            if let Ok(value) = header_value.to_str() {
                log::info!("Set-Cookie: {}", value);
                session = value.split(';').next().unwrap().to_string();
            }
        } else {
            log::error!("cookies not found");
        }
        session
    }
    pub async fn login(&mut self) -> Result<(), Box<dyn Error>> {
        let mut headers = HeaderMap::new();
        headers.insert("User-Agent", USER_AGENT.parse().unwrap());
        let url = format!("http://{}/SSWeb/rd/copy_paste.jsp", self.ip);
        let response = self.web.get(url).headers(headers).send().await?;
        self.cookies = ClipboardSync::get_cookies(&response);
        log::info!("cookies is {}", self.cookies);
        let mut resp = response.text().await?;
        log::trace!("Response: {}", resp);
        if resp.contains("loginForm") {
            let response = self.verify().await?;
            resp = response.text().await?;
            log::trace!("verify Response: {}", resp);
        }
        if resp.contains("<title>Copy & Paste</title>") {
            Ok(())
        } else {
            Err(Box::new(io::Error::new(io::ErrorKind::Other, "登录失败")))
        }
    }

    pub async fn update_msg(&mut self) -> Result<(), Box<dyn Error>> {
        //http://ssweb/SSWeb/rd/copy_paste.jsp?d-16544-p=1&d-16544-s=2
        //d-16544-s 2:时间从靠近现在开始排序 1:时间从最早开始排序
        //d-16544-o mesage排序，不要设置
        //d-16544-p=1 页码
        let url = format!("http://{}/SSWeb/rd/copy_paste.jsp?d-16544-p=1&d-16544-s=2", self.ip);
        let mut headers = HeaderMap::new();
        headers.insert("Host", "ssweb".parse().unwrap());
        headers.insert("Connection", "keep-alive".parse().unwrap());
        headers.insert("Cache-Control", "max-age=0".parse().unwrap());
        headers.insert("DNT", "1".parse().unwrap());
        headers.insert("Upgrade-Insecure-Requests", "1".parse().unwrap());
        headers.insert("User-Agent", USER_AGENT.parse().unwrap());
        headers.insert("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".parse().unwrap());
        headers.insert("Accept-Encoding", "gzip, deflate".parse().unwrap());
        headers.insert("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6".parse().unwrap());
        let response = self.web.get(url).headers(headers).send().await?;
        let resp = response.text().await?;
        self.msg2clip(&resp).await;
        Ok(())
    }
    fn parse_msg<'a>(msg: &'a str) -> Msg {
        //x|x|x| -> index|total|magic|
        let head = &msg[0..7];
        let body = &msg[7..];
        let check = head.split('|').filter(|text| {
            let text = text.as_bytes();
            if text.len() == 1 {
                if text[0] >= b'!' && text[0] < b'|' {true} else {false}
            } else {
                false
            }
        }).map(|x|x.as_bytes()[0] - b'!').collect::<Vec<_>>();

        if check.len() != 3 {
            return Msg::Msg(msg)
        }

        return Msg::Sub((check[0], check[1], check[2], body));
    }
    pub async fn msg2clip(&mut self, html: &str) {
        // log::info!("html: {}", html);
        let document = Html::parse_document(html);
        let selector = Selector::parse("code").unwrap();
        let mut msg_map = BTreeMap::new();
        let mut magic_ref = 0xff;
        let mut is_first = true;
        for element in document.select(&selector).into_iter() {
            //let text = element.text().collect::<Vec<_>>().concat();
            let text = element.text().collect::<String>();
            log::info!("Found: [{}]", text);
            let msg = ClipboardSync::parse_msg(text.as_str());
            match msg {
                Msg::Msg(msg) => {
                    if magic_ref == 0xff {
                        self.clip.set(&msg);
                        break;
                    }
                },
                Msg::Sub((index, total, magic, _body)) => {
                    magic_ref = if magic_ref == 0xff {magic} else {magic_ref};
                    if magic_ref != magic { // other message ignore
                        if is_first {
                            break;
                        }
                        is_first = false;
                        continue
                    }
                    if msg_map.len() == 0 && (
                        self.cache_magic == magic_ref // message is repeated ignore it
                        || !is_first // we only process the newest msg
                    ) {
                        break;
                    }
                    // same msg pack, append to list
                    msg_map.insert(index, text);
                    if msg_map.len() as u8 == total {
                        let msg = msg_map.into_iter().map(|(_key, v)| v).collect::<String>();
                        self.cache_magic = magic_ref; // save for next msg
                        self.clip.set(&msg);
                        break;
                    }
                }
            }
            is_first = false;
        }
    }
}
