#![allow(dead_code)]

use anyhow::{Context, Result};
use rusqlite::Connection;

use crate::message::EntityKey;
use crate::state::issue_detail::{IssueDetailData, IssueMetadata};
use crate::view::common::cross_ref::{CrossRef, CrossRefKind};
use crate::view::common::discussion_tree::{DiscussionNode, NoteNode};

/// Fetch issue metadata and cross-references (Phase 1 load).
///
/// Runs inside a single read transaction for snapshot consistency.
/// Returns metadata + cross-refs; discussions are loaded separately.
pub fn fetch_issue_detail(conn: &Connection, key: &EntityKey) -> Result<IssueDetailData> {
    let metadata = fetch_issue_metadata(conn, key)?;
    let cross_refs = fetch_issue_cross_refs(conn, key)?;
    Ok(IssueDetailData {
        metadata,
        cross_refs,
    })
}

/// Fetch issue metadata from the local DB.
fn fetch_issue_metadata(conn: &Connection, key: &EntityKey) -> Result<IssueMetadata> {
    let row = conn
        .query_row(
            "SELECT i.iid, p.path_with_namespace, i.title,
                COALESCE(i.description, ''), i.state, i.author_username,
                COALESCE(i.milestone_title, ''),
                i.due_date, i.created_at, i.updated_at,
                COALESCE(i.web_url, ''),
                (SELECT COUNT(*) FROM discussions d
                 WHERE d.issue_id = i.id AND d.noteable_type = 'Issue')
         FROM issues i
         JOIN projects p ON p.id = i.project_id
         WHERE i.project_id = ?1 AND i.iid = ?2",
            rusqlite::params![key.project_id, key.iid],
            |row| {
                Ok(IssueMetadata {
                    iid: row.get(0)?,
                    project_path: row.get(1)?,
                    title: row.get(2)?,
                    description: row.get(3)?,
                    state: row.get(4)?,
                    author: row.get::<_, Option<String>>(5)?.unwrap_or_default(),
                    assignees: Vec::new(), // Fetched separately below.
                    labels: Vec::new(),    // Fetched separately below.
                    milestone: {
                        let m: String = row.get(6)?;
                        if m.is_empty() { None } else { Some(m) }
                    },
                    due_date: row.get(7)?,
                    created_at: row.get(8)?,
                    updated_at: row.get(9)?,
                    web_url: row.get(10)?,
                    discussion_count: row.get::<_, i64>(11)? as usize,
                })
            },
        )
        .context("fetching issue metadata")?;

    // Fetch assignees.
    let mut assignees_stmt = conn
        .prepare("SELECT username FROM issue_assignees WHERE issue_id = (SELECT id FROM issues WHERE project_id = ?1 AND iid = ?2)")
        .context("preparing assignees query")?;
    let assignees: Vec<String> = assignees_stmt
        .query_map(rusqlite::params![key.project_id, key.iid], |r| r.get(0))
        .context("fetching assignees")?
        .collect::<std::result::Result<Vec<_>, _>>()
        .context("reading assignee row")?;

    // Fetch labels.
    let mut labels_stmt = conn
        .prepare(
            "SELECT l.name FROM issue_labels il
             JOIN labels l ON l.id = il.label_id
             WHERE il.issue_id = (SELECT id FROM issues WHERE project_id = ?1 AND iid = ?2)
             ORDER BY l.name",
        )
        .context("preparing labels query")?;
    let labels: Vec<String> = labels_stmt
        .query_map(rusqlite::params![key.project_id, key.iid], |r| r.get(0))
        .context("fetching labels")?
        .collect::<std::result::Result<Vec<_>, _>>()
        .context("reading label row")?;

    Ok(IssueMetadata {
        assignees,
        labels,
        ..row
    })
}

/// Fetch cross-references for an issue from the entity_references table.
fn fetch_issue_cross_refs(conn: &Connection, key: &EntityKey) -> Result<Vec<CrossRef>> {
    let mut stmt = conn
        .prepare(
            "SELECT er.reference_type, er.target_entity_type, er.target_entity_id,
                    er.target_entity_iid, er.target_project_path,
                    CASE
                        WHEN er.target_entity_type = 'issue'
                            THEN (SELECT title FROM issues WHERE id = er.target_entity_id)
                        WHEN er.target_entity_type = 'merge_request'
                            THEN (SELECT title FROM merge_requests WHERE id = er.target_entity_id)
                        ELSE NULL
                    END as entity_title,
                    CASE
                        WHEN er.target_entity_id IS NOT NULL
                            THEN (SELECT project_id FROM issues WHERE id = er.target_entity_id
                                  UNION ALL
                                  SELECT project_id FROM merge_requests WHERE id = er.target_entity_id
                                  LIMIT 1)
                        ELSE NULL
                    END as target_project_id
             FROM entity_references er
             WHERE er.source_entity_type = 'issue'
               AND er.source_entity_id = (SELECT id FROM issues WHERE project_id = ?1 AND iid = ?2)
             ORDER BY er.reference_type, er.target_entity_iid",
        )
        .context("preparing cross-ref query")?;

    let refs = stmt
        .query_map(rusqlite::params![key.project_id, key.iid], |row| {
            let ref_type: String = row.get(0)?;
            let target_type: String = row.get(1)?;
            let target_id: Option<i64> = row.get(2)?;
            let target_iid: Option<i64> = row.get(3)?;
            let target_path: Option<String> = row.get(4)?;
            let title: Option<String> = row.get(5)?;
            let target_project_id: Option<i64> = row.get(6)?;

            let kind = match (ref_type.as_str(), target_type.as_str()) {
                ("closes", "merge_request") => CrossRefKind::ClosingMr,
                ("related", "issue") => CrossRefKind::RelatedIssue,
                _ => CrossRefKind::MentionedIn,
            };

            let iid = target_iid.unwrap_or(0);
            let project_id = target_project_id.unwrap_or(key.project_id);

            let entity_key = match target_type.as_str() {
                "merge_request" => EntityKey::mr(project_id, iid),
                _ => EntityKey::issue(project_id, iid),
            };

            let label = title.unwrap_or_else(|| {
                let prefix = if target_type == "merge_request" {
                    "!"
                } else {
                    "#"
                };
                let path = target_path.unwrap_or_default();
                if path.is_empty() {
                    format!("{prefix}{iid}")
                } else {
                    format!("{path}{prefix}{iid}")
                }
            });

            let navigable = target_id.is_some();

            Ok(CrossRef {
                kind,
                entity_key,
                label,
                navigable,
            })
        })
        .context("fetching cross-refs")?
        .collect::<std::result::Result<Vec<_>, _>>()
        .context("reading cross-ref row")?;

    Ok(refs)
}

/// Fetch discussions for an issue (Phase 2 async load).
///
/// Returns `DiscussionNode` tree suitable for the discussion tree widget.
pub fn fetch_issue_discussions(conn: &Connection, key: &EntityKey) -> Result<Vec<DiscussionNode>> {
    let issue_id: i64 = conn
        .query_row(
            "SELECT id FROM issues WHERE project_id = ?1 AND iid = ?2",
            rusqlite::params![key.project_id, key.iid],
            |r| r.get(0),
        )
        .context("looking up issue id")?;

    let mut disc_stmt = conn
        .prepare(
            "SELECT d.id, d.gitlab_discussion_id, d.resolvable, d.resolved
             FROM discussions d
             WHERE d.issue_id = ?1 AND d.noteable_type = 'Issue'
             ORDER BY d.first_note_at ASC, d.id ASC",
        )
        .context("preparing discussions query")?;

    let mut note_stmt = conn
        .prepare(
            "SELECT n.author_username, n.body, n.created_at, n.is_system,
                    n.note_type, n.position_new_path, n.position_new_line
             FROM notes n
             WHERE n.discussion_id = ?1
             ORDER BY n.position ASC, n.created_at ASC",
        )
        .context("preparing notes query")?;

    let disc_rows: Vec<_> = disc_stmt
        .query_map(rusqlite::params![issue_id], |row| {
            Ok((
                row.get::<_, i64>(0)?,    // id
                row.get::<_, String>(1)?, // gitlab_discussion_id
                row.get::<_, bool>(2)?,   // resolvable
                row.get::<_, bool>(3)?,   // resolved
            ))
        })
        .context("fetching discussions")?
        .collect::<std::result::Result<Vec<_>, _>>()
        .context("reading discussion row")?;

    let mut discussions = Vec::new();
    for (disc_db_id, discussion_id, resolvable, resolved) in disc_rows {
        let notes: Vec<NoteNode> = note_stmt
            .query_map(rusqlite::params![disc_db_id], |row| {
                Ok(NoteNode {
                    author: row.get::<_, Option<String>>(0)?.unwrap_or_default(),
                    body: row.get::<_, Option<String>>(1)?.unwrap_or_default(),
                    created_at: row.get(2)?,
                    is_system: row.get(3)?,
                    is_diff_note: row.get::<_, Option<String>>(4)?.as_deref() == Some("DiffNote"),
                    diff_file_path: row.get(5)?,
                    diff_new_line: row.get(6)?,
                })
            })
            .context("fetching notes")?
            .collect::<std::result::Result<Vec<_>, _>>()
            .context("reading note row")?;

        discussions.push(DiscussionNode {
            discussion_id,
            notes,
            resolvable,
            resolved,
        });
    }

    Ok(discussions)
}

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

    fn create_issue_detail_schema(conn: &Connection) {
        conn.execute_batch(
            "
            CREATE TABLE projects (
                id INTEGER PRIMARY KEY,
                gitlab_project_id INTEGER UNIQUE NOT NULL,
                path_with_namespace TEXT NOT NULL
            );
            CREATE TABLE issues (
                id INTEGER PRIMARY KEY,
                gitlab_id INTEGER UNIQUE NOT NULL,
                project_id INTEGER NOT NULL,
                iid INTEGER NOT NULL,
                title TEXT NOT NULL,
                description TEXT,
                state TEXT NOT NULL DEFAULT 'opened',
                author_username TEXT,
                milestone_title TEXT,
                due_date TEXT,
                created_at INTEGER NOT NULL,
                updated_at INTEGER NOT NULL,
                web_url TEXT,
                UNIQUE(project_id, iid)
            );
            CREATE TABLE issue_assignees (
                issue_id INTEGER NOT NULL,
                username TEXT NOT NULL,
                UNIQUE(issue_id, username)
            );
            CREATE TABLE labels (
                id INTEGER PRIMARY KEY,
                project_id INTEGER NOT NULL,
                name TEXT NOT NULL
            );
            CREATE TABLE issue_labels (
                issue_id INTEGER NOT NULL,
                label_id INTEGER NOT NULL,
                UNIQUE(issue_id, label_id)
            );
            CREATE TABLE discussions (
                id INTEGER PRIMARY KEY,
                gitlab_discussion_id TEXT NOT NULL,
                project_id INTEGER NOT NULL,
                issue_id INTEGER,
                merge_request_id INTEGER,
                noteable_type TEXT NOT NULL,
                resolvable INTEGER NOT NULL DEFAULT 0,
                resolved INTEGER NOT NULL DEFAULT 0,
                first_note_at INTEGER
            );
            CREATE TABLE notes (
                id INTEGER PRIMARY KEY,
                gitlab_id INTEGER UNIQUE NOT NULL,
                discussion_id INTEGER NOT NULL,
                project_id INTEGER NOT NULL,
                note_type TEXT,
                is_system INTEGER NOT NULL DEFAULT 0,
                author_username TEXT,
                body TEXT,
                created_at INTEGER NOT NULL,
                updated_at INTEGER NOT NULL,
                position INTEGER,
                position_new_path TEXT,
                position_new_line INTEGER
            );
            CREATE TABLE entity_references (
                id INTEGER PRIMARY KEY,
                project_id INTEGER NOT NULL,
                source_entity_type TEXT NOT NULL,
                source_entity_id INTEGER NOT NULL,
                target_entity_type TEXT NOT NULL,
                target_entity_id INTEGER,
                target_project_path TEXT,
                target_entity_iid INTEGER,
                reference_type TEXT NOT NULL,
                source_method TEXT NOT NULL DEFAULT 'api',
                created_at INTEGER NOT NULL DEFAULT 0
            );
            CREATE TABLE merge_requests (
                id INTEGER PRIMARY KEY,
                gitlab_id INTEGER UNIQUE NOT NULL,
                project_id INTEGER NOT NULL,
                iid INTEGER NOT NULL,
                title TEXT NOT NULL,
                state TEXT NOT NULL DEFAULT 'opened',
                UNIQUE(project_id, iid)
            );
            ",
        )
        .unwrap();
    }

    fn setup_issue_detail_data(conn: &Connection) {
        // Project.
        conn.execute(
            "INSERT INTO projects (id, gitlab_project_id, path_with_namespace) VALUES (1, 100, 'group/project')",
            [],
        )
        .unwrap();

        // Issue.
        conn.execute(
            "INSERT INTO issues (id, gitlab_id, project_id, iid, title, description, state, author_username, milestone_title, due_date, created_at, updated_at, web_url)
             VALUES (1, 1000, 1, 42, 'Fix authentication flow', 'Detailed description here', 'opened', 'alice', 'v1.0', '2026-03-01', 1700000000000, 1700000060000, 'https://gitlab.com/group/project/-/issues/42')",
            [],
        )
        .unwrap();

        // Assignees.
        conn.execute(
            "INSERT INTO issue_assignees (issue_id, username) VALUES (1, 'bob')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO issue_assignees (issue_id, username) VALUES (1, 'charlie')",
            [],
        )
        .unwrap();

        // Labels.
        conn.execute(
            "INSERT INTO labels (id, project_id, name) VALUES (1, 1, 'backend')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO labels (id, project_id, name) VALUES (2, 1, 'urgent')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO issue_labels (issue_id, label_id) VALUES (1, 1)",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO issue_labels (issue_id, label_id) VALUES (1, 2)",
            [],
        )
        .unwrap();

        // Discussions + notes.
        conn.execute(
            "INSERT INTO discussions (id, gitlab_discussion_id, project_id, issue_id, noteable_type, resolvable, resolved, first_note_at)
             VALUES (1, 'disc-aaa', 1, 1, 'Issue', 0, 0, 1700000010000)",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO notes (id, gitlab_id, discussion_id, project_id, author_username, body, created_at, updated_at, position, is_system, note_type)
             VALUES (1, 10001, 1, 1, 'alice', 'This looks good overall', 1700000010000, 1700000010000, 0, 0, 'DiscussionNote')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO notes (id, gitlab_id, discussion_id, project_id, author_username, body, created_at, updated_at, position, is_system, note_type)
             VALUES (2, 10002, 1, 1, 'bob', 'Agreed, but see my comment below', 1700000020000, 1700000020000, 1, 0, 'DiscussionNote')",
            [],
        )
        .unwrap();

        // System note discussion.
        conn.execute(
            "INSERT INTO discussions (id, gitlab_discussion_id, project_id, issue_id, noteable_type, first_note_at)
             VALUES (2, 'disc-bbb', 1, 1, 'Issue', 1700000030000)",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO notes (id, gitlab_id, discussion_id, project_id, author_username, body, created_at, updated_at, position, is_system, note_type)
             VALUES (3, 10003, 2, 1, 'system', 'changed the description', 1700000030000, 1700000030000, 0, 1, NULL)",
            [],
        )
        .unwrap();

        // Closing MR cross-ref.
        conn.execute(
            "INSERT INTO merge_requests (id, gitlab_id, project_id, iid, title, state)
             VALUES (1, 2000, 1, 10, 'Fix auth MR', 'opened')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO entity_references (project_id, source_entity_type, source_entity_id, target_entity_type, target_entity_id, target_entity_iid, reference_type)
             VALUES (1, 'issue', 1, 'merge_request', 1, 10, 'closes')",
            [],
        )
        .unwrap();
    }

    #[test]
    fn test_fetch_issue_detail_basic() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let data = fetch_issue_detail(&conn, &key).unwrap();

        assert_eq!(data.metadata.iid, 42);
        assert_eq!(data.metadata.title, "Fix authentication flow");
        assert_eq!(data.metadata.state, "opened");
        assert_eq!(data.metadata.author, "alice");
        assert_eq!(data.metadata.project_path, "group/project");
        assert_eq!(data.metadata.milestone, Some("v1.0".to_string()));
        assert_eq!(data.metadata.due_date, Some("2026-03-01".to_string()));
        assert_eq!(
            data.metadata.web_url,
            "https://gitlab.com/group/project/-/issues/42"
        );
    }

    #[test]
    fn test_fetch_issue_detail_assignees() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let data = fetch_issue_detail(&conn, &key).unwrap();

        assert_eq!(data.metadata.assignees.len(), 2);
        assert!(data.metadata.assignees.contains(&"bob".to_string()));
        assert!(data.metadata.assignees.contains(&"charlie".to_string()));
    }

    #[test]
    fn test_fetch_issue_detail_labels() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let data = fetch_issue_detail(&conn, &key).unwrap();

        assert_eq!(data.metadata.labels, vec!["backend", "urgent"]);
    }

    #[test]
    fn test_fetch_issue_detail_cross_refs() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let data = fetch_issue_detail(&conn, &key).unwrap();

        assert_eq!(data.cross_refs.len(), 1);
        assert_eq!(data.cross_refs[0].kind, CrossRefKind::ClosingMr);
        assert_eq!(data.cross_refs[0].entity_key, EntityKey::mr(1, 10));
        assert_eq!(data.cross_refs[0].label, "Fix auth MR");
        assert!(data.cross_refs[0].navigable);
    }

    #[test]
    fn test_fetch_issue_detail_discussion_count() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let data = fetch_issue_detail(&conn, &key).unwrap();

        assert_eq!(data.metadata.discussion_count, 2);
    }

    #[test]
    fn test_fetch_issue_discussions_basic() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let discussions = fetch_issue_discussions(&conn, &key).unwrap();

        assert_eq!(discussions.len(), 2);
    }

    #[test]
    fn test_fetch_issue_discussions_notes() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let discussions = fetch_issue_discussions(&conn, &key).unwrap();

        // First discussion has 2 notes.
        assert_eq!(discussions[0].notes.len(), 2);
        assert_eq!(discussions[0].notes[0].author, "alice");
        assert_eq!(discussions[0].notes[0].body, "This looks good overall");
        assert_eq!(discussions[0].notes[1].author, "bob");
        assert!(!discussions[0].notes[0].is_system);
    }

    #[test]
    fn test_fetch_issue_discussions_system_note() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let discussions = fetch_issue_discussions(&conn, &key).unwrap();

        // Second discussion is a system note.
        assert_eq!(discussions[1].notes.len(), 1);
        assert!(discussions[1].notes[0].is_system);
        assert_eq!(discussions[1].notes[0].body, "changed the description");
    }

    #[test]
    fn test_fetch_issue_discussions_ordering() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 42);
        let discussions = fetch_issue_discussions(&conn, &key).unwrap();

        // Ordered by first_note_at.
        assert_eq!(discussions[0].discussion_id, "disc-aaa");
        assert_eq!(discussions[1].discussion_id, "disc-bbb");
    }

    #[test]
    fn test_fetch_issue_detail_not_found() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);
        setup_issue_detail_data(&conn);

        let key = EntityKey::issue(1, 999);
        let result = fetch_issue_detail(&conn, &key);
        assert!(result.is_err());
    }

    #[test]
    fn test_fetch_issue_detail_no_description() {
        let conn = Connection::open_in_memory().unwrap();
        create_issue_detail_schema(&conn);

        conn.execute(
            "INSERT INTO projects (id, gitlab_project_id, path_with_namespace) VALUES (1, 100, 'g/p')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO issues (id, gitlab_id, project_id, iid, title, description, state, created_at, updated_at)
             VALUES (1, 1000, 1, 1, 'No desc', NULL, 'opened', 0, 0)",
            [],
        )
        .unwrap();

        let key = EntityKey::issue(1, 1);
        let data = fetch_issue_detail(&conn, &key).unwrap();
        assert_eq!(data.metadata.description, "");
    }
}
