use crate::git_tracker::get_git_version;
use crate::post_manager::PostManager;
use std::collections::HashMap;
use std::fs;
use std::future::Future;
use std::pin::Pin;

pub async fn render_template(
    template: &str,
    post_manager: &PostManager,
    current_post: Option<&str>,
) -> Result<String, String> {
    parse_template(template, post_manager, current_post, 0).await
}

fn parse_template<'a>(
    template: &'a str,
    post_manager: &'a PostManager,
    current_post: Option<&'a str>,
    depth: usize,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send + 'a>> {
    Box::pin(async move { parse_template_impl(template, post_manager, current_post, depth).await })
}

async fn parse_template_impl(
    template: &str,
    post_manager: &PostManager,
    current_post: Option<&str>,
    depth: usize,
) -> Result<String, String> {
    const MAX_DEPTH: usize = 10;
    if depth > MAX_DEPTH {
        return Err("Too many nested includes (max 10)".to_string());
    }

    let mut result = String::new();
    let chars: Vec<char> = template.chars().collect();
    let mut i = 0;

    while i < chars.len() {
        // Check for $< tag start
        if i + 1 < chars.len() && chars[i] == '$' && chars[i + 1] == '<' {
            // Try to parse tag
            if let Some((tag_name, attrs, end_pos)) = parse_tag(&chars, i) {
                // Process tag immediately based on type
                let content =
                    process_tag(&tag_name, &attrs, post_manager, current_post, depth).await?;
                result.push_str(&content);
                i = end_pos;
                continue;
            }
        }

        // Regular character
        result.push(chars[i]);
        i += 1;
    }

    Ok(result)
}

fn parse_tag(chars: &[char], start: usize) -> Option<(String, HashMap<String, String>, usize)> {
    let mut i = start + 2; // Skip $<

    // Skip whitespace
    while i < chars.len() && chars[i].is_whitespace() {
        i += 1;
    }

    // Parse tag name
    let mut tag_name = String::new();
    while i < chars.len() && !chars[i].is_whitespace() && chars[i] != '/' && chars[i] != '>' {
        tag_name.push(chars[i]);
        i += 1;
    }

    if tag_name.is_empty() {
        return None;
    }

    // Parse attributes
    let mut attrs = HashMap::new();

    loop {
        // Skip whitespace
        while i < chars.len() && chars[i].is_whitespace() {
            i += 1;
        }

        // Check for end of tag
        if i >= chars.len() {
            return None;
        }

        if chars[i] == '/' {
            i += 1;
            if i < chars.len() && chars[i] == '>' {
                return Some((tag_name, attrs, i + 1));
            }
            return None;
        }

        if chars[i] == '>' {
            return Some((tag_name, attrs, i + 1));
        }

        // Parse attribute name
        let mut attr_name = String::new();
        while i < chars.len() && chars[i] != '=' && !chars[i].is_whitespace() {
            attr_name.push(chars[i]);
            i += 1;
        }

        if attr_name.is_empty() {
            return None;
        }

        // Skip whitespace
        while i < chars.len() && chars[i].is_whitespace() {
            i += 1;
        }

        // Expect =
        if i >= chars.len() || chars[i] != '=' {
            return None;
        }
        i += 1;

        // Skip whitespace
        while i < chars.len() && chars[i].is_whitespace() {
            i += 1;
        }

        // Parse attribute value (quoted)
        if i >= chars.len() || chars[i] != '"' {
            return None;
        }
        i += 1; // Skip opening quote

        let mut attr_value = String::new();
        while i < chars.len() && chars[i] != '"' {
            attr_value.push(chars[i]);
            i += 1;
        }

        if i >= chars.len() {
            return None;
        }
        i += 1; // Skip closing quote

        attrs.insert(attr_name, attr_value);
    }
}

async fn process_tag(
    tag_name: &str,
    attrs: &HashMap<String, String>,
    post_manager: &PostManager,
    current_post: Option<&str>,
    depth: usize,
) -> Result<String, String> {
    match tag_name {
        "include" => {
            let src = attrs
                .get("src")
                .ok_or_else(|| "include tag missing 'src' attribute".to_string())?;

            let include_path = format!("templates/{}", src);
            let content = fs::read_to_string(&include_path)
                .map_err(|e| format!("Failed to read include file '{}': {}", src, e))?;

            // Recursively parse the included content
            parse_template(&content, post_manager, current_post, depth + 1).await
        }

        "post-html" => {
            let post_name = current_post
                .ok_or_else(|| "post-html tag used outside of post context".to_string())?;

            let post = post_manager
                .get_post(post_name)
                .await
                .ok_or_else(|| format!("Post '{}' not found", post_name))?;

            Ok(post.html_content.clone())
        }

        "title" => {
            if let Some(post_name) = current_post {
                let post = post_manager
                    .get_post(post_name)
                    .await
                    .ok_or_else(|| format!("Post '{}' not found", post_name))?;

                let none: String = "".to_string();
                let pre = attrs.get("pre").unwrap_or(&none);
                let postfix = attrs.get("post").unwrap_or(&none);

                Ok(format!("{}{}{}", pre, post.title, postfix))
            } else {
                let def = attrs
                    .get("default")
                    .and_then(|s| Some(s.clone()))
                    .unwrap_or("<title not set>".to_string());
                Ok(def.to_string())
            }
        }

        "updated" => {
            // Insert the last updated time of the current post (or most recent post)
            let post_ts = if let Some(p) = current_post {
                post_manager
                    .get_post(p)
                    .await
                    .ok_or_else(|| format!("Post '{}' not found", p))?
                    .modified_at
            } else {
                post_manager.get_update_timestamp().await?
            };

            Ok(post_ts.format("%Y-%m-%d").to_string())
        }

        "version" => {
            let git_dir = post_manager.get_root_dir().clone();
            let git_version = get_git_version(&git_dir)?;
            Ok(format!("{}/{}", git_version.branch, git_version.commit))
        }

        "post-list" => {
            let limit = attrs.get("limit").and_then(|s| s.parse::<usize>().ok());

            let posts = if let Some(l) = limit {
                post_manager.get_posts_limited(l).await
            } else {
                post_manager.get_all_posts().await
            };

            let mut list_html = String::from("<ul class=\"post-list\">\n");
            for post in posts {
                list_html.push_str(&format!(
                    "  <li><a href=\"/p/{}\">{}</a> <span class=\"date\">{}</span></li>\n",
                    post.name,
                    post.title,
                    post.created_at.format("%Y-%m-%d")
                ));
            }
            list_html.push_str("</ul>");

            Ok(list_html)
        }

        _ => Err(format!("Unknown tag: {}", tag_name)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_tag_post_list() {
        let chars: Vec<char> = "$<post-list/>".chars().collect();
        let result = parse_tag(&chars, 0);
        assert!(result.is_some());
        let (tag_name, attrs, _) = result.unwrap();
        assert_eq!(tag_name, "post-list");
        assert!(attrs.is_empty());

        let chars: Vec<char> = "$<post-list />".chars().collect();
        let result = parse_tag(&chars, 0);
        assert!(result.is_some());

        let chars: Vec<char> = r#"$<post-list limit="50"/>"#.chars().collect();
        let result = parse_tag(&chars, 0);
        assert!(result.is_some());
        let (tag_name, attrs, _) = result.unwrap();
        assert_eq!(tag_name, "post-list");
        assert_eq!(attrs.get("limit"), Some(&"50".to_string()));
    }

    #[test]
    fn test_parse_tag_include() {
        let chars: Vec<char> = r#"$<include src="header.html"/>"#.chars().collect();
        let result = parse_tag(&chars, 0);
        assert!(result.is_some());
        let (tag_name, attrs, _) = result.unwrap();
        assert_eq!(tag_name, "include");
        assert_eq!(attrs.get("src"), Some(&"header.html".to_string()));

        let chars: Vec<char> = r#"$<include src="header.html" />"#.chars().collect();
        let result = parse_tag(&chars, 0);
        assert!(result.is_some());
        let (tag_name, attrs, _) = result.unwrap();
        assert_eq!(tag_name, "include");
        assert_eq!(attrs.get("src"), Some(&"header.html".to_string()));
    }

    #[test]
    fn test_parse_tag_post_html() {
        let chars: Vec<char> = "$<post-html/>".chars().collect();
        let result = parse_tag(&chars, 0);
        assert!(result.is_some());
        let (tag_name, attrs, _) = result.unwrap();
        assert_eq!(tag_name, "post-html");
        assert!(attrs.is_empty());
    }
}
