use std::collections::HashMap;

use crate::{get_client, StdError};
use regex::Regex;
use serde::Deserialize;
use std::fmt::Display;
use tracing::{event, instrument, Level};
use url::Url;

// Filter a filename string for filenames
fn filter_filename(filename: &str) -> bool {
    let re = Regex::new("ISO.7010.[EWMPF][0-9]{3}.*").unwrap();
    re.is_match(filename)
}

// Scrape all images from the wikipedia page, returning a vec of title, filename pairs
pub fn scrape_web() -> StdError<Vec<(String, String)>> {
    event!(Level::INFO, "Scraping the wikipedia page for things");
    // Parse CSS selectors to scrape elements
    let gallerybox_sel = scraper::Selector::parse(".mw-body-content li.gallerybox")
        .map_err(|e| format!("{:?}", e))?;
    let link_sel = scraper::Selector::parse("a.image").map_err(|e| format!("{:?}", e))?;
    let title_sel = scraper::Selector::parse(".gallerytext p").map_err(|e| format!("{:?}", e))?;

    // Fetch stuff!
    let client = get_client(None)?;
    event!(Level::INFO, "Fetching wiki page");
    let txt = client
        .get("https://en.wikipedia.org/wiki/ISO_7010")
        .send()?
        .text()?;
    let page = scraper::Html::parse_document(txt.as_str());
    return Ok(page
        .select(&gallerybox_sel)
        .map(|a| {
            let link = a
                .select(&link_sel)
                .next()
                .unwrap()
                .value()
                .attr("href")
                .unwrap()
                .to_owned();
            let title = a
                .select(&title_sel)
                .next()
                .unwrap()
                .text()
                .collect::<String>()
                .trim()
                .to_owned();
            (title, link)
        })
        // Filter for filenames that look like ISO diagrams
        .filter(|tup| filter_filename(&tup.1))
        // Extract the file name only (.e.g `File:ISO_7010_X000.svg`)
        .filter_map(|(title, link)| {
            link.split('/')
                .next_back()
                .map(|end| (title, end.to_owned()))
        })
        .collect::<Vec<_>>());
}

#[instrument]
pub fn wiki_query_url(params: Vec<(&str, &str)>) -> StdError<Url> {
    let mut url = Url::parse("https://commons.wikimedia.org/w/api.php?action=query&format=json")?;
    url.query_pairs_mut().extend_pairs(params);
    Ok(url)
}
// https://commons.wikimedia.org/w/api.php?action=query&format=json&list=categorymembers&cmtitle=Category:ISO_7010_safety_signs_(vector_drawings)&cmlimit=2

#[instrument]
pub fn get_files_in_category(category: &str) -> StdError<Vec<String>> {
    let client = get_client(None)?;
    let url = wiki_query_url(
        [
            ("list", "categorymembers"),
            ("cmtitle", category),
            ("cmtype", "file"),
            ("cmlimit", "max"),
        ]
        .into(),
    )?;
    let data = client.get(url).send()?.json::<serde_json::Value>()?;
    if data.get("continue").is_some() {
        // There are more results than are contained in one response, so now you need to implement
        // pagination. Have fun!
        panic!("Wikimedia query result is paginated!");
    }
    Ok(data["query"]["categorymembers"]
        .as_array()
        .unwrap()
        .iter()
        .filter_map(|m| Some(m.as_object().unwrap()["title"].as_str()?.replace(' ', "_")))
        .collect())
}

#[derive(Debug)]
pub struct FileMeta {
    pub url: url::Url,
    pub name: String,
    pub html_url: url::Url,
    pub author: String,
    pub attribution_required: String,
    pub license_short_name: String,
    pub license_url: Option<url::Url>,
}

impl Display for FileMeta {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "FileMeta{{url: {}, name: {}, html_url: {}, author: {}, attribution_required: {}, license_short_name: {}, license_url: {}}}",
            self.url, self.name, self.html_url, self.author, self.attribution_required, self.license_short_name,
            self.license_url.clone().map_or("None".to_owned(), |u| u.to_string()) // Ew. 
        )
    }
}

// Partial representation of the data returned from a MediaWiki imageinfo query
#[derive(Deserialize)]
struct Query {
    query: QueryInner,
}

#[derive(Deserialize, Debug)]
struct QueryInner {
    pages: HashMap<String, Page>,
}

#[derive(Deserialize, Debug)]
struct Page {
    imageinfo: Vec<ImageInfo>,
}

#[derive(Deserialize, Debug)]
struct ImageInfo {
    user: String,
    url: url::Url,
    descriptionurl: url::Url,
    extmetadata: ExtMeta,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct ExtMeta {
    object_name: ExtMetaItem<String>,
    license_short_name: ExtMetaItem<String>,
    attribution_required: ExtMetaItem<String>,
    license_url: Option<ExtMetaItem<url::Url>>,
}

#[derive(Deserialize, Debug, Clone)]
struct ExtMetaItem<T> {
    value: T,
}

pub fn get_file_metadata(files: Vec<&str>) -> StdError<Vec<FileMeta>> {
    let client = get_client(None)?;
    // Api only lets us do 50 files in one request
    Ok(files
        .chunks(50)
        .flat_map(|files_chunk| {
            let url = wiki_query_url(
                [
                    ("titles", files_chunk.join("|").as_ref()),
                    ("prop", "imageinfo"),
                    (
                        "iiprop",
                        "timestamp|url|size|mime|mediatype|extmetadata|user",
                    ),
                    // Get metadata for as many revisions of the file as we are allowed. We're unlikely to encounter a file with >500 revisions.
                    ("iilimit", "500"),
                    (
                        "iiextmetadatafilter",
                        "ObjectName|LicenseShortName|AttributionRequired|LicenseUrl",
                    ),
                ]
                .into(),
            )
            .unwrap();
            let data = client.get(url).send().unwrap().json::<Query>().unwrap();

            data.query
                .pages
                .values()
                .map(|page| {
                    let latest = page.imageinfo.first().unwrap();
                    let oldest = page.imageinfo.last().unwrap();
                    FileMeta {
                        url: latest.url.clone(),
                        name: latest.extmetadata.object_name.value.clone(),
                        html_url: latest.descriptionurl.clone(),
                        author: oldest.user.clone(),
                        license_short_name: latest.extmetadata.license_short_name.value.clone(),
                        license_url: latest.extmetadata.license_url.clone().map(|i| i.value),
                        attribution_required: latest.extmetadata.attribution_required.value.clone(),
                    }
                })
                .collect::<Vec<_>>()
        })
        .collect())
}
