#[derive(Debug, Serialize)]
pub struct MrDetail {
    pub id: i64,
    pub iid: i64,
    pub title: String,
    pub description: Option<String>,
    pub state: String,
    pub draft: bool,
    pub author_username: String,
    pub source_branch: String,
    pub target_branch: String,
    pub created_at: i64,
    pub updated_at: i64,
    pub merged_at: Option<i64>,
    pub closed_at: Option<i64>,
    pub web_url: Option<String>,
    pub project_path: String,
    pub labels: Vec<String>,
    pub assignees: Vec<String>,
    pub reviewers: Vec<String>,
    pub discussions: Vec<MrDiscussionDetail>,
}

#[derive(Debug, Serialize)]
pub struct MrDiscussionDetail {
    pub notes: Vec<MrNoteDetail>,
    pub individual_note: bool,
}

#[derive(Debug, Serialize)]
pub struct MrNoteDetail {
    pub gitlab_id: i64,
    pub author_username: String,
    pub body: String,
    pub created_at: i64,
    pub is_system: bool,
    pub position: Option<DiffNotePosition>,
}

#[derive(Debug, Clone, Serialize)]
pub struct DiffNotePosition {
    pub old_path: Option<String>,
    pub new_path: Option<String>,
    pub old_line: Option<i64>,
    pub new_line: Option<i64>,
    pub position_type: Option<String>,
}
pub fn run_show_mr(config: &Config, iid: i64, project_filter: Option<&str>) -> Result<MrDetail> {
    let db_path = get_db_path(config.storage.db_path.as_deref());
    let conn = create_connection(&db_path)?;

    let mr = find_mr(&conn, iid, project_filter)?;

    let labels = get_mr_labels(&conn, mr.id)?;

    let assignees = get_mr_assignees(&conn, mr.id)?;

    let reviewers = get_mr_reviewers(&conn, mr.id)?;

    let discussions = get_mr_discussions(&conn, mr.id)?;

    Ok(MrDetail {
        id: mr.id,
        iid: mr.iid,
        title: mr.title,
        description: mr.description,
        state: mr.state,
        draft: mr.draft,
        author_username: mr.author_username,
        source_branch: mr.source_branch,
        target_branch: mr.target_branch,
        created_at: mr.created_at,
        updated_at: mr.updated_at,
        merged_at: mr.merged_at,
        closed_at: mr.closed_at,
        web_url: mr.web_url,
        project_path: mr.project_path,
        labels,
        assignees,
        reviewers,
        discussions,
    })
}

struct MrRow {
    id: i64,
    iid: i64,
    title: String,
    description: Option<String>,
    state: String,
    draft: bool,
    author_username: String,
    source_branch: String,
    target_branch: String,
    created_at: i64,
    updated_at: i64,
    merged_at: Option<i64>,
    closed_at: Option<i64>,
    web_url: Option<String>,
    project_path: String,
}

fn find_mr(conn: &Connection, iid: i64, project_filter: Option<&str>) -> Result<MrRow> {
    let (sql, params): (&str, Vec<Box<dyn rusqlite::ToSql>>) = match project_filter {
        Some(project) => {
            let project_id = resolve_project(conn, project)?;
            (
                "SELECT m.id, m.iid, m.title, m.description, m.state, m.draft,
                        m.author_username, m.source_branch, m.target_branch,
                        m.created_at, m.updated_at, m.merged_at, m.closed_at,
                        m.web_url, p.path_with_namespace
                 FROM merge_requests m
                 JOIN projects p ON m.project_id = p.id
                 WHERE m.iid = ? AND m.project_id = ?",
                vec![Box::new(iid), Box::new(project_id)],
            )
        }
        None => (
            "SELECT m.id, m.iid, m.title, m.description, m.state, m.draft,
                    m.author_username, m.source_branch, m.target_branch,
                    m.created_at, m.updated_at, m.merged_at, m.closed_at,
                    m.web_url, p.path_with_namespace
             FROM merge_requests m
             JOIN projects p ON m.project_id = p.id
             WHERE m.iid = ?",
            vec![Box::new(iid)],
        ),
    };

    let param_refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|p| p.as_ref()).collect();

    let mut stmt = conn.prepare(sql)?;
    let mrs: Vec<MrRow> = stmt
        .query_map(param_refs.as_slice(), |row| {
            let draft_val: i64 = row.get(5)?;
            Ok(MrRow {
                id: row.get(0)?,
                iid: row.get(1)?,
                title: row.get(2)?,
                description: row.get(3)?,
                state: row.get(4)?,
                draft: draft_val == 1,
                author_username: row.get(6)?,
                source_branch: row.get(7)?,
                target_branch: row.get(8)?,
                created_at: row.get(9)?,
                updated_at: row.get(10)?,
                merged_at: row.get(11)?,
                closed_at: row.get(12)?,
                web_url: row.get(13)?,
                project_path: row.get(14)?,
            })
        })?
        .collect::<std::result::Result<Vec<_>, _>>()?;

    match mrs.len() {
        0 => Err(LoreError::NotFound(format!("MR !{} not found", iid))),
        1 => Ok(mrs.into_iter().next().unwrap()),
        _ => {
            let projects: Vec<String> = mrs.iter().map(|m| m.project_path.clone()).collect();
            Err(LoreError::Ambiguous(format!(
                "MR !{} exists in multiple projects: {}. Use --project to specify.",
                iid,
                projects.join(", ")
            )))
        }
    }
}

fn get_mr_labels(conn: &Connection, mr_id: i64) -> Result<Vec<String>> {
    let mut stmt = conn.prepare(
        "SELECT l.name FROM labels l
         JOIN mr_labels ml ON l.id = ml.label_id
         WHERE ml.merge_request_id = ?
         ORDER BY l.name",
    )?;

    let labels: Vec<String> = stmt
        .query_map([mr_id], |row| row.get(0))?
        .collect::<std::result::Result<Vec<_>, _>>()?;

    Ok(labels)
}

fn get_mr_assignees(conn: &Connection, mr_id: i64) -> Result<Vec<String>> {
    let mut stmt = conn.prepare(
        "SELECT username FROM mr_assignees
         WHERE merge_request_id = ?
         ORDER BY username",
    )?;

    let assignees: Vec<String> = stmt
        .query_map([mr_id], |row| row.get(0))?
        .collect::<std::result::Result<Vec<_>, _>>()?;

    Ok(assignees)
}

fn get_mr_reviewers(conn: &Connection, mr_id: i64) -> Result<Vec<String>> {
    let mut stmt = conn.prepare(
        "SELECT username FROM mr_reviewers
         WHERE merge_request_id = ?
         ORDER BY username",
    )?;

    let reviewers: Vec<String> = stmt
        .query_map([mr_id], |row| row.get(0))?
        .collect::<std::result::Result<Vec<_>, _>>()?;

    Ok(reviewers)
}

fn get_mr_discussions(conn: &Connection, mr_id: i64) -> Result<Vec<MrDiscussionDetail>> {
    let mut disc_stmt = conn.prepare(
        "SELECT id, individual_note FROM discussions
         WHERE merge_request_id = ?
         ORDER BY first_note_at",
    )?;

    let disc_rows: Vec<(i64, bool)> = disc_stmt
        .query_map([mr_id], |row| {
            let individual: i64 = row.get(1)?;
            Ok((row.get(0)?, individual == 1))
        })?
        .collect::<std::result::Result<Vec<_>, _>>()?;

    let mut note_stmt = conn.prepare(
        "SELECT gitlab_id, author_username, body, created_at, is_system,
                position_old_path, position_new_path, position_old_line,
                position_new_line, position_type
         FROM notes
         WHERE discussion_id = ?
         ORDER BY position",
    )?;

    let mut discussions = Vec::new();
    for (disc_id, individual_note) in disc_rows {
        let notes: Vec<MrNoteDetail> = note_stmt
            .query_map([disc_id], |row| {
                let is_system: i64 = row.get(4)?;
                let old_path: Option<String> = row.get(5)?;
                let new_path: Option<String> = row.get(6)?;
                let old_line: Option<i64> = row.get(7)?;
                let new_line: Option<i64> = row.get(8)?;
                let position_type: Option<String> = row.get(9)?;

                let position = if old_path.is_some()
                    || new_path.is_some()
                    || old_line.is_some()
                    || new_line.is_some()
                {
                    Some(DiffNotePosition {
                        old_path,
                        new_path,
                        old_line,
                        new_line,
                        position_type,
                    })
                } else {
                    None
                };

                Ok(MrNoteDetail {
                    gitlab_id: row.get(0)?,
                    author_username: row.get(1)?,
                    body: row.get(2)?,
                    created_at: row.get(3)?,
                    is_system: is_system == 1,
                    position,
                })
            })?
            .collect::<std::result::Result<Vec<_>, _>>()?;

        let has_user_notes = notes.iter().any(|n| !n.is_system);
        if has_user_notes || notes.is_empty() {
            discussions.push(MrDiscussionDetail {
                notes,
                individual_note,
            });
        }
    }

    Ok(discussions)
}

