#![feature(let_chains)]

use std::collections::HashSet;

use serde::{ Deserialize, Serialize };
use serde_json::json;
use wasm_bindgen::{ prelude::wasm_bindgen, JsValue };

use reqwest::{ Client, Url, multipart };


#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace=console)]
    fn log (s: &str);
}

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(catch, js_namespace = ["chrome", "storage", "local"], js_name = set)]
    async fn storage_set (data: &JsValue) -> Result<JsValue, JsValue>;

    #[wasm_bindgen(catch, js_namespace = ["chrome", "storage", "local"], js_name = get)]
    async fn storage_get (data: &JsValue) -> Result<JsValue, JsValue>;
}



#[wasm_bindgen]
pub fn post (link: &str) {
// @TODO
}

#[allow(dead_code)]
#[derive(Serialize, Deserialize, Debug)]
struct LocalStorage {
    pub token: String,
    pub group: String,

    #[serde(default)]
    pub header: String,
    #[serde(default = "default_domain")]
    pub domain: String,
    #[serde(default)]
    pub blacklist: String,

    #[serde(skip)]
    pub delay: u8,

    #[serde(default)]
    pub posts: bool,
    #[serde(default)]
    pub likes: bool,

    #[serde(default)]
    pub private: bool,
    #[serde(default)]
    pub screenshot: bool,
}


#[derive(Serialize, Deserialize, Debug)]
struct LikedTweet {
    pub id: String,
    pub by: TwitterUser,
    pub me: Option<TwitterUser>,

    #[serde(default)]
    pub is_private: bool,
}


#[derive(Serialize, Deserialize, Debug)]
pub struct TwitterUser {
    pub name: Option<String>,
    pub username: String,
}

impl TwitterUser {
    fn format (&self) -> String {
        match &self.name {
            Some(name) => format!("{name} ({})", self.username.trim_start_matches("@")),
            None => format!("{}", self.username)
        }
    }
}

pub fn default_domain () -> String {
    String::from("x.com")
}

#[allow(non_upper_case_globals)]
pub fn escape (mut line: String) -> String {
    const chars: [char; 21] = [ '\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '<', '&', '#', '+', '-', '=', '|', '{', '}', '.', '!' ];
    for char in chars {
        line = line.replace(char, &format!("\\{}", char));
    }
    line
}

pub fn format_telegram_api_url (token: &str) -> String {
    format!("https://api.telegram.org/bot{token}")
}

pub fn format_tweet_url (domain: &str, username: &str, id: &str) -> String {
    format!("https://{domain}/{username}/status/{id}")
}

pub fn format_caption (url: &str, header: &str, by: &TwitterUser, me: &Option<TwitterUser>) -> String {
    let by = by.format();
    let me = me.as_ref().map(TwitterUser::format);

    // @TODO: Edge case of where header is set but we can't retrieve Me

    let header = escape(header
        .replace("{me}", &me.unwrap_or_default())
        .replace("{notme}", &by));

    format!("{}{header}{}{}",
        if !header.is_empty() { "`" } else { Default::default() },
        if !header.is_empty() { "`\r\n\r\n" } else { Default::default() },
        escape(url.to_owned())
        )
}


#[allow(unused_macros)]
macro_rules! log {
    ($data: expr) => {
        log(&format!("{:?}", $data))
    };
}

#[wasm_bindgen]
pub async fn like (tweet: JsValue, image: Option<Vec<u8>>) -> Option<u8> {
    let store = storage_get(&JsValue::null()).await.ok()?;
    let store: LocalStorage = serde_wasm_bindgen::from_value(store).ok()?;

    if !store.likes { return None; }

    let blacklist = store.blacklist.split(",").map(str::trim).collect::<HashSet<&str>>();

    let tweet: LikedTweet = serde_wasm_bindgen::from_value(tweet).ok()?;

    if !store.private && tweet.is_private { return None; }
    if blacklist.contains(tweet.by.username.as_str()) { return None; }

    let domain = if store.domain.is_empty() { default_domain() } else { store.domain };
    let group = store.group;
    let token = store.token;
    let url = format_tweet_url(&domain, &tweet.by.username, &tweet.id);

    let caption = format_caption(&url, &store.header, &tweet.by, &tweet.me);

    let client = Client::new();

    let link_preview_options = json!({
        "is_disabled": false,
        "url": url,
        "prefer_large_media": true,
        "show_above_text": false,
    }).to_string();

    let (form, endpoint) = if let Some(image) = image && store.screenshot {
        let endpoint = format!("{}/sendPhoto", format_telegram_api_url(&token));
        let part = multipart::Part::bytes(image).file_name("test.png").mime_str("image/png").ok()?;


        (reqwest::multipart::Form::new()
            .text("chat_id", group)
            .text("caption", caption)
            .text("parse_mode", "MarkdownV2")
            .text("link_preview_options", link_preview_options)
            .part("photo", part),
        endpoint)

    } else {
        let endpoint = format!("{}/sendMessage", format_telegram_api_url(&token));

        (reqwest::multipart::Form::new()
            .text("chat_id", group)
            .text("link_preview_options", link_preview_options)
            .text("parse_mode", "MarkdownV2")
            .text("text", caption),
        endpoint)
    };

    let endpoint = Url::parse(&endpoint).ok()?;
    let _resp = client
        .post(endpoint)
        .multipart(form)
        .send().await.ok()?;

    log!(_resp);

    Some(0)

}
