use std::time::SystemTime;

use chrono::{DateTime, Local, Locale};
use minijinja::{context, Environment};
use sanitize_filename::sanitize;

use crate::graph::GraphContext;
use crate::model::Key;

pub fn string_to_slug(s: &str) -> String {
    s.to_lowercase()
        .chars()
        .map(|c| if c.is_alphanumeric() { c } else { '-' })
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<_>>()
        .join("-")
}

pub struct KeyFormatContext<'a> {
    pub id: &'a str,
    pub title: String,
    pub parent_title: Option<String>,
    pub parent_key: Option<String>,
    pub source_key: &'a Key,
    pub source_title: Option<String>,
}

pub fn format_target_key<C: GraphContext>(
    key_template: &str,
    key_date_format: &str,
    locale: Locale,
    fmt_ctx: &KeyFormatContext,
    graph_ctx: C,
    now: SystemTime,
) -> Key {
    let now: DateTime<Local> = now.into();
    let formatted_date = now.format_localized(key_date_format, locale).to_string();

    let slug = string_to_slug(&fmt_ctx.title);
    let source_title = fmt_ctx.source_title.clone().unwrap_or_default();

    let relative_key = Environment::new()
        .template_from_str(key_template)
        .expect("correct template")
        .render(context! {
            today => formatted_date,
            id => fmt_ctx.id.to_string(),
            title => sanitize(&fmt_ctx.title),
            slug => slug,
            parent => context! {
                title => fmt_ctx.parent_title.clone().map(|t| sanitize(&t)).unwrap_or_default(),
                slug => fmt_ctx.parent_title.clone().map(|t| string_to_slug(&t)).unwrap_or_default(),
                key => fmt_ctx.parent_key.clone().unwrap_or_default(),
            },
            source => context! {
                key => fmt_ctx.source_key.to_string(),
                file => fmt_ctx.source_key.source(),
                title => source_title.clone(),
                slug => string_to_slug(&source_title),
                path => fmt_ctx.source_key.path().unwrap_or_default(),
            }
        })
        .expect("template to work");

    let base_key = Key::combine(&fmt_ctx.source_key.parent(), &relative_key);
    ensure_unique_key(base_key, graph_ctx)
}

fn ensure_unique_key<C: GraphContext>(base_key: Key, ctx: C) -> Key {
    let mut candidate_key = base_key.clone();
    let mut counter = 1;

    while ctx.get_node_id(&candidate_key).is_some() {
        let suffixed_name = format!("{}-{}", base_key, counter);
        candidate_key = Key::name(&suffixed_name);
        counter += 1;
    }

    candidate_key
}
