use anyhow::{Context, Result};
use colored::Colorize;
use comfy_table::{presets::UTF8_FULL, Table};
use serde::Serialize;

use crate::types::{ClusterStatusResponse, NodeStatusInfo, RangeInfoDto};

/// Output format for CLI commands
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
    /// Human-readable table with colors
    Table,
    /// Machine-readable JSON
    Json,
}

impl std::str::FromStr for OutputFormat {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "table" => Ok(OutputFormat::Table),
            "json" => Ok(OutputFormat::Json),
            _ => anyhow::bail!("Invalid format: {}. Must be 'table' or 'json'", s),
        }
    }
}

/// Format cluster summary as human-readable table
pub fn format_cluster_summary(status: &ClusterStatusResponse) -> String {
    let mut output = String::new();

    output.push_str(&format!("\n{}\n", "CLUSTER OVERVIEW".bold()));
    output.push_str(&format!("  Node Count:    {}\n", status.node_count));
    output.push_str(&format!("  Shard Count:   {}\n", status.shard_count));
    output.push_str(&format!("  Meta Version:  {}\n", status.meta_version));

    output.push_str(&format!("\n{}\n", "NODES".bold()));
    output.push_str(&format_nodes_table(&status.nodes));

    output
}

/// Format nodes list as table
pub fn format_nodes_table(nodes: &[NodeStatusInfo]) -> String {
    if nodes.is_empty() {
        return "  (no nodes)\n".to_string();
    }

    let mut table = Table::new();
    table.load_preset(UTF8_FULL);
    table.set_header(vec!["Node ID", "State", "Shards", "Leader", "Follower"]);

    for node in nodes {
        let state_colored = match node.state.as_str() {
            "Alive" => node.state.green(),
            "Suspect" => node.state.yellow(),
            "Dead" => node.state.red(),
            _ => node.state.normal(),
        };

        let shard_count = node.shards.len();
        let shards_str = if shard_count <= 5 {
            node.shards.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(",")
        } else {
            format!("{} shards", shard_count)
        };

        // For now, we don't have leader/follower breakdown in NodeStatusInfo
        // This will be enhanced in Phase 2 with detailed member info
        table.add_row(vec![
            node.id.clone(),
            state_colored.to_string(),
            shards_str,
            "-".to_string(),
            "-".to_string(),
        ]);
    }

    table.to_string()
}

/// Format shards list as table
pub fn format_shards_table(shards: &[RangeInfoDto]) -> String {
    if shards.is_empty() {
        return "  (no shards)\n".to_string();
    }

    let mut table = Table::new();
    table.load_preset(UTF8_FULL);
    table.set_header(vec!["Shard ID", "Leader", "Replicas", "Size", "Assertions", "Generation"]);

    for shard in shards {
        let size_mb = shard.size_bytes as f64 / 1_048_576.0;
        let replicas_str = shard.replica_nodes.len().to_string();

        table.add_row(vec![
            shard.range_id.to_string(),
            shard.leader_node.clone(),
            replicas_str,
            format!("{:.2} MB", size_mb),
            shard.assertion_count.to_string(),
            shard.generation.to_string(),
        ]);
    }

    table.to_string()
}

/// Format detailed shard info
pub fn format_shard_detail(shard: &RangeInfoDto) -> String {
    let mut output = String::new();

    output.push_str(&format!("\n{}\n", "SHARD DETAILS".bold()));
    output.push_str(&format!("  Shard ID:      {}\n", shard.range_id));
    output.push_str(&format!("  Leader Node:   {}\n", shard.leader_node));
    output.push_str(&format!("  Replica Nodes: {}\n", shard.replica_nodes.join(", ")));
    output.push_str(&format!("  Key Range:     {} - {}\n", shard.start_key, shard.end_key));
    output.push_str(&format!("  Size:          {:.2} MB\n", shard.size_bytes as f64 / 1_048_576.0));
    output.push_str(&format!("  Assertions:    {}\n", shard.assertion_count));
    output.push_str(&format!("  Generation:    {}\n", shard.generation));

    output
}

/// Format node detail with assigned shards
pub fn format_node_detail(node: &NodeStatusInfo, shards: &[RangeInfoDto]) -> String {
    let mut output = String::new();

    output.push_str(&format!("\n{}\n", "NODE DETAILS".bold()));
    output.push_str(&format!("  Node ID: {}\n", node.id));

    let state_colored = match node.state.as_str() {
        "Alive" => node.state.green(),
        "Suspect" => node.state.yellow(),
        "Dead" => node.state.red(),
        _ => node.state.normal(),
    };
    output.push_str(&format!("  State:   {}\n", state_colored));

    output.push_str(&format!("\n{}\n", "ASSIGNED SHARDS".bold()));

    // Group shards by role (leader vs follower)
    let mut leader_shards = Vec::new();
    let mut follower_shards = Vec::new();

    for shard_id in &node.shards {
        if let Some(shard) = shards.iter().find(|s| s.range_id == *shard_id) {
            if shard.leader_node == node.id {
                leader_shards.push(shard);
            } else {
                follower_shards.push(shard);
            }
        }
    }

    output.push_str(&format!("  Leader for:   {} shards\n", leader_shards.len()));
    output.push_str(&format!("  Follower for: {} shards\n", follower_shards.len()));

    if !leader_shards.is_empty() {
        output.push_str(&format!("\n{}\n", "LEADER SHARDS".bold()));
        for shard in leader_shards {
            output.push_str(&format!(
                "  Shard {}: {:.2} MB, {} assertions\n",
                shard.range_id,
                shard.size_bytes as f64 / 1_048_576.0,
                shard.assertion_count
            ));
        }
    }

    if !follower_shards.is_empty() {
        output.push_str(&format!("\n{}\n", "FOLLOWER SHARDS".bold()));
        for shard in follower_shards {
            output.push_str(&format!(
                "  Shard {}: {:.2} MB, {} assertions (Leader: {})\n",
                shard.range_id,
                shard.size_bytes as f64 / 1_048_576.0,
                shard.assertion_count,
                shard.leader_node
            ));
        }
    }

    output
}

/// Format any value as JSON
pub fn format_json<T: Serialize>(value: &T) -> Result<String> {
    serde_json::to_string_pretty(value).context("Failed to serialize to JSON")
}

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

    #[test]
    fn test_format_cluster_summary() {
        let status = ClusterStatusResponse {
            node_count: 3,
            shard_count: 32,
            meta_version: 158,
            nodes: vec![
                NodeStatusInfo {
                    id: "a3f2b1c4".to_string(),
                    state: "Alive".to_string(),
                    shards: vec![1, 2, 3],
                },
                NodeStatusInfo {
                    id: "7d8e9f0a".to_string(),
                    state: "Dead".to_string(),
                    shards: vec![4, 5],
                },
            ],
        };

        let output = format_cluster_summary(&status);
        assert!(output.contains("Node Count:    3"));
        assert!(output.contains("Shard Count:   32"));
        assert!(output.contains("a3f2b1c4"));
    }

    #[test]
    fn test_format_json() {
        let status = ClusterStatusResponse {
            node_count: 1,
            shard_count: 8,
            meta_version: 10,
            nodes: vec![],
        };

        let json = format_json(&status).expect("Failed to format JSON");
        assert!(json.contains("\"node_count\": 1"));
        assert!(json.contains("\"shard_count\": 8"));
    }
}
