use crate::cli::render::{self, Align, Icons, StyledCell, Table as LoreTable, Theme};
use rusqlite::Connection;
use serde::Serialize;

use crate::Config;
use crate::cli::robot::{RobotMeta, expand_fields_preset, filter_fields};
use crate::core::db::create_connection;
use crate::core::error::{LoreError, Result};
use crate::core::paths::get_db_path;
use crate::core::project::resolve_project;
use crate::core::time::{ms_to_iso, parse_since};

use super::render_helpers::{format_branches, format_discussions};

#[derive(Debug, Serialize)]
pub struct MrListRow {
    pub iid: i64,
    pub title: 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,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub web_url: Option<String>,
    pub project_path: String,
    pub labels: Vec<String>,
    pub assignees: Vec<String>,
    pub reviewers: Vec<String>,
    pub discussion_count: i64,
    pub unresolved_count: i64,
}

#[derive(Serialize)]
pub struct MrListRowJson {
    pub iid: i64,
    pub title: String,
    pub state: String,
    pub draft: bool,
    pub author_username: String,
    pub source_branch: String,
    pub target_branch: String,
    pub labels: Vec<String>,
    pub assignees: Vec<String>,
    pub reviewers: Vec<String>,
    pub discussion_count: i64,
    pub unresolved_count: i64,
    pub created_at_iso: String,
    pub updated_at_iso: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub web_url: Option<String>,
    pub project_path: String,
}

impl From<&MrListRow> for MrListRowJson {
    fn from(row: &MrListRow) -> Self {
        Self {
            iid: row.iid,
            title: row.title.clone(),
            state: row.state.clone(),
            draft: row.draft,
            author_username: row.author_username.clone(),
            source_branch: row.source_branch.clone(),
            target_branch: row.target_branch.clone(),
            labels: row.labels.clone(),
            assignees: row.assignees.clone(),
            reviewers: row.reviewers.clone(),
            discussion_count: row.discussion_count,
            unresolved_count: row.unresolved_count,
            created_at_iso: ms_to_iso(row.created_at),
            updated_at_iso: ms_to_iso(row.updated_at),
            web_url: row.web_url.clone(),
            project_path: row.project_path.clone(),
        }
    }
}

#[derive(Serialize)]
pub struct MrListResult {
    pub mrs: Vec<MrListRow>,
    pub total_count: usize,
}

#[derive(Serialize)]
pub struct MrListResultJson {
    pub mrs: Vec<MrListRowJson>,
    pub total_count: usize,
    pub showing: usize,
}

impl From<&MrListResult> for MrListResultJson {
    fn from(result: &MrListResult) -> Self {
        Self {
            mrs: result.mrs.iter().map(MrListRowJson::from).collect(),
            total_count: result.total_count,
            showing: result.mrs.len(),
        }
    }
}

pub struct MrListFilters<'a> {
    pub limit: usize,
    pub project: Option<&'a str>,
    pub state: Option<&'a str>,
    pub author: Option<&'a str>,
    pub assignee: Option<&'a str>,
    pub reviewer: Option<&'a str>,
    pub labels: Option<&'a [String]>,
    pub since: Option<&'a str>,
    pub draft: bool,
    pub no_draft: bool,
    pub target_branch: Option<&'a str>,
    pub source_branch: Option<&'a str>,
    pub sort: &'a str,
    pub order: &'a str,
}

pub fn run_list_mrs(config: &Config, filters: MrListFilters) -> Result<MrListResult> {
    let db_path = get_db_path(config.storage.db_path.as_deref());
    let conn = create_connection(&db_path)?;

    let result = query_mrs(&conn, &filters)?;
    Ok(result)
}

fn query_mrs(conn: &Connection, filters: &MrListFilters) -> Result<MrListResult> {
    let mut where_clauses = Vec::new();
    let mut params: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();

    if let Some(project) = filters.project {
        let project_id = resolve_project(conn, project)?;
        where_clauses.push("m.project_id = ?");
        params.push(Box::new(project_id));
    }

    if let Some(state) = filters.state
        && state != "all"
    {
        where_clauses.push("m.state = ?");
        params.push(Box::new(state.to_string()));
    }

    if let Some(author) = filters.author {
        let username = author.strip_prefix('@').unwrap_or(author);
        where_clauses.push("m.author_username = ?");
        params.push(Box::new(username.to_string()));
    }

    if let Some(assignee) = filters.assignee {
        let username = assignee.strip_prefix('@').unwrap_or(assignee);
        where_clauses.push(
            "EXISTS (SELECT 1 FROM mr_assignees ma
             WHERE ma.merge_request_id = m.id AND ma.username = ?)",
        );
        params.push(Box::new(username.to_string()));
    }

    if let Some(reviewer) = filters.reviewer {
        let username = reviewer.strip_prefix('@').unwrap_or(reviewer);
        where_clauses.push(
            "EXISTS (SELECT 1 FROM mr_reviewers mr
             WHERE mr.merge_request_id = m.id AND mr.username = ?)",
        );
        params.push(Box::new(username.to_string()));
    }

    if let Some(since_str) = filters.since {
        let cutoff_ms = parse_since(since_str).ok_or_else(|| {
            LoreError::Other(format!(
                "Invalid --since value '{}'. Use relative (7d, 2w, 1m) or absolute (YYYY-MM-DD) format.",
                since_str
            ))
        })?;
        where_clauses.push("m.updated_at >= ?");
        params.push(Box::new(cutoff_ms));
    }

    if let Some(labels) = filters.labels {
        for label in labels {
            where_clauses.push(
                "EXISTS (SELECT 1 FROM mr_labels ml
                 JOIN labels l ON ml.label_id = l.id
                 WHERE ml.merge_request_id = m.id AND l.name = ?)",
            );
            params.push(Box::new(label.clone()));
        }
    }

    if filters.draft {
        where_clauses.push("m.draft = 1");
    } else if filters.no_draft {
        where_clauses.push("m.draft = 0");
    }

    if let Some(target_branch) = filters.target_branch {
        where_clauses.push("m.target_branch = ?");
        params.push(Box::new(target_branch.to_string()));
    }

    if let Some(source_branch) = filters.source_branch {
        where_clauses.push("m.source_branch = ?");
        params.push(Box::new(source_branch.to_string()));
    }

    let where_sql = if where_clauses.is_empty() {
        String::new()
    } else {
        format!("WHERE {}", where_clauses.join(" AND "))
    };

    let count_sql = format!(
        "SELECT COUNT(*) FROM merge_requests m
         JOIN projects p ON m.project_id = p.id
         {where_sql}"
    );

    let param_refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|p| p.as_ref()).collect();
    let total_count: i64 = conn.query_row(&count_sql, param_refs.as_slice(), |row| row.get(0))?;
    let total_count = total_count as usize;

    let sort_column = match filters.sort {
        "created" => "m.created_at",
        "iid" => "m.iid",
        _ => "m.updated_at",
    };
    let order = if filters.order == "asc" {
        "ASC"
    } else {
        "DESC"
    };

    let query_sql = format!(
        "SELECT
            m.iid,
            m.title,
            m.state,
            m.draft,
            m.author_username,
            m.source_branch,
            m.target_branch,
            m.created_at,
            m.updated_at,
            m.web_url,
            p.path_with_namespace,
            (SELECT GROUP_CONCAT(l.name, X'1F')
             FROM mr_labels ml
             JOIN labels l ON ml.label_id = l.id
             WHERE ml.merge_request_id = m.id) AS labels_csv,
            (SELECT GROUP_CONCAT(ma.username, X'1F')
             FROM mr_assignees ma
             WHERE ma.merge_request_id = m.id) AS assignees_csv,
            (SELECT GROUP_CONCAT(mr.username, X'1F')
             FROM mr_reviewers mr
             WHERE mr.merge_request_id = m.id) AS reviewers_csv,
            (SELECT COUNT(*) FROM discussions d
             WHERE d.merge_request_id = m.id) AS discussion_count,
            (SELECT COUNT(*) FROM discussions d
             WHERE d.merge_request_id = m.id AND d.resolvable = 1 AND d.resolved = 0) AS unresolved_count
         FROM merge_requests m
         JOIN projects p ON m.project_id = p.id
         {where_sql}
         ORDER BY {sort_column} {order}
         LIMIT ?"
    );

    params.push(Box::new(filters.limit as i64));
    let param_refs: Vec<&dyn rusqlite::ToSql> = params.iter().map(|p| p.as_ref()).collect();

    let mut stmt = conn.prepare(&query_sql)?;
    let mrs: Vec<MrListRow> = stmt
        .query_map(param_refs.as_slice(), |row| {
            let labels_csv: Option<String> = row.get(11)?;
            let labels = labels_csv
                .map(|s| s.split('\x1F').map(String::from).collect())
                .unwrap_or_default();

            let assignees_csv: Option<String> = row.get(12)?;
            let assignees = assignees_csv
                .map(|s| s.split('\x1F').map(String::from).collect())
                .unwrap_or_default();

            let reviewers_csv: Option<String> = row.get(13)?;
            let reviewers = reviewers_csv
                .map(|s| s.split('\x1F').map(String::from).collect())
                .unwrap_or_default();

            let draft_int: i64 = row.get(3)?;

            Ok(MrListRow {
                iid: row.get(0)?,
                title: row.get(1)?,
                state: row.get(2)?,
                draft: draft_int == 1,
                author_username: row.get(4)?,
                source_branch: row.get(5)?,
                target_branch: row.get(6)?,
                created_at: row.get(7)?,
                updated_at: row.get(8)?,
                web_url: row.get(9)?,
                project_path: row.get(10)?,
                labels,
                assignees,
                reviewers,
                discussion_count: row.get(14)?,
                unresolved_count: row.get(15)?,
            })
        })?
        .collect::<std::result::Result<Vec<_>, _>>()?;

    Ok(MrListResult { mrs, total_count })
}

pub fn print_list_mrs(result: &MrListResult) {
    if result.mrs.is_empty() {
        println!("No merge requests found.");
        return;
    }

    println!(
        "{}  {} of {}\n",
        Theme::bold().render("Merge Requests"),
        result.mrs.len(),
        result.total_count
    );

    let mut table = LoreTable::new()
        .headers(&[
            "IID", "Title", "State", "Author", "Branches", "Disc", "Updated",
        ])
        .align(0, Align::Right)
        .flex_col(1);

    for mr in &result.mrs {
        let title = if mr.draft {
            format!("{} {}", Icons::mr_draft(), mr.title)
        } else {
            mr.title.clone()
        };

        let relative_time = render::format_relative_time_compact(mr.updated_at);
        let branches = format_branches(&mr.target_branch, &mr.source_branch);
        let discussions = format_discussions(mr.discussion_count, mr.unresolved_count);

        let (icon, style) = match mr.state.as_str() {
            "opened" => (Icons::mr_opened(), Theme::success()),
            "merged" => (Icons::mr_merged(), Theme::accent()),
            "closed" => (Icons::mr_closed(), Theme::error()),
            "locked" => (Icons::mr_opened(), Theme::warning()),
            _ => (Icons::mr_opened(), Theme::dim()),
        };
        let state_cell = StyledCell::styled(format!("{icon} {}", mr.state), style);

        table.add_row(vec![
            StyledCell::styled(format!("!{}", mr.iid), Theme::info()),
            StyledCell::plain(title),
            state_cell,
            StyledCell::styled(format!("@{}", mr.author_username), Theme::accent()),
            StyledCell::styled(branches, Theme::info()),
            discussions,
            StyledCell::styled(relative_time, Theme::dim()),
        ]);
    }

    println!("{}", table.render());
}

pub fn print_list_mrs_json(result: &MrListResult, elapsed_ms: u64, fields: Option<&[String]>) {
    let json_result = MrListResultJson::from(result);
    let meta = RobotMeta::new(elapsed_ms);
    let output = serde_json::json!({
        "ok": true,
        "data": json_result,
        "meta": meta,
    });
    let mut output = output;
    if let Some(f) = fields {
        let expanded = expand_fields_preset(f, "mrs");
        filter_fields(&mut output, "mrs", &expanded);
    }
    match serde_json::to_string(&output) {
        Ok(json) => println!("{json}"),
        Err(e) => eprintln!("Error serializing to JSON: {e}"),
    }
}

pub fn open_mr_in_browser(result: &MrListResult) -> Option<String> {
    let first_mr = result.mrs.first()?;
    let url = first_mr.web_url.as_ref()?;

    match open::that(url) {
        Ok(()) => {
            println!("Opened: {url}");
            Some(url.clone())
        }
        Err(e) => {
            eprintln!("Failed to open browser: {e}");
            None
        }
    }
}
