use anyhow::{Result, anyhow};
use image::DynamicImage;
use log::info;
use tokio::sync::mpsc::{Receiver, Sender, channel};

use crate::utils::lazy;

pub struct StepDownloader {
    status: SearchStatus,
    rx_status: Receiver<SearchStatus>,
    tx_cmd: Sender<SearchCmd>,
    rx_total: Receiver<SearchTotalInfo>,
    rx_item: Receiver<SearchItemInfo>,
}
impl Default for StepDownloader {
    fn default() -> Self {
        Self::new()
    }
}

pub struct SearchTotalInfo {
    total: u32,
    curr_page: u32,
}
pub struct SearchItemInfo {
    name: String,
    imgs: Vec<DynamicImage>,
    stp_id: String,
}

impl StepDownloader {
    pub fn new() -> Self {
        let status = SearchStatus::Ready;
        let (rx_status, tx_cmd, rx_total, rx_item) = Self::search_process();
        Self {
            status,
            rx_status,
            tx_cmd,
            rx_total,
            rx_item,
        }
    }
}

impl StepDownloader {
    fn search_process() -> (
        Receiver<SearchStatus>,
        Sender<SearchCmd>,
        Receiver<SearchTotalInfo>,
        Receiver<SearchItemInfo>,
    ) {
        log::info!("Create the search_process.");
        let (tx_status, rx_status) = channel(5);
        let (tx_cmd, mut rx_cmd) = channel(5);
        let (tx_total, mut rx_total) = channel(10);
        let (tx_item, mut rx_item) = channel(10);

        tokio::spawn(async move {
            loop {
                if let Some(cmd) = rx_cmd.recv().await {
                    match cmd {
                        SearchCmd::Search(keyword, cur, lim) => {
                            log::info!("To search : {}", keyword.clone());
                            if tx_status.send(SearchStatus::Searching).await.is_err() {
                                log::error!("Failed to send the searching state.")
                            }
                            match Self::search_keyword(&keyword, cur, lim).await {
                                Ok(r) => {
                                    //搜索到了Root列表
                                    let total = SearchTotalInfo {
                                        total: r.result.total,
                                        curr_page: cur,
                                    };
                                    tx_total.send(total).await.unwrap();

                                    //TODO: complete the search process.
                                }
                                Err(e) => {
                                    log::error!("Failed to search by keyword : {e:?}");
                                    if tx_status.send(SearchStatus::Error).await.is_err() {
                                        log::error!("Failed to send tx_status:Error");
                                    }
                                }
                            }
                            log::info!("Search for {} done.", keyword.clone());
                            if tx_status.send(SearchStatus::Done).await.is_err() {
                                log::error!("Failed to send the searching state.")
                            }
                        }
                        SearchCmd::Stop => {
                            log::warn!(
                                "Received cmd to Stop the search process in first rx_cmd.recv, This may cause error!"
                            );
                            if tx_status.send(SearchStatus::Error).await.is_err() {
                                log::error!("Failed to send tx_status:Error");
                            }
                        }
                        SearchCmd::Exit => {
                            log::warn!(
                                "Received cmd to Exit the search_process, that means to exit the whole Application, Whatch that!"
                            );
                            break;
                        }
                    }
                }
            }
        });
        (rx_status, tx_cmd, rx_total, rx_item)
    }
    pub fn get_status(&mut self) -> SearchStatus {
        if let Ok(status) = self.rx_status.try_recv() {
            self.status = status;
        }
        self.status.clone()
    }
    pub fn search(&self, keyword: &str, cur_page: u32, page_lim: u32) {
        if let Ok(()) =
            self.tx_cmd
                .try_send(SearchCmd::Search(keyword.to_owned(), cur_page, page_lim))
        {
            log::info!("Success send the search {keyword} cmd.");
        } else {
            log::warn!("Failed to send the search {keyword} cmd.");
        }
    }
    pub fn exit(&self) {
        let _ = self.tx_cmd.try_send(SearchCmd::Exit);
    }
    pub fn stop(&self) {
        let _ = self.tx_cmd.try_send(SearchCmd::Stop);
    }
    async fn search_has_device(has_device: &str) -> Result<String> {
        let mut form_maps = std::collections::HashMap::new();
        form_maps.insert("uuids[]", has_device);
        let url = "https://pro.lceda.cn/api/devices/searchByIds";
        let resp = reqwest::Client::new()
            .post(url)
            .form(&form_maps)
            .send()
            .await?;
        let text = resp.text().await?;
        let v: serde_json::Value = serde_json::from_str(text.as_str())?;
        let id = v["result"][0]["attributes"]["3D Model"].clone();
        if let serde_json::Value::String(id) = id {
            return Ok(id.clone());
        }
        Err(anyhow::Error::msg("Failed to parse the json."))
    }
    async fn search_model_id(uuid: &str) -> Result<String> {
        let mut form_maps = std::collections::HashMap::new();
        form_maps.insert("uuids[]", uuid);
        form_maps.insert("dataStr", "yes");
        let url = "https://pro.lceda.cn/api/components/searchByIds?forceOnline=1";

        let resp = reqwest::Client::new()
            .post(url)
            .form(&form_maps)
            .send()
            .await?;
        let text = resp.text().await?;
        let v: serde_json::Value = serde_json::from_str(text.as_str())?;
        info!("In search_model_id: The v is : {v:#?}");
        let data_str: serde_json::Value = v["result"][0]["dataStr"].clone();
        info!("The data str Value: {data_str:#?}");
        if let serde_json::Value::String(data_str) = data_str {
            let data_str: DataStr = serde_json::from_str(data_str.as_str())?;
            if let Some(model) = data_str.model {
                return Ok(model);
            }
        }
        Err(anyhow::Error::msg("Failed to parse the json."))
    }
    async fn download_step(id: &str) -> Result<String> {
        let url = format!("https://modules.lceda.cn/qAxj6KHrDKw4blvCG8QJPs7Y/{id}");
        let url = url.as_str();
        let resp = reqwest::get(url).await?;
        let text = resp.text().await?;
        Ok(text)
    }
    async fn search_keyword(
        keyword: &str,
        cur_page: u32,
        page_size: u32,
    ) -> Result<KeywordSearchRoot> {
        let mut form_maps = std::collections::HashMap::new();
        form_maps.insert("keyword", keyword);
        let cur_page = &format!("{cur_page}");
        let page_size = &format!("{page_size}");
        form_maps.insert("curPage", cur_page);
        form_maps.insert("pageSize", page_size);
        let resp = reqwest::Client::new()
            .post("https://pro.lceda.cn/api/eda/product/search")
            .form(&form_maps)
            .send()
            .await?;
        let text = resp.text().await?;
        let j: KeywordSearchRoot = serde_json::from_str(&text)?;
        Ok(j)
    }
}
#[derive(Clone)]
pub enum SearchCmd {
    Search(String, u32, u32),
    Stop,
    Exit,
}
#[derive(Clone)]
pub enum SearchStatus {
    Ready,
    Error,
    Searching,
    Done,
}

#[cfg(test)]
mod StepDownloaderTest {
    use std::io::Write;

    use super::*;
    use log::info;
    use reqwest;

    // #[tokio::test]
    // pub async fn test_search() {
    //     env_logger::try_init().unwrap();
    //     log::info!("To test search `STM32`");
    //     let d = StepDownloader::default();
    //     d.search("STM32");
    //     d.exit();
    // }
    #[tokio::test]
    async fn test_search_func() {
        assert!(env_logger::try_init().is_ok());
        log::info!("Start test for call search_keyword function.");
        test_search("STM32").await;
        // test_search("STEM32F103").await;
        // test_search("TPS54302").await;
    }
    async fn test_search_uuids(d: &str) {
        let rst = super::StepDownloader::search_has_device(d).await;
        match rst {
            Ok(v) => {
                log::info!("Get the uuids: {}", v.clone());
                match super::StepDownloader::search_model_id(v.clone().as_str()).await {
                    Ok(i) => {
                        log::info!("Get the model_id: {}", i.clone());
                        match super::StepDownloader::download_step(i.as_str()).await {
                            Ok(t) => {
                                info!("Success to get the content.");

                                let mut file = std::fs::OpenOptions::new()
                                    .read(false)
                                    .write(true)
                                    .create(true)
                                    .append(false)
                                    .open("web_temp/first_step.step")
                                    .unwrap();
                                file.write_all(t.as_bytes()).unwrap();
                                let _ = file.flush();
                            }
                            Err(e) => todo!(),
                        }
                    }
                    Err(e) => {
                        log::error!("Failed to call search_model_id: {e:?}");
                    }
                }
            }
            Err(e) => {
                log::error!("Failed to call search_has_device: {e:?}");
            }
        }
    }
    async fn test_search(k: &str) {
        let rst = super::StepDownloader::search_keyword(k, 1, 10).await;
        match rst {
            Ok(v) => {
                log::info!("Search done! ========================");
                log::info!("The result:\r\n\r\n {v:#?}");
                let item = &v.result.productList[0];
                let uuids = item.hasDevice.clone();
                if let Some(v) = uuids {
                    test_search_uuids(v.as_str()).await;
                }
            }
            Err(e) => {
                log::error!("Error occured.");
                log::error!("Error : {e:?}");
            }
        }
    }
    // #[tokio::test]
    pub async fn test_post() {
        if env_logger::try_init().is_ok() {
            log::info!("The env_logger::try_init done.");
        }
        log::info!("Start test");
        match reqwest::get("https://www.baidu.com").await {
            Ok(r) => {
                log::info!("Get www.baidu.com : {r:?}");
            }
            Err(e) => {
                log::error!("Failed to get www.baidu.com : {e:?}");
            }
        }
        log::info!("==========================================");
        let mut form = std::collections::HashMap::new();
        form.insert("keyword", "STM32");
        form.insert("currPage", "1");
        form.insert("pageSize", "10");
        let resp = reqwest::Client::new()
            .post("https://pro.lceda.cn/api/eda/product/search")
            .form(&form)
            .send()
            .await;

        match resp {
            Ok(mut r) => {
                if std::fs::create_dir("web_temp").is_ok() {
                    log::info!("Created the dir: web_temp");
                } else {
                    log::warn!("Failed to create the dir: web_temp");
                }
                let mut file = std::fs::OpenOptions::new()
                    .read(false)
                    .write(true)
                    // .create(true)
                    .append(false)
                    .open("web_temp/first_resp.json")
                    .unwrap();
                let enc = r.headers();
                info!("Get the headers: {enc:?}");
                info!("-------------------------------------------");
                info!("Get the status: {:?}", r.status());
                info!("-------------------------------------------");
                info!("Get the http version: {:?}", r.version());
                info!("-------------------------------------------");
                info!("Get the url : {:?}", r.url());
                // let text = r.text_with_charset("utf-8").await.unwrap();
                // file.write_all(text.as_bytes()).unwrap();

                let chunk = r.chunk().await.unwrap();
                if let Some(data) = chunk {
                    let v = data.to_vec();
                    let data = v.as_slice();
                    file.write_all(data).unwrap();
                    log::info!("All content has been wrote to {file:?}");
                } else {
                    log::warn!("Failed to parse and save data to file.");
                }
            }
            Err(e) => {
                log::error!("Error occured: {e:?}");
            }
        }
    }
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct KeywordSearchRoot {
    code: u32,
    success: bool,
    result: KeywordSearchResult,
}
#[allow(non_snake_case)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct KeywordSearchResult {
    total: u32,
    productList: Vec<KeywordSearchListItem>,
}
#[allow(non_snake_case)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct KeywordSearchListItem {
    id: u32,
    code: String,
    image: Option<KeywordChipImages>,
    hasDevice: Option<String>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct KeywordChipImages(String);
impl KeywordChipImages {
    pub fn imgs(&self) -> Vec<String> {
        let urls = self.0.clone();
        let list = urls.split("<$>");
        let mut rst = Vec::new();
        for i in list {
            rst.push(i.to_string());
        }
        rst
    }
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct KeywordDevInfo {
    uuid: String,
    description: String,
    title: String,
    images: Vec<String>,
    attributes: KeywordAttributes,
}
#[allow(non_snake_case)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct KeywordAttributes {
    Datasheet: Option<String>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct DataStr {
    model: Option<String>,
    src: Option<String>,
    _type: Option<String>,
}
