use fastly::http::{header, StatusCode};
use fastly::{geo, ConfigStore, Error, Request, Response};
use log::LevelFilter::Info;
use regex::Regex;

const BACKEND_NAME: &str = "origin_0";

#[fastly::main]
fn main(mut req: Request) -> Result<Response, Error> {
    log_fastly::init_simple("mylogs", Info);

    let mut latitude = 0.0f64;
    let mut longtitude = 0.0f64;

    if let Some(geo) = req.get_client_ip_addr().and_then(geo::geo_lookup) {
        // Get country codo
        let country_code = geo.country_code();

        // Get country's region info from edge dictionary ("ConfigStore")
        let dict = ConfigStore::open("region_defs");
        let region = if let Some(region) = dict.get(country_code) {
            region
        } else if let Some(region) = dict.get("_default") {
            region
        } else {
            "blocked".to_owned()
        };

        log::info!("Country: {}, Region: {}", country_code, region);

        // Block the request if the region is set as "blocked"
        if region == "blocked" {
            return Ok(
                Response::from_status(StatusCode::FORBIDDEN).with_body_text_plain(
                    "Sorry, our service is currently not available in your region\n",
                ),
            );
        }

        // Set country/region/cotinent information on request header
        let continent = geo.continent().as_code();
        req.set_header("client-geo-country", country_code);
        req.set_header("client-geo-region", region);
        req.set_header("client-geo-continent", continent);

        log::info!("Continent: {}", continent);

        // Get latitude and longtitude from Fastly Geo API
        latitude = geo.latitude();
        longtitude = geo.longitude();
    }

    // Get latitude and longtitude from cookie,
    // if info exists in cookie, will overwrite what we got from Fastly GEO API
    if let Some((lat, lng)) = get_cookie_geo(&req) {
        latitude = lat;
        longtitude = lng;
    }

    // Set latitude and longitude info to request header
    req.set_header(
        "client-geo-latlng",
        format!("{:.1}, {:.1}", latitude, longtitude),
    );

    log::info!("Lat:{}, Lng: {}", latitude, longtitude);

    // Send request to backend
    Ok(req.send(BACKEND_NAME)?)
}

/// Get latitude & longtitude from cookie
fn get_cookie_geo(req: &Request) -> Option<(f64, f64)> {
    let cookie_val: &str = req.get_header(header::COOKIE)?.to_str().ok()?;

    // we split at ";" not "; ", in case the cookie is ending with ";"
    let geo_string = cookie_val.split(';').find_map(|kv| {
        let index = kv.find('=')?;
        let (key, value) = kv.split_at(index);
        if key.trim() != "client-geo-latlng" {
            return None;
        }

        // remove the "="
        let value = value[1..].to_string();
        Some(value)
    })?;

    let geo = urlencoding::decode(&geo_string).ok()?;
    let geo_cap = Regex::new(r"^([0-9\.]+),\s*([0-9\.]+)$")
        .ok()?
        .captures(&geo)?;

    let lat: f64 = geo_cap.get(1)?.as_str().parse().ok()?;
    let long: f64 = geo_cap.get(2)?.as_str().parse().ok()?;

    Some((lat, long))
}