use std::path::PathBuf;

extern crate historian;
use historian::{Historian, Page, Edit, PageRenderer, export_wiki, Linker, Searcher, SearchResult};

#[macro_use] extern crate rocket;
use rocket::serde::json::Json;
use rocket::Responder;
use rocket::response::{Redirect, content::{RawHtml, RawText}};
use rocket::form::Form;
use rocket::http::uri::Origin;
use rocket::fs::NamedFile;
use rocket::State;

extern crate clap;
use clap::Parser;

extern crate grep;

#[macro_use] extern crate toml;

#[derive(Responder)]
enum PageResponder {
    Redirect(Redirect),
    File(NamedFile),
    Page(RawHtml<String>),
    Text(RawText<String>)
}

#[get("/<path..>")]
async fn page<'r>(
    path: PathBuf, origin: &Origin<'r>,
    historian: &State<Historian>, renderer: &State<PageRenderer>
) -> Option<PageResponder> {
    let path_str = path.to_str().unwrap();
    if let Some(resource_path) = renderer.resolve_to_resource(path_str) {
        Some(PageResponder::File(NamedFile::open(resource_path).await.unwrap()))
    } else if let Some(attachment_path) = historian.resolve_to_attachment(path_str) {
        Some(PageResponder::File(NamedFile::open(attachment_path).await.unwrap()))
    } else {
        historian.resolve_to_page(path_str).map(|page| {
            if let Some(query) = origin.query() {
                for (key, value) in query.segments() {
                    if key == "action" {
                        if value == "edit" {
                            return PageResponder::Page(RawHtml(renderer.render_page_template("edit.html", &historian, &page, &toml! {
                                dynamic = true
                            })))
                        } else if value == "history" {
                            return PageResponder::Page(RawHtml(renderer.template("history.html")
                                .with_historian(&historian)
                                .with_page(&page)
                                .insert("revisions", &historian.get_history_of_page(&page))
                                .insert("options", &toml! {
                                    dynamic = true
                                })
                                .render()))
                        }
                    }

                    if key == "revision" {
                        if let Some(revision) = historian.get_revision_by_id(value) {
                            return PageResponder::Page(RawHtml(renderer.template("revision.html")
                                .with_historian(&historian)
                                .with_page(&page)
                                .insert("revision", &revision)
                                .insert("content", &historian.get_page_text_of_revision(&page, &revision).unwrap())
                                .insert("options", &toml! {
                                    dynamic = true
                                })
                                .render()))
                        }
                    }

                    if key == "q" {
                        return PageResponder::Page(RawHtml(renderer.template("search.html")
                            .with_historian(&historian)
                            .with_page(&page)
                            .insert("results", &Searcher::new(&historian).search(&page, value))
                            .insert("options", &toml! {
                                dynamic = true
                            })
                            .render()))
                    }
                }
            }

            if page.is_directory && !origin.path().ends_with("/") {
                PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned() + "/"))
            } else {
                PageResponder::Page(RawHtml(renderer.render_page(&historian, &page, &toml! {
                    dynamic = true
                })))
            }
        }).or_else(|| {
            if let Some(query) = origin.query() {
                for (key, value) in query.segments() {
                    if key == "action" && value == "edit" {
                        return Some(PageResponder::Page(RawHtml(renderer.template("edit.html")
                            .with_historian(&historian)
                            .with_url(&path_str)
                            .insert("page", &historian.start_page(path_str))
                            .insert("content", "")
                            .insert("ancestors", &Vec::<String>::with_capacity(0))
                            .insert("options", &toml! {
                                dynamic = true
                            })
                            .render())))
                    }
                }
                None                
            } else {
                Some(PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned() + "?action=edit")))
            }
        })
    }
}

#[derive(FromForm)]
struct PageAction<'r> {
    content: &'r str,
    author: &'r str,
    summary: &'r str,
    subaction: &'r str
}

#[post("/<path..>", data = "<action>")]
fn action(
    path: PathBuf, origin: &Origin,
    historian: &State<Historian>, renderer: &State<PageRenderer>,
    action: Form<PageAction<'_>>
) -> PageResponder {
    let path_str = path.to_str().unwrap();
    let page = historian.resolve_to_page(path_str).unwrap_or_else(|| historian.start_page(path_str));

    if let Some(query) = origin.query() {
        println!("{:?}", query);
        for (key, value) in query.segments() {
            if key == "action" {
                if value == "edit" {
                    if action.subaction == "preview" {
                        return PageResponder::Page(RawHtml(renderer.template("edit.html")
                            .with_page(&page)
                            .with_historian(&historian)
                            .insert("content", action.content)
                            .insert("subaction", action.subaction)
                            .insert("summary", action.summary)
                            .insert("author", action.author)
                            .insert("options", &toml! {
                                dynamic = true
                            })
                            .render()))
                    } else {
                        historian.submit_edit(&page, &Edit {
                            author: Some(action.author.to_owned()),
                            summary: action.summary.to_owned(),
                            content: action.content.to_owned()
                        });
                        return PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned()))
                    }
                } else if value == "resolvelinks" {
                    let linker = Linker::new(&historian);
                    return PageResponder::Text(RawText(linker.resolve_links_for_edit(&page, &Edit {
                        author: None,
                        summary: action.summary.to_owned(),
                        content: action.content.to_owned()
                    })))
                }
            }
        }
    }

    PageResponder::Page(RawHtml(renderer.render_template("message.html", &historian, &toml! {
        header = "Invalid Action"
        message = "Action was invalid or misunderstood"
    }, &toml! {
        dynamic = true
    })))
}

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Args {
    /// Path to wiki repository
    wiki_path: Option<String>,

    /// Print a tree of the wiki contents.
    #[arg(long)]
    print_tree: bool,
    
    /// Render the wiki to a static website
    #[arg(long)]
    render_to: Option<String>,

    /// Resolve a link to a page on the wiki
    #[arg(long)]
    resolve_link: Option<String>,

    /// Resolve all wiki links in the given page, output the modified text
    #[arg(long)]
    resolve_links: Option<String>,

    /// Path to templates
    #[arg(long)]
    template_path: Option<String>,

    /// Search the wiki
    #[arg(long)]
    search: Option<String>,

    /// Search root
    #[arg(long)]
    search_root: Option<String>
}

fn print_tree(historian: &Historian, page: &Page, prefix: &str) {
    for child in &page.children {
        if let Some(child_page) = historian.resolve_to_page(&child.full_name) {
            println!("{}{}", prefix, child_page.full_name);
            print_tree(historian, &child_page, &format!("{}{}", prefix, prefix));
        }
    }
}

fn print_result(result: &SearchResult) {
    println!("{}", result.page.full_name);
    for line in &result.matches {
        println!("{}", line);
    }
    println!("---");
}

#[rocket::main]
async fn main() {
    let args = Args::parse();

    if let Some(wiki_path) = args.wiki_path {
        let historian = Historian::new(wiki_path);
        
        if args.print_tree {
            print_tree(&historian, &historian.resolve_to_page("").unwrap(), "*");
            return;
        }

        let renderer = if let Some(template_path) = args.template_path {
            PageRenderer::with_template_path(&template_path)
        } else {
            PageRenderer::new()
        };

        let linker = Linker::new(&historian);

        if let Some(resolve_link) = args.resolve_link {
            println!("{}", linker.resolve_link(&resolve_link).unwrap());
            return;
        }

        if let Some(resolve_links) = args.resolve_links {
            let page = historian.resolve_to_page(&resolve_links).expect("failed to find page");
            println!("{}", linker.resolve_links(&page));
            return;
        }

        if let Some(render_to) = args.render_to {
            export_wiki(&historian, &renderer, &render_to);
            return;
        }

        if let Some(search) = args.search {
            let searcher = Searcher::new(&historian);
            let search_root = args.search_root.as_deref().unwrap_or("");
            let page = historian.resolve_to_page(&search_root).expect("failed to find page");
            for result in searcher.search(&page, &search) {
                print_result(&result);
            }
            return;
        }

        rocket::build()
            .manage(historian)
            .manage(renderer)
            .mount("/", routes![page, action])
            .launch()
            .await;
        return;
    } else if let Some(resolve_links) = args.resolve_links {
        if let Some((historian, page)) = Historian::resolve_from_file(&resolve_links) {
            let linker = Linker::new(&historian);
            println!("{}", linker.resolve_links(&page));
            return;
        }

        return;
    }

    return;

}
