use rusqlite::Connection;

use crate::cli::render::{self, Theme};
use crate::core::error::Result;
use crate::core::time::ms_to_iso;

use super::types::*;

pub(super) fn query_active(
    conn: &Connection,
    project_id: Option<i64>,
    since_ms: i64,
    limit: usize,
    include_closed: bool,
) -> Result<ActiveResult> {
    // Prevent overflow: saturating_add caps at usize::MAX instead of wrapping to 0.
    // The .min() ensures the value fits in i64 for SQLite's LIMIT clause.
    let limit_plus_one = limit.saturating_add(1).min(i64::MAX as usize) as i64;

    // State filter for open-entities-only (default behavior)
    let state_joins = if include_closed {
        ""
    } else {
        " LEFT JOIN issues i ON d.issue_id = i.id
         LEFT JOIN merge_requests m ON d.merge_request_id = m.id"
    };
    let state_filter = if include_closed {
        ""
    } else {
        " AND (i.id IS NULL OR i.state = 'opened')
           AND (m.id IS NULL OR m.state = 'opened')"
    };

    // Total unresolved count -- conditionally built
    let total_sql_global = format!(
        "SELECT COUNT(*) FROM discussions d
         {state_joins}
         WHERE d.resolvable = 1 AND d.resolved = 0
           AND d.last_note_at >= ?1
           {state_filter}"
    );
    let total_sql_scoped = format!(
        "SELECT COUNT(*) FROM discussions d
         {state_joins}
         WHERE d.resolvable = 1 AND d.resolved = 0
           AND d.last_note_at >= ?1
           AND d.project_id = ?2
           {state_filter}"
    );

    let total_unresolved_in_window: u32 = match project_id {
        None => conn.query_row(&total_sql_global, rusqlite::params![since_ms], |row| {
            row.get(0)
        })?,
        Some(pid) => {
            conn.query_row(&total_sql_scoped, rusqlite::params![since_ms, pid], |row| {
                row.get(0)
            })?
        }
    };

    // Active discussions with context -- conditionally built SQL
    let sql_global = format!(
        "
        WITH picked AS (
            SELECT d.id, d.noteable_type, d.issue_id, d.merge_request_id,
                   d.project_id, d.last_note_at
            FROM discussions d
            {state_joins}
            WHERE d.resolvable = 1 AND d.resolved = 0
              AND d.last_note_at >= ?1
              {state_filter}
            ORDER BY d.last_note_at DESC
            LIMIT ?2
        ),
        note_counts AS (
            SELECT
                n.discussion_id,
                COUNT(*) AS note_count
            FROM notes n
            JOIN picked p ON p.id = n.discussion_id
            WHERE n.is_system = 0
            GROUP BY n.discussion_id
        ),
        participants AS (
            SELECT
                x.discussion_id,
                GROUP_CONCAT(x.author_username, X'1F') AS participants
            FROM (
                SELECT DISTINCT n.discussion_id, n.author_username
                FROM notes n
                JOIN picked p ON p.id = n.discussion_id
                WHERE n.is_system = 0 AND n.author_username IS NOT NULL
            ) x
            GROUP BY x.discussion_id
        )
        SELECT
            p.id AS discussion_id,
            p.noteable_type,
            COALESCE(i.iid, m.iid) AS entity_iid,
            COALESCE(i.title, m.title) AS entity_title,
            proj.path_with_namespace,
            p.last_note_at,
            COALESCE(nc.note_count, 0) AS note_count,
            COALESCE(pa.participants, '') AS participants
        FROM picked p
        JOIN projects proj ON p.project_id = proj.id
        LEFT JOIN issues i ON p.issue_id = i.id
        LEFT JOIN merge_requests m ON p.merge_request_id = m.id
        LEFT JOIN note_counts nc ON nc.discussion_id = p.id
        LEFT JOIN participants pa ON pa.discussion_id = p.id
        ORDER BY p.last_note_at DESC
    "
    );

    let sql_scoped = format!(
        "
        WITH picked AS (
            SELECT d.id, d.noteable_type, d.issue_id, d.merge_request_id,
                   d.project_id, d.last_note_at
            FROM discussions d
            {state_joins}
            WHERE d.resolvable = 1 AND d.resolved = 0
              AND d.last_note_at >= ?1
              AND d.project_id = ?2
              {state_filter}
            ORDER BY d.last_note_at DESC
            LIMIT ?3
        ),
        note_counts AS (
            SELECT
                n.discussion_id,
                COUNT(*) AS note_count
            FROM notes n
            JOIN picked p ON p.id = n.discussion_id
            WHERE n.is_system = 0
            GROUP BY n.discussion_id
        ),
        participants AS (
            SELECT
                x.discussion_id,
                GROUP_CONCAT(x.author_username, X'1F') AS participants
            FROM (
                SELECT DISTINCT n.discussion_id, n.author_username
                FROM notes n
                JOIN picked p ON p.id = n.discussion_id
                WHERE n.is_system = 0 AND n.author_username IS NOT NULL
            ) x
            GROUP BY x.discussion_id
        )
        SELECT
            p.id AS discussion_id,
            p.noteable_type,
            COALESCE(i.iid, m.iid) AS entity_iid,
            COALESCE(i.title, m.title) AS entity_title,
            proj.path_with_namespace,
            p.last_note_at,
            COALESCE(nc.note_count, 0) AS note_count,
            COALESCE(pa.participants, '') AS participants
        FROM picked p
        JOIN projects proj ON p.project_id = proj.id
        LEFT JOIN issues i ON p.issue_id = i.id
        LEFT JOIN merge_requests m ON p.merge_request_id = m.id
        LEFT JOIN note_counts nc ON nc.discussion_id = p.id
        LEFT JOIN participants pa ON pa.discussion_id = p.id
        ORDER BY p.last_note_at DESC
    "
    );

    // Row-mapping closure shared between both variants
    let map_row = |row: &rusqlite::Row| -> rusqlite::Result<ActiveDiscussion> {
        let noteable_type: String = row.get(1)?;
        let entity_type = if noteable_type == "MergeRequest" {
            "MR"
        } else {
            "Issue"
        };
        let participants_csv: Option<String> = row.get(7)?;
        // Sort participants for deterministic output -- GROUP_CONCAT order is undefined
        let mut participants: Vec<String> = participants_csv
            .as_deref()
            .filter(|s| !s.is_empty())
            .map(|csv| csv.split('\x1F').map(String::from).collect())
            .unwrap_or_default();
        participants.sort();

        const MAX_PARTICIPANTS: usize = 50;
        let participants_total = participants.len() as u32;
        let participants_truncated = participants.len() > MAX_PARTICIPANTS;
        if participants_truncated {
            participants.truncate(MAX_PARTICIPANTS);
        }

        Ok(ActiveDiscussion {
            discussion_id: row.get(0)?,
            entity_type: entity_type.to_string(),
            entity_iid: row.get(2)?,
            entity_title: row.get(3)?,
            project_path: row.get(4)?,
            last_note_at: row.get(5)?,
            note_count: row.get(6)?,
            participants,
            participants_total,
            participants_truncated,
        })
    };

    // Select variant first, then prepare exactly one statement
    let discussions: Vec<ActiveDiscussion> = match project_id {
        None => {
            let mut stmt = conn.prepare_cached(&sql_global)?;
            stmt.query_map(rusqlite::params![since_ms, limit_plus_one], &map_row)?
                .collect::<std::result::Result<Vec<_>, _>>()?
        }
        Some(pid) => {
            let mut stmt = conn.prepare_cached(&sql_scoped)?;
            stmt.query_map(rusqlite::params![since_ms, pid, limit_plus_one], &map_row)?
                .collect::<std::result::Result<Vec<_>, _>>()?
        }
    };

    let truncated = discussions.len() > limit;
    let discussions: Vec<ActiveDiscussion> = discussions.into_iter().take(limit).collect();

    Ok(ActiveResult {
        discussions,
        total_unresolved_in_window,
        truncated,
    })
}

pub(super) fn print_active_human(r: &ActiveResult, project_path: Option<&str>) {
    println!();
    println!(
        "{}",
        Theme::bold().render(&format!(
            "Active Discussions ({} unresolved in window)",
            r.total_unresolved_in_window
        ))
    );
    println!("{}", "\u{2500}".repeat(60));
    super::print_scope_hint(project_path);
    println!();

    if r.discussions.is_empty() {
        println!(
            "  {}",
            Theme::dim().render("No active unresolved discussions in this time window.")
        );
        println!();
        return;
    }

    for disc in &r.discussions {
        let prefix = if disc.entity_type == "MR" { "!" } else { "#" };
        let participants_str = disc
            .participants
            .iter()
            .map(|p| format!("@{p}"))
            .collect::<Vec<_>>()
            .join(", ");

        println!(
            "  {} {} {}  {} notes  {}",
            Theme::info().render(&format!("{prefix}{}", disc.entity_iid)),
            render::truncate(&disc.entity_title, render::flex_width(30, 20)),
            Theme::dim().render(&render::format_relative_time(disc.last_note_at)),
            disc.note_count,
            Theme::dim().render(&disc.project_path),
        );
        if !participants_str.is_empty() {
            println!("    {}", Theme::dim().render(&participants_str));
        }
    }
    if r.truncated {
        println!(
            "  {}",
            Theme::dim().render("(showing first -n; rerun with a higher --limit)")
        );
    }
    println!();
}

pub(super) fn active_to_json(r: &ActiveResult) -> serde_json::Value {
    serde_json::json!({
        "total_unresolved_in_window": r.total_unresolved_in_window,
        "truncated": r.truncated,
        "discussions": r.discussions.iter().map(|d| serde_json::json!({
            "discussion_id": d.discussion_id,
            "entity_type": d.entity_type,
            "entity_iid": d.entity_iid,
            "entity_title": d.entity_title,
            "project_path": d.project_path,
            "last_note_at": ms_to_iso(d.last_note_at),
            "note_count": d.note_count,
            "participants": d.participants,
            "participants_total": d.participants_total,
            "participants_truncated": d.participants_truncated,
        })).collect::<Vec<_>>(),
    })
}
