use actix_web::{
    HttpResponse,
    HttpRequest,
    http::header::HeaderValue,
    http::header::ContentType
};
use jzon::{JsonValue, object};
use lazy_static::lazy_static;
use include_dir::{include_dir, Dir};
use std::fs;

use crate::include_file;
use crate::router::{userdata, items};
use crate::router::databases::csv::Region;

fn get_config() -> JsonValue {
    let args = crate::get_args();
    object!{
        import: !args.disable_imports,
        export: !args.disable_exports
    }
}

pub fn get_login_token(req: &HttpRequest) -> Option<String> {
    let blank_header = HeaderValue::from_static("");
    let cookies = req.headers().get("Cookie").unwrap_or(&blank_header).to_str().unwrap_or("");
    if cookies.is_empty() {
        return None;
    }
    Some(cookies.split("ew_token=").last().unwrap_or("").split(';').collect::<Vec<_>>()[0].to_string())
}

pub fn error(msg: &str) -> HttpResponse {
    let resp = object!{
        result: "ERR",
        message: msg
    };
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn login(_req: HttpRequest, body: String) -> HttpResponse {
    let body = jzon::parse(&body).unwrap();
    let token = userdata::webui_login(body["uid"].as_i64().unwrap(), &body["password"].to_string());
    
    if token.is_err() {
        return error(&token.unwrap_err());
    }
    
    let resp = object!{
        result: "OK"
    };
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .insert_header(("Set-Cookie", format!("ew_token={}; SameSite=Strict; Path=/; HttpOnly", token.unwrap())))
        .body(jzon::stringify(resp))
}

pub fn import(_req: HttpRequest, body: String) -> HttpResponse {
    if !get_config()["import"].as_bool().unwrap() {
        return error("Importing accounts is disabled on this server.");
    }
    let body = jzon::parse(&body).unwrap();
    
    let result = userdata::webui_import_user(body);
    
    if result.is_err() {
        return error(&result.unwrap_err());
    }
    let result = result.unwrap();
    
    let resp = object!{
        result: "OK",
        uid: result["uid"].clone(),
        migration_token: result["migration_token"].clone()
    };
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn user(req: HttpRequest) -> HttpResponse {
    let token = get_login_token(&req);
    if token.is_none() {
        return error("Not logged in");
    }
    let data = userdata::webui_get_user(&token.unwrap());
    if data.is_none() {
        return error("Expired login");
    }
    let mut data = data.unwrap();
    
    data["userdata"]["user"]["rank"] = items::get_user_rank_data(data["userdata"]["user"]["exp"].as_i64().unwrap())["rank"].clone();
    
    let resp = object!{
        result: "OK",
        data: data
    };
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn start_loginbonus(req: HttpRequest, body: String) -> HttpResponse {
    let token = get_login_token(&req);
    if token.is_none() {
        return error("Not logged in");
    }
    let body = jzon::parse(&body).unwrap();
    let resp = userdata::webui_start_loginbonus(body["bonus_id"].as_i64().unwrap(), &token.unwrap());
    
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn set_time(req: HttpRequest, body: String) -> HttpResponse {
    let token = get_login_token(&req);
    if token.is_none() {
        return error("Not logged in");
    }
    let body = jzon::parse(&body).unwrap();
    let resp = userdata::set_server_time(body["timestamp"].as_i64().unwrap(), &token.unwrap());
    
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn logout(req: HttpRequest) -> HttpResponse {
    let token = get_login_token(&req);
    if token.is_some() {
        userdata::webui_logout(&token.unwrap());
    }
    let resp = object!{
        result: "OK"
    };
    HttpResponse::Found()
        .insert_header(ContentType::json())
        .insert_header(("Set-Cookie", "ew_token=deleted; expires=Thu, 01 Jan 1970 00:00:00 GMT"))
        .insert_header(("Location", "/login.html"))
        .body(jzon::stringify(resp))
}

static WEBUI_ASSETS: Dir<'_> = include_dir!("webui/");

pub fn main(req: HttpRequest) -> HttpResponse {
    let path = if req.path().ends_with("/") { format!("{}index.html", req.path()) } else { req.path().to_string() };
    let mut chars = path.chars();
    chars.next();
    let path = chars.as_str();

    if path == "login.html" {
        let token = get_login_token(&req);
        if token.is_some() {
            let data = userdata::webui_get_user(&token.unwrap());
            if data.is_some() {
                return HttpResponse::Found()
                    .insert_header(("Location", "/account.html"))
                    .body("");
            }
        }
    }

    if let Some(file) = WEBUI_ASSETS.get_file(&path) {
        let body = file.contents();
        let mime = mime_guess::from_path(path).first_or_octet_stream();
        return HttpResponse::Ok()
            .insert_header(ContentType(mime))
            .insert_header(("content-length", body.len()))
            .body(body);
    } else if path.starts_with("webui/images/card-thumbnails") {
        let args = crate::get_args();

        let file_name = path.split("/").last().unwrap_or("");
        let file_path = format!("{}/{}", args.image_asset_path, file_name).replace("//", "/");
        return if args.image_asset_path != "" && let Ok(body) = fs::read(file_path) {
            let mime = mime_guess::from_path(path).first_or_octet_stream();
            HttpResponse::Ok()
                .insert_header(ContentType(mime))
                .insert_header(("content-length", body.len()))
                .body(body)
        } else {
            if args.image_asset_path != "" {
                println!("File '{file_name}' was requested, but no file was found on the disk!");
            }
            HttpResponse::SeeOther()
                .insert_header(("location", format!("https://sif2-api.ethanthesleepy.one{}", req.path())))
                .body("")
        }
    }

    HttpResponse::Found()
        .insert_header(("Location", "/"))
        .body("")
}

pub fn export(req: HttpRequest) -> HttpResponse {
    if !get_config()["export"].as_bool().unwrap() {
        return error("Exporting accounts is disabled on this server.");
    }
    let token = get_login_token(&req);
    if token.is_none() {
        return error("Not logged in");
    }
    let resp = object!{
        result: "OK",
        data: userdata::export_user(&token.unwrap()).unwrap()
    };
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn server_info(_req: HttpRequest) -> HttpResponse {
    let args = crate::get_args();

    let resp = object!{
        result: "OK",
        data: {
            account_import: get_config()["import"].as_bool().unwrap(),
            custom_songs: !crate::router::custom_song::disabled(),
            links: {
                global: args.global_android,
                japan: args.japan_android,
                ios: {
                    global: args.global_ios,
                    japan: args.japan_ios
                },
                assets: args.assets_url
            }
        }
    };
    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}

fn get_query_str(req: &HttpRequest, key: &str, def: &str) -> String {
    let query_str = req.query_string();
    query_str
        .split('&')
        .find(|s| s.starts_with(&format!("{key}=")))
        .and_then(|s| s.split('=').nth(1))
        .unwrap_or(def).to_string()
}

pub fn get_card_info(req: HttpRequest) -> HttpResponse {
    let page = get_query_str(&req, "page", "1").parse::<usize>().unwrap_or(1) - 1;
    let max = get_query_str(&req, "max", "10").parse::<usize>().unwrap_or(10);
    let all = get_query_str(&req, "all", "false");
    let name_query = get_query_str(&req, "query", "");

    let start = page * max;

    let items = crate::router::databases::csv::table(Region::Jp, "card");

    if all == "true" {
        let resp = object!{
            total_pages: 1,
            current: items
        };

        return HttpResponse::Ok()
            .content_type(ContentType::json())
            .body(jzon::stringify(resp));
    }

    let mut filtered_items: Vec<_> = items.members().collect();

    if !name_query.is_empty() {
        let lowercase_query = name_query.to_lowercase();
        filtered_items.retain(|item| {
            item["name"].to_string().to_lowercase().contains(&lowercase_query)
        });
    }
    
    let total_len = filtered_items.len();

    let page_items: Vec<_> = filtered_items
        .into_iter()
        .skip(start)
        .take(max)
        .cloned()
        .collect();

    if page_items.is_empty() {
        return HttpResponse::NotFound()
            .finish();
    }

    let total_pages = (total_len as f64 / max as f64).ceil() as usize;
    let args = crate::get_args();

    let resp = object!{
        total_pages: total_pages,
        current: page_items,
        image_path: args.image_asset_path
    };

    HttpResponse::Ok()
        .content_type(ContentType::json())
        .body(jzon::stringify(resp))
}

pub fn get_music_info(req: HttpRequest) -> HttpResponse {
    let page = get_query_str(&req, "page", "1").parse::<usize>().unwrap_or(1) - 1;
    let max = get_query_str(&req, "max", "10").parse::<usize>().unwrap_or(10);
    let lang = get_query_str(&req, "lang", "JP");

    let start = page * max;

    let items = if lang == "EN" {
        crate::router::databases::csv::table(Region::En, "music")
    } else {
        crate::router::databases::csv::table(Region::Jp, "music")
    };

    let page_items: Vec<_> = items.members()
        .skip(start)
        .take(max)
        .cloned()
        .collect();

    if page_items.is_empty() {
        return HttpResponse::NotFound()
            .finish();
    }

    let total_items = items.len();
    let total_pages = (total_items as f64 / max as f64).ceil() as usize;

    let resp = object!{
        total_pages: total_pages,
        current: page_items
    };

    HttpResponse::Ok()
        .content_type(ContentType::json())
        .body(jzon::stringify(resp))
}

lazy_static! {
    static ref ITEM: JsonValue = jzon::parse(&include_file!("src/router/webui/item.json")).unwrap();
    static ref LOGIN_BONUS: JsonValue = jzon::parse(&include_file!("src/router/webui/login_bonus.json")).unwrap();
}

pub fn list_login_bonus(_req: HttpRequest) -> HttpResponse {
    HttpResponse::Ok()
        .content_type(ContentType::json())
        .body(jzon::stringify(LOGIN_BONUS.clone()))
}

pub fn list_items(_req: HttpRequest) -> HttpResponse {
    HttpResponse::Ok()
        .content_type(ContentType::json())
        .body(jzon::stringify(ITEM.clone()))
}

pub fn cheat(req: HttpRequest, _body: String) -> HttpResponse {
    let token = get_login_token(&req);
    if token.is_none() {
        return error("Not logged in");
    }
    let key = userdata::webui_login_token(&token.unwrap());
    if key.is_none() {
        return error("Not logged in");
    }
    let key = key.unwrap();
    let mut user = userdata::get_acc_home(&key);

    for item in ITEM.entries() {
        let id = item.0.parse::<i32>().unwrap_or(0);
        let data = item.1;
        if id == 0 {
            continue;
        }
        let reward_type = data["reward_type"].as_i32().unwrap();
        let limit = if reward_type == 4 {
            items::LIMIT_COINS
        } else if reward_type == 1 {
            items::LIMIT_PRIMOGEMS
        } else {
            items::LIMIT_ITEMS
        };
        items::gift_item_basic(id, limit, reward_type, "You have cheated. Here are \"gifts\".", &mut user);
    }

    userdata::save_acc_home(&key, user);

    let resp = object!{
        result: "OK"
    };

    HttpResponse::Ok()
        .insert_header(ContentType::json())
        .body(jzon::stringify(resp))
}
