use crate::cli::render::{self, Theme};
use rusqlite::Connection;
use serde::Serialize;

use crate::Config;
use crate::cli::robot::RobotMeta;
use crate::core::db::create_connection;
use crate::core::error::Result;
use crate::core::paths::get_db_path;
use crate::ingestion::storage::events::{EventCounts, count_events};

pub struct CountResult {
    pub entity: String,
    pub count: i64,
    pub system_count: Option<i64>,
    pub state_breakdown: Option<StateBreakdown>,
}

pub struct StateBreakdown {
    pub opened: i64,
    pub closed: i64,
    pub merged: Option<i64>,
    pub locked: Option<i64>,
}

pub fn run_count(config: &Config, entity: &str, type_filter: Option<&str>) -> Result<CountResult> {
    let db_path = get_db_path(config.storage.db_path.as_deref());
    let conn = create_connection(&db_path)?;

    match entity {
        "issues" => count_issues(&conn),
        "discussions" => count_discussions(&conn, type_filter),
        "notes" => count_notes(&conn, type_filter),
        "mrs" => count_mrs(&conn),
        _ => Ok(CountResult {
            entity: entity.to_string(),
            count: 0,
            system_count: None,
            state_breakdown: None,
        }),
    }
}

fn count_issues(conn: &Connection) -> Result<CountResult> {
    // Single query with conditional aggregation instead of 3 separate queries
    let (count, opened, closed): (i64, i64, i64) = conn.query_row(
        "SELECT
            COUNT(*),
            COALESCE(SUM(CASE WHEN state = 'opened' THEN 1 ELSE 0 END), 0),
            COALESCE(SUM(CASE WHEN state = 'closed' THEN 1 ELSE 0 END), 0)
         FROM issues",
        [],
        |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
    )?;

    Ok(CountResult {
        entity: "Issues".to_string(),
        count,
        system_count: None,
        state_breakdown: Some(StateBreakdown {
            opened,
            closed,
            merged: None,
            locked: None,
        }),
    })
}

fn count_mrs(conn: &Connection) -> Result<CountResult> {
    // Single query with conditional aggregation instead of 5 separate queries
    let (count, opened, merged, closed, locked): (i64, i64, i64, i64, i64) = conn.query_row(
        "SELECT
            COUNT(*),
            COALESCE(SUM(CASE WHEN state = 'opened' THEN 1 ELSE 0 END), 0),
            COALESCE(SUM(CASE WHEN state = 'merged' THEN 1 ELSE 0 END), 0),
            COALESCE(SUM(CASE WHEN state = 'closed' THEN 1 ELSE 0 END), 0),
            COALESCE(SUM(CASE WHEN state = 'locked' THEN 1 ELSE 0 END), 0)
         FROM merge_requests",
        [],
        |row| {
            Ok((
                row.get(0)?,
                row.get(1)?,
                row.get(2)?,
                row.get(3)?,
                row.get(4)?,
            ))
        },
    )?;

    Ok(CountResult {
        entity: "Merge Requests".to_string(),
        count,
        system_count: None,
        state_breakdown: Some(StateBreakdown {
            opened,
            closed,
            merged: Some(merged),
            locked: Some(locked),
        }),
    })
}

fn count_discussions(conn: &Connection, type_filter: Option<&str>) -> Result<CountResult> {
    let (count, entity_name) = match type_filter {
        Some("issue") => {
            let count: i64 = conn.query_row(
                "SELECT COUNT(*) FROM discussions WHERE noteable_type = 'Issue'",
                [],
                |row| row.get(0),
            )?;
            (count, "Issue Discussions")
        }
        Some("mr") => {
            let count: i64 = conn.query_row(
                "SELECT COUNT(*) FROM discussions WHERE noteable_type = 'MergeRequest'",
                [],
                |row| row.get(0),
            )?;
            (count, "MR Discussions")
        }
        _ => {
            let count: i64 =
                conn.query_row("SELECT COUNT(*) FROM discussions", [], |row| row.get(0))?;
            (count, "Discussions")
        }
    };

    Ok(CountResult {
        entity: entity_name.to_string(),
        count,
        system_count: None,
        state_breakdown: None,
    })
}

fn count_notes(conn: &Connection, type_filter: Option<&str>) -> Result<CountResult> {
    let (total, system_count, entity_name) = match type_filter {
        Some("issue") => {
            let (total, system): (i64, i64) = conn.query_row(
                "SELECT COUNT(*), COALESCE(SUM(n.is_system), 0)
                 FROM notes n
                 JOIN discussions d ON n.discussion_id = d.id
                 WHERE d.noteable_type = 'Issue'",
                [],
                |row| Ok((row.get(0)?, row.get(1)?)),
            )?;
            (total, system, "Issue Notes")
        }
        Some("mr") => {
            let (total, system): (i64, i64) = conn.query_row(
                "SELECT COUNT(*), COALESCE(SUM(n.is_system), 0)
                 FROM notes n
                 JOIN discussions d ON n.discussion_id = d.id
                 WHERE d.noteable_type = 'MergeRequest'",
                [],
                |row| Ok((row.get(0)?, row.get(1)?)),
            )?;
            (total, system, "MR Notes")
        }
        _ => {
            let (total, system): (i64, i64) = conn.query_row(
                "SELECT COUNT(*), COALESCE(SUM(is_system), 0) FROM notes",
                [],
                |row| Ok((row.get(0)?, row.get(1)?)),
            )?;
            (total, system, "Notes")
        }
    };

    let non_system = total - system_count;

    Ok(CountResult {
        entity: entity_name.to_string(),
        count: non_system,
        system_count: Some(system_count),
        state_breakdown: None,
    })
}

#[derive(Serialize)]
struct CountJsonOutput {
    ok: bool,
    data: CountJsonData,
    meta: RobotMeta,
}

#[derive(Serialize)]
struct CountJsonData {
    entity: String,
    count: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    system_excluded: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    breakdown: Option<CountJsonBreakdown>,
}

#[derive(Serialize)]
struct CountJsonBreakdown {
    opened: i64,
    closed: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    merged: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    locked: Option<i64>,
}

pub fn run_count_events(config: &Config) -> Result<EventCounts> {
    let db_path = get_db_path(config.storage.db_path.as_deref());
    let conn = create_connection(&db_path)?;
    count_events(&conn)
}

#[derive(Serialize)]
struct EventCountJsonOutput {
    ok: bool,
    data: EventCountJsonData,
    meta: RobotMeta,
}

#[derive(Serialize)]
struct EventCountJsonData {
    state_events: EventTypeCounts,
    label_events: EventTypeCounts,
    milestone_events: EventTypeCounts,
    total: usize,
}

#[derive(Serialize)]
struct EventTypeCounts {
    issue: usize,
    merge_request: usize,
    total: usize,
}

pub fn print_event_count_json(counts: &EventCounts, elapsed_ms: u64) {
    let output = EventCountJsonOutput {
        ok: true,
        data: EventCountJsonData {
            state_events: EventTypeCounts {
                issue: counts.state_issue,
                merge_request: counts.state_mr,
                total: counts.state_issue + counts.state_mr,
            },
            label_events: EventTypeCounts {
                issue: counts.label_issue,
                merge_request: counts.label_mr,
                total: counts.label_issue + counts.label_mr,
            },
            milestone_events: EventTypeCounts {
                issue: counts.milestone_issue,
                merge_request: counts.milestone_mr,
                total: counts.milestone_issue + counts.milestone_mr,
            },
            total: counts.total(),
        },
        meta: RobotMeta::new(elapsed_ms),
    };

    match serde_json::to_string(&output) {
        Ok(json) => println!("{json}"),
        Err(e) => eprintln!("Error serializing to JSON: {e}"),
    }
}

pub fn print_event_count(counts: &EventCounts) {
    println!(
        "{:<20} {:>8} {:>8} {:>8}",
        Theme::info().bold().render("Event Type"),
        Theme::bold().render("Issues"),
        Theme::bold().render("MRs"),
        Theme::bold().render("Total")
    );

    let state_total = counts.state_issue + counts.state_mr;
    let label_total = counts.label_issue + counts.label_mr;
    let milestone_total = counts.milestone_issue + counts.milestone_mr;

    println!(
        "{:<20} {:>8} {:>8} {:>8}",
        "State events",
        render::format_number(counts.state_issue as i64),
        render::format_number(counts.state_mr as i64),
        render::format_number(state_total as i64)
    );
    println!(
        "{:<20} {:>8} {:>8} {:>8}",
        "Label events",
        render::format_number(counts.label_issue as i64),
        render::format_number(counts.label_mr as i64),
        render::format_number(label_total as i64)
    );
    println!(
        "{:<20} {:>8} {:>8} {:>8}",
        "Milestone events",
        render::format_number(counts.milestone_issue as i64),
        render::format_number(counts.milestone_mr as i64),
        render::format_number(milestone_total as i64)
    );

    let total_issues = counts.state_issue + counts.label_issue + counts.milestone_issue;
    let total_mrs = counts.state_mr + counts.label_mr + counts.milestone_mr;
    println!(
        "{:<20} {:>8} {:>8} {:>8}",
        Theme::bold().render("Total"),
        render::format_number(total_issues as i64),
        render::format_number(total_mrs as i64),
        Theme::bold().render(&render::format_number(counts.total() as i64))
    );
}

pub fn print_count_json(result: &CountResult, elapsed_ms: u64) {
    let breakdown = result.state_breakdown.as_ref().map(|b| CountJsonBreakdown {
        opened: b.opened,
        closed: b.closed,
        merged: b.merged,
        locked: b.locked.filter(|&l| l > 0),
    });

    let output = CountJsonOutput {
        ok: true,
        data: CountJsonData {
            entity: result.entity.to_lowercase().replace(' ', "_"),
            count: result.count,
            system_excluded: result.system_count,
            breakdown,
        },
        meta: RobotMeta::new(elapsed_ms),
    };

    match serde_json::to_string(&output) {
        Ok(json) => println!("{json}"),
        Err(e) => eprintln!("Error serializing to JSON: {e}"),
    }
}

pub fn print_count(result: &CountResult) {
    let count_str = render::format_number(result.count);

    if let Some(system_count) = result.system_count {
        println!(
            "{}: {:>10} {}",
            Theme::info().render(&result.entity),
            Theme::bold().render(&count_str),
            Theme::dim().render(&format!(
                "(excluding {} system)",
                render::format_number(system_count)
            ))
        );
    } else {
        println!(
            "{}: {:>10}",
            Theme::info().render(&result.entity),
            Theme::bold().render(&count_str)
        );
    }

    if let Some(breakdown) = &result.state_breakdown {
        println!("  opened: {:>10}", render::format_number(breakdown.opened));
        if let Some(merged) = breakdown.merged {
            println!("  merged: {:>10}", render::format_number(merged));
        }
        println!("  closed: {:>10}", render::format_number(breakdown.closed));
        if let Some(locked) = breakdown.locked
            && locked > 0
        {
            println!("  locked: {:>10}", render::format_number(locked));
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::cli::render;

    #[test]
    fn format_number_handles_small_numbers() {
        assert_eq!(render::format_number(0), "0");
        assert_eq!(render::format_number(1), "1");
        assert_eq!(render::format_number(100), "100");
        assert_eq!(render::format_number(999), "999");
    }

    #[test]
    fn format_number_adds_thousands_separators() {
        assert_eq!(render::format_number(1000), "1,000");
        assert_eq!(render::format_number(12345), "12,345");
        assert_eq!(render::format_number(1234567), "1,234,567");
    }
}
