use regex::Regex;
use serde_json::Value;

use crate::client::ConfluenceClient;
use crate::mcp::{CallToolResult, ToolDefinition};
use crate::types::{ConfluencePage, ConfluencePageV1};

use super::schema;

pub fn definitions() -> Vec<ToolDefinition> {
    vec![
        ToolDefinition {
            name: "update_release_table_cell".to_string(),
            description: "Update a specific cell in a Confluence table. Finds a row by identifier and updates a specific column.".to_string(),
            input_schema: schema(&[
                ("pageId", "string", true, "The page ID containing the table"),
                ("rowIdentifier", "string", true, "Text to identify the row (e.g., a date, feature name, or SDLC number)"),
                ("columnIdentifier", "string", true, "Text to identify the column header"),
                ("newContent", "string", true, "New HTML/storage format content for the cell"),
                ("replaceMode", "string", false, "How to update: replace (default), append, or prepend"),
            ]),
        },
        ToolDefinition {
            name: "find_replace_in_page".to_string(),
            description: "Find and replace text/patterns in a Confluence page storage format."
                .to_string(),
            input_schema: schema(&[
                ("pageId", "string", true, "The page ID to update"),
                (
                    "findPattern",
                    "string",
                    true,
                    "The text or regex pattern to find",
                ),
                (
                    "replaceWith",
                    "string",
                    true,
                    "The replacement text/content",
                ),
                (
                    "isRegex",
                    "boolean",
                    false,
                    "Whether findPattern is a regex (default: false)",
                ),
                (
                    "globalReplace",
                    "boolean",
                    false,
                    "Replace all occurrences (default: false)",
                ),
            ]),
        },
        ToolDefinition {
            name: "insert_table_row".to_string(),
            description: "Insert a new row into a Confluence table.".to_string(),
            input_schema: schema(&[
                ("pageId", "string", true, "The page ID containing the table"),
                (
                    "tableIdentifier",
                    "string",
                    true,
                    "Text to identify the table (e.g., a column header)",
                ),
                (
                    "afterRowIdentifier",
                    "string",
                    false,
                    "Insert after the row containing this text (default: end of table)",
                ),
                (
                    "rowContent",
                    "string",
                    true,
                    "Complete <tr>...</tr> HTML for the new row",
                ),
            ]),
        },
    ]
}

/// Fetch page body, title, and version.
async fn fetch_page_for_edit(
    client: &ConfluenceClient,
    page_id: &str,
) -> Result<(String, String, i64), String> {
    if client.config().is_cloud {
        let page: ConfluencePage = client
            .get(&format!("/pages/{page_id}?body-format=storage"))
            .await?;
        let body = page
            .body
            .and_then(|b| b.storage)
            .map_or(String::new(), |s| s.value);
        let ver = page.version.map_or(1, |v| v.number);
        Ok((page.title, body, ver))
    } else {
        let page: ConfluencePageV1 = client
            .get(&format!(
                "/content/{page_id}?expand=body.storage,version"
            ))
            .await?;
        let body = page
            .body
            .and_then(|b| b.storage)
            .map_or(String::new(), |s| s.value);
        let ver = page.version.map_or(1, |v| v.number);
        Ok((page.title, body, ver))
    }
}

/// Save updated page content.
async fn save_page(
    client: &ConfluenceClient,
    page_id: &str,
    title: &str,
    content: &str,
    version: i64,
    message: &str,
) -> Result<(), String> {
    if client.config().is_cloud {
        let body = serde_json::json!({
            "id": page_id,
            "status": "current",
            "title": title,
            "body": {
                "representation": "storage",
                "value": content,
            },
            "version": {
                "number": version,
                "message": message,
            }
        });
        client
            .put::<Value>(&format!("/pages/{page_id}"), &body)
            .await?;
    } else {
        let body = serde_json::json!({
            "type": "page",
            "title": title,
            "body": {
                "storage": {
                    "value": content,
                    "representation": "storage"
                }
            },
            "version": {
                "number": version,
                "message": message,
            }
        });
        client
            .put::<Value>(&format!("/content/{page_id}"), &body)
            .await?;
    }
    Ok(())
}

pub async fn update_release_table_cell(client: &ConfluenceClient, args: &Value) -> CallToolResult {
    let page_id = match args.get("pageId").and_then(|v| v.as_str()) {
        Some(id) => id,
        None => return CallToolResult::text("Error: pageId is required."),
    };
    let row_id = match args.get("rowIdentifier").and_then(|v| v.as_str()) {
        Some(r) => r,
        None => return CallToolResult::text("Error: rowIdentifier is required."),
    };
    let col_id = match args.get("columnIdentifier").and_then(|v| v.as_str()) {
        Some(c) => c,
        None => return CallToolResult::text("Error: columnIdentifier is required."),
    };
    let new_content = match args.get("newContent").and_then(|v| v.as_str()) {
        Some(c) => c,
        None => return CallToolResult::text("Error: newContent is required."),
    };
    let replace_mode = args
        .get("replaceMode")
        .and_then(|v| v.as_str())
        .unwrap_or("replace");

    let (title, body, version) = match fetch_page_for_edit(client, page_id).await {
        Ok(v) => v,
        Err(e) => return CallToolResult::text(format!("Error updating table cell: {e}")),
    };

    if body.is_empty() {
        return CallToolResult::text(format!("Page \"{title}\" has no content."));
    }

    let table_re = Regex::new(r"(?is)<table[^>]*>[\s\S]*?</table>").unwrap();
    let tables: Vec<&str> = table_re.find_iter(&body).map(|m| m.as_str()).collect();

    if tables.is_empty() {
        return CallToolResult::text(format!("No tables found in page \"{title}\"."));
    }

    let mut updated_content = body.clone();
    let mut cell_found = false;
    let mut cell_updated = false;

    let col_lower = col_id.to_lowercase();
    let row_lower = row_id.to_lowercase();

    for table in &tables {
        if !table.to_lowercase().contains(&col_lower) {
            continue;
        }

        let row_re = Regex::new(r"(?is)<tr[^>]*>[\s\S]*?</tr>").unwrap();
        let rows: Vec<&str> = row_re.find_iter(table).map(|m| m.as_str()).collect();

        if rows.is_empty() {
            continue;
        }

        // Find column index from header row
        let header_cell_re = Regex::new(r"(?is)<t[hd][^>]*>[\s\S]*?</t[hd]>").unwrap();
        let header_cells: Vec<&str> = header_cell_re
            .find_iter(rows[0])
            .map(|m| m.as_str())
            .collect();

        let col_idx = header_cells
            .iter()
            .position(|c| c.to_lowercase().contains(&col_lower));

        let col_idx = match col_idx {
            Some(i) => i,
            None => continue,
        };

        for row in rows.iter().skip(1) {
            if !row.to_lowercase().contains(&row_lower) {
                continue;
            }

            cell_found = true;

            let td_re = Regex::new(r"(?is)<td([^>]*)>([\s\S]*?)</td>").unwrap();
            let cells: Vec<regex::Captures> = td_re.captures_iter(row).collect();

            if col_idx >= cells.len() {
                continue;
            }

            let target_cap = &cells[col_idx];
            let full_cell = target_cap.get(0).unwrap().as_str();
            let attrs = &target_cap[1];
            let existing = &target_cap[2];

            let final_content = match replace_mode {
                "append" => format!("{existing}{new_content}"),
                "prepend" => format!("{new_content}{existing}"),
                _ => new_content.to_string(),
            };

            let new_cell = format!("<td{attrs}>{final_content}</td>");
            let new_row = row.replace(full_cell, &new_cell);
            let new_table = table.replace(*row, &new_row);
            updated_content = updated_content.replace(*table, &new_table);
            cell_updated = true;
            break;
        }

        if cell_updated {
            break;
        }
    }

    if !cell_found {
        return CallToolResult::text(format!(
            "Could not find a row containing \"{row_id}\" with column \"{col_id}\" in page \"{title}\".\n\nTip: Use `get_page_storage_format` to inspect the page structure."
        ));
    }

    if !cell_updated {
        return CallToolResult::text(format!(
            "Found the row but could not update the cell. The table structure may be complex.\n\nTip: Use `get_page_storage_format` to inspect the exact structure and use `update_page` directly."
        ));
    }

    let new_version = version + 1;
    let msg = format!("Updated {col_id} column for {row_id}");

    match save_page(client, page_id, &title, &updated_content, new_version, &msg).await {
        Ok(()) => CallToolResult::text(format!(
            "✅ Successfully updated the \"{col_id}\" column for row \"{row_id}\" in page \"{title}\".\n\n**New Version**: {new_version}"
        )),
        Err(e) => CallToolResult::text(format!("Error updating table cell: {e}")),
    }
}

pub async fn find_replace_in_page(client: &ConfluenceClient, args: &Value) -> CallToolResult {
    let page_id = match args.get("pageId").and_then(|v| v.as_str()) {
        Some(id) => id,
        None => return CallToolResult::text("Error: pageId is required."),
    };
    let find_pattern = match args.get("findPattern").and_then(|v| v.as_str()) {
        Some(p) => p,
        None => return CallToolResult::text("Error: findPattern is required."),
    };
    let replace_with = match args.get("replaceWith").and_then(|v| v.as_str()) {
        Some(r) => r,
        None => return CallToolResult::text("Error: replaceWith is required."),
    };
    let is_regex = args
        .get("isRegex")
        .and_then(|v| v.as_bool())
        .unwrap_or(false);
    let global_replace = args
        .get("globalReplace")
        .and_then(|v| v.as_bool())
        .unwrap_or(false);

    let (title, body, version) = match fetch_page_for_edit(client, page_id).await {
        Ok(v) => v,
        Err(e) => return CallToolResult::text(format!("Error in find/replace: {e}")),
    };

    if body.is_empty() {
        return CallToolResult::text(format!("Page \"{title}\" has no content."));
    }

    // Count matches
    let match_count = if is_regex {
        match Regex::new(find_pattern) {
            Ok(re) => re.find_iter(&body).count(),
            Err(e) => {
                return CallToolResult::text(format!("Invalid regex: {e}"));
            }
        }
    } else {
        body.matches(find_pattern).count()
    };

    if match_count == 0 {
        return CallToolResult::text(format!(
            "Pattern \"{find_pattern}\" not found in page \"{title}\"."
        ));
    }

    // Perform replacement
    let updated_content = if is_regex {
        let re = Regex::new(find_pattern).unwrap();
        if global_replace {
            re.replace_all(&body, replace_with).to_string()
        } else {
            re.replace(&body, replace_with).to_string()
        }
    } else if global_replace {
        body.replace(find_pattern, replace_with)
    } else {
        body.replacen(find_pattern, replace_with, 1)
    };

    let replacement_count = if global_replace { match_count } else { 1 };

    let new_version = version + 1;
    let truncated_pattern = if find_pattern.len() > 50 {
        format!("{}...", &find_pattern[..50])
    } else {
        find_pattern.to_string()
    };
    let msg = format!("Find/replace: \"{truncated_pattern}\"");

    match save_page(client, page_id, &title, &updated_content, new_version, &msg).await {
        Ok(()) => CallToolResult::text(format!(
            "✅ Successfully replaced {replacement_count} occurrence(s) in page \"{title}\".\n\n**New Version**: {new_version}"
        )),
        Err(e) => CallToolResult::text(format!("Error in find/replace: {e}")),
    }
}

pub async fn insert_table_row(client: &ConfluenceClient, args: &Value) -> CallToolResult {
    let page_id = match args.get("pageId").and_then(|v| v.as_str()) {
        Some(id) => id,
        None => return CallToolResult::text("Error: pageId is required."),
    };
    let table_identifier = match args.get("tableIdentifier").and_then(|v| v.as_str()) {
        Some(t) => t,
        None => return CallToolResult::text("Error: tableIdentifier is required."),
    };
    let after_row_id = args.get("afterRowIdentifier").and_then(|v| v.as_str());
    let row_content = match args.get("rowContent").and_then(|v| v.as_str()) {
        Some(r) => r,
        None => return CallToolResult::text("Error: rowContent is required."),
    };

    let (title, body, version) = match fetch_page_for_edit(client, page_id).await {
        Ok(v) => v,
        Err(e) => return CallToolResult::text(format!("Error inserting table row: {e}")),
    };

    if body.is_empty() {
        return CallToolResult::text(format!("Page \"{title}\" has no content."));
    }

    let table_re = Regex::new(r"(?is)<table[^>]*>[\s\S]*?</table>").unwrap();
    let tables: Vec<&str> = table_re.find_iter(&body).map(|m| m.as_str()).collect();

    if tables.is_empty() {
        return CallToolResult::text(format!("No tables found in page \"{title}\"."));
    }

    let table_lower = table_identifier.to_lowercase();
    let target_table = tables
        .iter()
        .find(|t| t.to_lowercase().contains(&table_lower));

    let target_table = match target_table {
        Some(t) => *t,
        None => {
            return CallToolResult::text(format!(
                "Could not find a table containing \"{table_identifier}\" in page \"{title}\"."
            ));
        }
    };

    let updated_table = if let Some(after_row) = after_row_id {
        let row_re = Regex::new(r"(?is)<tr[^>]*>[\s\S]*?</tr>").unwrap();
        let rows: Vec<&str> = row_re.find_iter(target_table).map(|m| m.as_str()).collect();

        let after_lower = after_row.to_lowercase();
        let mut insert_after_idx = None;
        for (i, row) in rows.iter().enumerate() {
            if row.to_lowercase().contains(&after_lower) {
                insert_after_idx = Some(i);
            }
        }

        match insert_after_idx {
            Some(idx) => {
                let target_row = rows[idx];
                target_table.replace(target_row, &format!("{target_row}{row_content}"))
            }
            None => {
                return CallToolResult::text(format!(
                    "Could not find a row containing \"{after_row}\" in the table."
                ));
            }
        }
    } else {
        // Insert at end
        if let Some(tbody_end) = target_table.rfind("</tbody>") {
            format!(
                "{}{}{}",
                &target_table[..tbody_end],
                row_content,
                &target_table[tbody_end..]
            )
        } else if let Some(table_end) = target_table.rfind("</table>") {
            format!(
                "{}{}{}",
                &target_table[..table_end],
                row_content,
                &target_table[table_end..]
            )
        } else {
            target_table.to_string()
        }
    };

    let updated_content = body.replace(target_table, &updated_table);
    let new_version = version + 1;

    match save_page(
        client,
        page_id,
        &title,
        &updated_content,
        new_version,
        "Added new table row",
    )
    .await
    {
        Ok(()) => CallToolResult::text(format!(
            "✅ Successfully inserted new row in table in page \"{title}\".\n\n**New Version**: {new_version}"
        )),
        Err(e) => CallToolResult::text(format!("Error inserting table row: {e}")),
    }
}
