use askama::Template;
use axum::http::{HeaderMap, StatusCode};
use axum::response::{Html, IntoResponse, Redirect, Response};
use axum::Json;

use crate::db;

use super::views::ErrorTemplate;

/// Return a human-friendly status label (e.g. "In progress" instead of
/// "in_progress").
pub(super) fn friendly_status(raw: &str) -> &'static str {
    match raw {
        "open" => "Open",
        "in_progress" => "In progress",
        "closed" => "Closed",
        _ => "Open",
    }
}

/// Format an ISO 8601 timestamp into a human-friendly form (e.g. "15 Mar 2026")
/// for the noscript fallback. Returns the original string unchanged on parse
/// failure so the page still renders something sensible.
pub(super) fn friendly_date(iso: &str) -> String {
    chrono::NaiveDateTime::parse_from_str(iso, "%Y-%m-%dT%H:%M:%SZ")
        .map(|dt| dt.format("%-d %b %Y %H:%M").to_string())
        .unwrap_or_else(|_| iso.to_string())
}

/// Render a markdown string to sanitised HTML.
///
/// Uses pulldown-cmark for parsing and ammonia for sanitisation so that
/// untrusted user input can be displayed safely. Images are stripped
/// entirely — only structural/inline markup is allowed through.
pub(super) fn render_markdown(src: &str) -> String {
    use pulldown_cmark::{html::push_html, Parser};

    let parser = Parser::new(src);
    let mut raw_html = String::new();
    push_html(&mut raw_html, parser);

    ammonia::Builder::default()
        .rm_tags(&["img"])
        .clean(&raw_html)
        .to_string()
}

pub(super) fn render(tmpl: impl Template) -> Response {
    match tmpl.render() {
        Ok(html) => Html(html).into_response(),
        Err(e) => error_response(500, &format!("template render failed: {e}"), &[]),
    }
}

pub(super) fn error_response(code: u16, msg: &str, all_projects: &[String]) -> Response {
    let body = ErrorTemplate {
        all_projects: all_projects.to_vec(),
        active_project: None,
        status_code: code,
        message: msg.to_string(),
    };
    let html = body
        .render()
        .unwrap_or_else(|_| format!("<h1>{code}</h1><p>{msg}</p>"));
    let status = StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
    (status, Html(html)).into_response()
}

pub(super) fn list_projects_safe(root: &std::path::Path) -> Vec<String> {
    db::list_projects_in(root).unwrap_or_default()
}

/// Returns true when the client prefers a JSON response.
fn wants_json(headers: &HeaderMap) -> bool {
    headers
        .get("accept")
        .and_then(|v| v.to_str().ok())
        .map(|v| v.contains("application/json"))
        .unwrap_or(false)
}

/// Build a redirect-or-JSON response after a successful mutation.
pub(super) fn mutation_response(
    headers: &HeaderMap,
    redirect_to: &str,
    json_body: serde_json::Value,
) -> Response {
    if wants_json(headers) {
        Json(json_body).into_response()
    } else {
        Redirect::to(redirect_to).into_response()
    }
}

/// Build an error response appropriate for the caller (JSON or HTML).
pub(super) fn mutation_error(headers: &HeaderMap, code: u16, err: &anyhow::Error) -> Response {
    let status = StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
    if wants_json(headers) {
        (status, Json(serde_json::json!({"error": err.to_string()}))).into_response()
    } else {
        error_response(code, &err.to_string(), &[])
    }
}
