use crate::config::ColumnConfig;
use crate::db;
use crate::modes::server::AppState;
use anyhow::Result;
use axum::{
    extract::{Path, Query, State},
    response::{Html, Response},
};
use html_escape::{encode_double_quoted_attribute, encode_text};
use log::debug;
use rusqlite::Connection;
use serde::Deserialize;
use std::collections::HashMap;

/// Escape text content for safe HTML insertion.
fn esc(s: &str) -> String {
    encode_text(s).to_string()
}

/// Escape attribute values for safe HTML attribute insertion.
fn esc_attr(s: &str) -> String {
    encode_double_quoted_attribute(s).to_string()
}

#[derive(Deserialize)]
/// Query parameters for the item list endpoint.
///
/// This struct defines the query parameters used to filter, sort, and paginate
/// the list of items displayed on the main page.
///
/// # Fields
///
/// * `sort` - Sorting order, defaults to "newest".
/// * `tags` - Optional comma-separated list of tags to filter by.
/// * `count` - Number of items per page, defaults to 1000.
/// * `start` - Starting index for pagination, defaults to 0.
pub struct ListQueryParams {
    #[serde(default = "default_sort")]
    sort: String,
    #[serde(default)]
    tags: Option<String>,
    #[serde(default = "default_count")]
    count: usize,
    #[serde(default)]
    start: usize,
}

fn default_sort() -> String {
    "newest".to_string()
}

/// Provides the default sorting order for item lists.
///
/// # Returns
///
/// A string representing the default sort order: "newest".
fn default_count() -> usize {
    1000
}

/// Adds the web page routes to the Axum router.
///
/// This function configures the routes for the web interface, including the
/// main item list, individual item details, and static CSS styles.
///
/// # Arguments
///
/// * `app` - The existing Axum router with application state.
///
/// # Returns
///
/// The updated router with web routes added.
///
/// # Examples
///
/// ```ignore
/// let app = pages::add_routes(axum::Router::new());
/// ```
pub fn add_routes(app: axum::Router<AppState>) -> axum::Router<AppState> {
    app.route("/", axum::routing::get(list_items))
        .route("/item/{item_id}", axum::routing::get(show_item))
        .route("/style.css", axum::routing::get(style_css))
}

async fn list_items(
    State(state): State<AppState>,
    Query(params): Query<ListQueryParams>,
) -> Result<Response, Html<String>> {
    let conn = state.db.lock().await;
    let settings = &state.settings;

    let result = build_item_list(&conn, &params, &settings.list_format);

    match result {
        Ok(html) => {
            // Build response with explicit Content-Length
            let response = Response::builder()
                .header("content-type", "text/html")
                .header("content-length", html.len().to_string())
                .body(axum::body::Body::from(html))
                .map_err(|_| Html("<html><body>Internal Server Error</body></html>".to_string()))?;
            Ok(response)
        }
        Err(_e) => Err(Html(
            "<html><body>An internal error occurred</body></html>".to_string(),
        )),
    }
}

fn build_item_list(
    conn: &Connection,
    params: &ListQueryParams,
    columns: &[ColumnConfig],
) -> Result<String> {
    let tags: Vec<String> = params
        .tags
        .as_ref()
        .map(|t| t.split(',').map(|s| s.trim().to_string()).collect())
        .unwrap_or_default();

    let items = if tags.is_empty() {
        db::query_all_items(conn)?
    } else {
        db::query_tagged_items(conn, &tags)?
    };

    // Sort items
    let mut sorted_items = items;
    if params.sort == "newest" {
        sorted_items.sort_by(|a, b| b.id.cmp(&a.id));
    } else {
        sorted_items.sort_by(|a, b| a.id.cmp(&b.id));
    }

    // Apply pagination
    let start = params.start;
    let count = params.count.min(10000);
    let end = std::cmp::min(start + count, sorted_items.len());
    let page_items = if start < sorted_items.len() {
        sorted_items[start..std::cmp::min(end, sorted_items.len())].to_vec()
    } else {
        vec![]
    };

    // Get tags and meta for all items in the page
    let item_ids: Vec<i64> = page_items.iter().filter_map(|item| item.id).collect();
    let tags_map = db::get_tags_for_items(conn, &item_ids)?;
    let meta_map = db::get_meta_for_items(conn, &item_ids)?;

    // Debug: print number of tags per item
    for item_id in &item_ids {
        if let Some(tags) = tags_map.get(item_id) {
            debug!("Item {} has {} tags: {:?}", item_id, tags.len(), tags);
        }
    }

    let mut html = String::new();
    html.push_str("<html><head><title>Keep - Items</title>");
    html.push_str("<link rel=\"stylesheet\" href=\"/style.css\">");
    html.push_str("</head><body>");
    html.push_str("<h1>Items</h1>");
    html.push_str("<p><a href=\"/swagger\">API Documentation</a></p>");

    // Add recent tags section using the items we already have
    html.push_str("<h2>Recent Tags</h2>");

    // Collect all tags from all items, keeping track of their timestamps
    let mut all_tags_with_time: Vec<(String, chrono::DateTime<chrono::Utc>)> = Vec::new();
    for item in &sorted_items {
        if let Some(item_id) = item.id
            && let Some(tags) = tags_map.get(&item_id)
        {
            for tag in tags {
                all_tags_with_time.push((tag.name.clone(), item.ts));
            }
        }
    }

    // Sort by timestamp descending (most recent first)
    all_tags_with_time.sort_by(|a, b| b.1.cmp(&a.1));

    // Get unique tags in order of most recent appearance
    let mut seen = std::collections::HashSet::new();
    let mut recent_tags = Vec::new();
    for (tag, _) in all_tags_with_time {
        if !seen.contains(&tag) {
            seen.insert(tag.clone());
            recent_tags.push(tag);
            if recent_tags.len() >= 20 {
                break;
            }
        }
    }

    if recent_tags.is_empty() {
        html.push_str("<p>No tags found</p>");
    } else {
        html.push_str("<p>");
        for tag in recent_tags {
            html.push_str(&format!(
                "<a href=\"/?tags={}\" style=\"margin-right: 8px;\">{}</a>",
                esc_attr(&tag),
                esc(&tag)
            ));
        }
        html.push_str("</p>");
    }

    // Start table
    html.push_str("<table>");

    // Table headers
    html.push_str("<tr>");
    for column in columns {
        html.push_str(&format!("<th>{}</th>", esc(&column.label)));
    }
    html.push_str("<th>Actions</th>");
    html.push_str("</tr>");

    // Table rows
    for item in page_items {
        let item_id = item.id.unwrap_or(0);
        let tags = tags_map.get(&item_id).cloned().unwrap_or_default();
        let meta: HashMap<String, String> = meta_map
            .get(&item_id)
            .map(|metas| {
                metas
                    .iter()
                    .map(|(name, value)| (name.clone(), value.clone()))
                    .collect()
            })
            .unwrap_or_default();

        html.push_str("<tr>");
        for column in columns {
            let value = match column.name.as_str() {
                "id" => {
                    let id_value = item.id.map(|id| id.to_string()).unwrap_or_default();
                    // Make the ID a link to the item details page
                    format!("<a href=\"/item/{item_id}\">{id_value}</a>")
                }
                "time" => item.ts.format("%Y-%m-%d %H:%M:%S").to_string(),
                "size" => item.size.map(|s| s.to_string()).unwrap_or_default(),
                "tags" => {
                    // Make sure we're using all tags for the item
                    let tag_links: Vec<String> = tags
                        .iter()
                        .map(|t| {
                            format!(
                                "<a href=\"/?tags={}\">{}</a>",
                                esc_attr(&t.name),
                                esc(&t.name)
                            )
                        })
                        .collect();
                    tag_links.join(", ")
                }
                _ => {
                    if column.name.starts_with("meta:") {
                        let meta_key = &column.name[5..];
                        meta.get(meta_key).cloned().unwrap_or_default()
                    } else {
                        String::new()
                    }
                }
            };

            // Apply max_len if specified, but skip for tags column to avoid truncating HTML
            let display_value = if column.name == "tags" {
                value
            } else if let Some(max_len_str) = &column.max_len {
                if let Ok(max_len) = max_len_str.parse::<usize>() {
                    if value.chars().count() > max_len {
                        let truncated: String = value.chars().take(max_len).collect();
                        format!("{truncated}...")
                    } else {
                        value
                    }
                } else {
                    value
                }
            } else {
                value
            };

            // Apply alignment
            let align_style = match column.align {
                crate::config::ColumnAlignment::Left => "text-align: left;",
                crate::config::ColumnAlignment::Right => "text-align: right;",
                crate::config::ColumnAlignment::Center => "text-align: center;",
            };

            let rendered_value = if column.name == "tags" {
                display_value // Already contains escaped HTML links
            } else {
                esc(&display_value)
            };

            html.push_str(&format!(
                "<td style=\"{align_style}\">{rendered_value}</td>"
            ));
        }

        // Actions column
        html.push_str(&format!(
            "<td><a href=\"/item/{item_id}\">View</a> | <a href=\"/api/item/{item_id}/content\">Download</a></td>"
        ));

        html.push_str("</tr>");
    }

    html.push_str("</table>");

    // Add pagination info
    html.push_str(&format!(
        "<p>Showing {} to {} of {} items</p>",
        start + 1,
        std::cmp::min(end, sorted_items.len()),
        sorted_items.len()
    ));

    html.push_str("</body></html>");

    Ok(html)
}

async fn style_css() -> &'static str {
    r#"
    body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 20px;
        background-color: #f5f5f5;
    }
    h1, h2 {
        color: #333;
    }
    table {
        border-collapse: collapse;
        width: 100%;
        background-color: white;
        box-shadow: 0 1px 3px rgba(0,0,0,0.1);
    }
    th, td {
        border: 1px solid #ddd;
        padding: 12px;
        text-align: left;
    }
    th {
        background-color: #f2f2f2;
        position: sticky;
        top: 0;
    }
    tr:nth-child(even) {
        background-color: #f9f9f9;
    }
    tr:hover {
        background-color: #f1f1f1;
    }
    a {
        color: #0066cc;
        text-decoration: none;
    }
    a:hover {
        text-decoration: underline;
    }
    .pagination {
        margin: 20px 0;
    }
    .actions {
        white-space: nowrap;
    }
    "#
}

async fn show_item(
    State(state): State<AppState>,
    Path(id): Path<i64>,
) -> Result<Response, Html<String>> {
    let conn = state.db.lock().await;

    let result = build_item_details(&conn, id);

    match result {
        Ok(html) => {
            // Build response with explicit Content-Length
            let response = Response::builder()
                .header("content-type", "text/html")
                .header("content-length", html.len().to_string())
                .body(axum::body::Body::from(html))
                .map_err(|_| Html("<html><body>Internal Server Error</body></html>".to_string()))?;
            Ok(response)
        }
        Err(_e) => Err(Html(
            "<html><body>An internal error occurred</body></html>".to_string(),
        )),
    }
}

fn build_item_details(conn: &Connection, id: i64) -> Result<String> {
    let item = match db::get_item(conn, id)? {
        Some(item) => item,
        None => return Err(anyhow::anyhow!("Item not found")),
    };

    let tags = db::get_item_tags(conn, &item)?;
    let metas = db::get_item_meta(conn, &item)?;

    let mut html = String::new();
    html.push_str(&format!("<html><head><title>Keep - Item #{id}</title>"));
    html.push_str("<link rel=\"stylesheet\" href=\"/style.css\">");
    html.push_str("</head><body>");
    html.push_str(&format!("<h1>Item #{id}</h1>"));

    // Single table for all details
    html.push_str("<table>");
    html.push_str(&format!(
        "<tr><th>ID</th><td>{}</td></tr>",
        item.id.unwrap_or(0)
    ));
    html.push_str(&format!(
        "<tr><th>Timestamp</th><td>{}</td></tr>",
        item.ts.format("%Y-%m-%d %H:%M:%S")
    ));
    html.push_str(&format!(
        "<tr><th>Size</th><td>{}</td></tr>",
        item.size.unwrap_or(0)
    ));
    html.push_str(&format!(
        "<tr><th>Compression</th><td>{}</td></tr>",
        esc(&item.compression)
    ));

    // Tags row
    html.push_str("<tr><th>Tags</th><td>");
    if tags.is_empty() {
        html.push_str("No tags");
    } else {
        let tag_links: Vec<String> = tags
            .iter()
            .map(|t| {
                format!(
                    "<a href=\"/?tags={}\">{}</a>",
                    esc_attr(&t.name),
                    esc(&t.name)
                )
            })
            .collect();
        html.push_str(&tag_links.join(", "));
    }
    html.push_str("</td></tr>");

    // Metadata rows
    if metas.is_empty() {
        html.push_str("<tr><th>Metadata</th><td>No metadata</td></tr>");
    } else {
        for meta in metas {
            html.push_str(&format!(
                "<tr><th>{}</th><td>{}</td></tr>",
                esc(&meta.name),
                esc(&meta.value)
            ));
        }
    }
    html.push_str("</table>");

    // Links
    html.push_str("<h2>Actions</h2>");
    html.push_str(&format!(
        "<p><a href=\"/api/item/{id}/content\">Download Content</a></p>"
    ));
    html.push_str("<p><a href=\"/\">Back to list</a></p>");

    html.push_str("</body></html>");

    Ok(html)
}
