Documentation
use std::io::{self, Read};
use std::path::PathBuf;

use chrono::Local;
use minijinja::{context, Environment};
use rand::distr::Alphanumeric;
use rand::Rng;

use diwe::config::{Configuration, NoteTemplate, DEFAULT_KEY_DATE_FORMAT};
use liwe::locale::get_locale;
use liwe::model::{strip_doc_extension, Key};

#[derive(Debug, Clone, Default, clap::ValueEnum)]
pub enum IfExists {
    #[default]
    Suffix,
    Override,
    Skip,
    Fail,
}

fn get_default_template() -> NoteTemplate {
    NoteTemplate {
        key_template: "{{slug}}".to_string(),
        document_template: "# {{title}}\n\n{{content}}".to_string(),
    }
}

pub struct DocumentCreator<'a> {
    config: &'a Configuration,
    library_path: PathBuf,
}

pub struct CreateOptions {
    pub title: String,
    pub template_name: Option<String>,
    pub content: Option<String>,
    pub key: Option<String>,
    pub if_exists: IfExists,
}

pub struct CreatedDocument {
    pub path: PathBuf,
}

impl<'a> DocumentCreator<'a> {
    pub fn new(config: &'a Configuration, library_path: PathBuf) -> Self {
        Self {
            config,
            library_path,
        }
    }

    fn find_available_key(&self, base_key: &Key) -> Key {
        let mut candidate_key = base_key.clone();
        let mut counter = 1;

        while self
            .library_path
            .join(candidate_key.to_path(self.config.format))
            .exists()
        {
            let suffixed_name = format!("{}-{}", base_key, counter);
            candidate_key = Key::name(&suffixed_name);
            counter += 1;
        }

        candidate_key
    }

    pub fn create(&self, options: CreateOptions) -> Result<Option<CreatedDocument>, String> {
        let template_name = options
            .template_name
            .or_else(|| self.config.library.default_template.clone())
            .unwrap_or_else(|| "default".to_string());

        let fallback_template = get_default_template();
        let template = self
            .config
            .templates
            .get(&template_name)
            .or_else(|| {
                if template_name == "default" {
                    Some(&fallback_template)
                } else {
                    None
                }
            })
            .ok_or_else(|| format!("Template '{}' not found in configuration", template_name))?;

        let content = options.content.unwrap_or_default();

        let key_date_format = self
            .config
            .library
            .date_format
            .clone()
            .unwrap_or_else(|| DEFAULT_KEY_DATE_FORMAT.to_string());

        let format_options = self.config.format_options();

        let content_date_format = format_options
            .date_format()
            .unwrap_or("%b %d, %Y")
            .to_string();

        let key_time_format = self
            .config
            .library
            .time_format
            .clone()
            .unwrap_or_else(|| key_date_format.clone());

        let content_time_format = format_options
            .time_format()
            .map(|format| format.to_string())
            .unwrap_or_else(|| content_date_format.clone());

        let key_locale = get_locale(self.config.library.locale.as_deref());
        let content_locale = get_locale(format_options.locale());

        let now = Local::now();
        let key_today = now
            .format_localized(&key_date_format, key_locale)
            .to_string();
        let content_today = now
            .format_localized(&content_date_format, content_locale)
            .to_string();
        let key_now = now
            .format_localized(&key_time_format, key_locale)
            .to_string();
        let content_now = now
            .format_localized(&content_time_format, content_locale)
            .to_string();

        let slug = string_to_slug(&options.title);
        let id = generate_random_id();

        let relative_key = match &options.key {
            Some(key) => {
                if strip_doc_extension(key) != key.as_str() {
                    return Err(format!("Key '{}' must not include a file extension", key));
                }
                key.clone()
            }
            None => render_template(
                &template.key_template,
                &options.title,
                &slug,
                &key_today,
                &key_now,
                &id,
                &content,
            )?,
        };

        let document_content = render_template(
            &template.document_template,
            &options.title,
            &slug,
            &content_today,
            &content_now,
            &id,
            &content,
        )?;

        let base_key = Key::name(&relative_key);
        if base_key.as_str().is_empty() {
            return Err(if options.key.is_some() {
                "Provided key is empty.".to_string()
            } else {
                "Generated key is empty. Use a non-empty title.".to_string()
            });
        }
        let path_str = base_key.to_path(self.config.format);
        let filename_len = std::path::Path::new(&path_str)
            .file_name()
            .map(|f| f.len())
            .unwrap_or(path_str.len());
        if filename_len > 255 {
            return Err(format!(
                "Generated filename is too long ({} bytes, max 255). Use a shorter title.",
                filename_len
            ));
        }
        let file_path = self.library_path.join(base_key.to_path(self.config.format));
        let file_exists = file_path.exists();

        let final_key = match options.if_exists {
            IfExists::Skip if file_exists => return Ok(None),
            IfExists::Fail if file_exists => {
                return Err(format!("Document '{}' already exists", base_key))
            }
            IfExists::Suffix => self.find_available_key(&base_key),
            IfExists::Override | IfExists::Skip | IfExists::Fail => base_key,
        };

        let file_path = self
            .library_path
            .join(final_key.to_path(self.config.format));

        if let Some(parent) = file_path.parent() {
            if !parent.exists() {
                std::fs::create_dir_all(parent)
                    .map_err(|e| format!("Failed to create parent directories: {}", e))?;
            }
        }

        std::fs::write(&file_path, document_content)
            .map_err(|e| format!("Failed to write file: {}", e))?;

        let absolute_path = file_path.canonicalize().unwrap_or(file_path);

        Ok(Some(CreatedDocument {
            path: absolute_path,
        }))
    }
}

pub fn read_stdin_if_available() -> String {
    use std::io::IsTerminal;

    if std::io::stdin().is_terminal() {
        return String::new();
    }

    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer).unwrap_or_default();
    buffer
}

fn render_template(
    template_str: &str,
    title: &str,
    slug: &str,
    today: &str,
    now: &str,
    id: &str,
    content: &str,
) -> Result<String, String> {
    Environment::new()
        .template_from_str(template_str)
        .map_err(|e| format!("Invalid template syntax: {}", e))?
        .render(context! {
            title => title,
            slug => slug,
            today => today,
            now => now,
            id => id,
            content => content,
        })
        .map_err(|e| format!("Template rendering failed: {}", e))
}

fn generate_random_id() -> String {
    rand::rng()
        .sample_iter(&Alphanumeric)
        .take(8)
        .map(char::from)
        .collect::<String>()
        .to_lowercase()
}

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("-")
}