use rusqlite::{Connection, OptionalExtension};
use tracing::info;

use crate::core::error::Result;
use crate::core::time::now_ms;

pub fn extract_refs_from_state_events(conn: &Connection, project_id: i64) -> Result<usize> {
    let changes = conn.execute(
        "INSERT OR IGNORE INTO entity_references (
            project_id,
            source_entity_type, source_entity_id,
            target_entity_type, target_entity_id,
            reference_type, source_method, created_at
        )
        SELECT
            rse.project_id,
            'merge_request',
            mr.id,
            'issue',
            rse.issue_id,
            'closes',
            'api',
            rse.created_at
        FROM resource_state_events rse
        JOIN merge_requests mr
            ON mr.project_id = rse.project_id
            AND mr.iid = rse.source_merge_request_iid
        WHERE rse.source_merge_request_iid IS NOT NULL
          AND rse.issue_id IS NOT NULL
          AND rse.project_id = ?1",
        rusqlite::params![project_id],
    )?;

    if changes > 0 {
        info!(
            project_id,
            references_inserted = changes,
            "Extracted cross-references from state events"
        );
    }

    Ok(changes)
}

#[derive(Debug, Clone)]
pub struct EntityReference<'a> {
    pub project_id: i64,
    pub source_entity_type: &'a str,
    pub source_entity_id: i64,
    pub target_entity_type: &'a str,
    pub target_entity_id: Option<i64>,
    pub target_project_path: Option<&'a str>,
    pub target_entity_iid: Option<i64>,
    pub reference_type: &'a str,
    pub source_method: &'a str,
}

pub fn insert_entity_reference(conn: &Connection, ref_: &EntityReference<'_>) -> Result<bool> {
    let now = now_ms();
    let changes = conn.execute(
        "INSERT OR IGNORE INTO entity_references \
         (project_id, source_entity_type, source_entity_id, \
          target_entity_type, target_entity_id, target_project_path, target_entity_iid, \
          reference_type, source_method, created_at) \
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
        rusqlite::params![
            ref_.project_id,
            ref_.source_entity_type,
            ref_.source_entity_id,
            ref_.target_entity_type,
            ref_.target_entity_id,
            ref_.target_project_path,
            ref_.target_entity_iid,
            ref_.reference_type,
            ref_.source_method,
            now,
        ],
    )?;

    Ok(changes > 0)
}

pub fn resolve_issue_local_id(
    conn: &Connection,
    project_id: i64,
    issue_iid: i64,
) -> Result<Option<i64>> {
    let mut stmt =
        conn.prepare_cached("SELECT id FROM issues WHERE project_id = ?1 AND iid = ?2")?;

    let result = stmt
        .query_row(rusqlite::params![project_id, issue_iid], |row| row.get(0))
        .optional()?;

    Ok(result)
}

pub fn resolve_project_path(conn: &Connection, gitlab_project_id: i64) -> Result<Option<String>> {
    let mut stmt = conn
        .prepare_cached("SELECT path_with_namespace FROM projects WHERE gitlab_project_id = ?1")?;

    let result = stmt
        .query_row(rusqlite::params![gitlab_project_id], |row| row.get(0))
        .optional()?;

    Ok(result)
}

pub fn count_references_for_source(
    conn: &Connection,
    source_entity_type: &str,
    source_entity_id: i64,
) -> Result<usize> {
    let count: i64 = conn.query_row(
        "SELECT COUNT(*) FROM entity_references \
         WHERE source_entity_type = ?1 AND source_entity_id = ?2",
        rusqlite::params![source_entity_type, source_entity_id],
        |row| row.get(0),
    )?;

    Ok(count as usize)
}

#[cfg(test)]
#[path = "references_tests.rs"]
mod tests;
