//! SQL file emitter that reads SQL dump files and emits Wasot events.
//!
//! This emitter creates a temporary SQLite database, executes the SQL statements,
//! and then reads from it to emit events. This allows importing SQL files that were
//! created by the SqlFileConsumer.

use crate::events::events::Emitter;
use crate::events::{BusReaderIterator, Event};
use crate::parsers::wasot::{Id, MetaLine, ParsedArguments, QuotedString};
use bus::Bus;
use sqlx::{Row, SqlitePool};
use std::fmt;
use std::fs;
use std::path::Path;
use std::sync::{Arc, Mutex};

#[derive(Debug)]
pub struct SqlFileEmitterError(pub String);

impl fmt::Display for SqlFileEmitterError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "SqlFileEmitter error: {}", self.0)
    }
}

impl std::error::Error for SqlFileEmitterError {}

impl From<sqlx::Error> for SqlFileEmitterError {
    fn from(err: sqlx::Error) -> Self {
        SqlFileEmitterError(err.to_string())
    }
}

impl From<std::io::Error> for SqlFileEmitterError {
    fn from(err: std::io::Error) -> Self {
        SqlFileEmitterError(err.to_string())
    }
}

impl From<serde_json::Error> for SqlFileEmitterError {
    fn from(err: serde_json::Error) -> Self {
        SqlFileEmitterError(err.to_string())
    }
}

/// Emitter that reads from SQL file, creates temp database, and emits Wasot events
pub struct SqlFileEmitter {
    /// Database connection pool (to temp database)
    pool: SqlitePool,
    /// Bus for events
    bus: Arc<Mutex<Bus<Event>>>,
    /// Temp file handle (kept alive to prevent deletion)
    _temp_file: tempfile::NamedTempFile,
}

impl SqlFileEmitter {
    /// Create a new SQL file emitter from a file path
    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, SqlFileEmitterError> {
        let sql_content = fs::read_to_string(path)?;
        Self::from_string(sql_content).await
    }

    /// Create a new SQL file emitter from SQL string content
    pub async fn from_string(sql_content: String) -> Result<Self, SqlFileEmitterError> {
        // Create temp database file
        let temp_file = tempfile::NamedTempFile::new()?;
        let db_path = temp_file.path().to_string_lossy().to_string();
        let database_url = format!("sqlite:{db_path}?mode=rwc");

        // Connect to temp database
        let pool = SqlitePool::connect(&database_url).await?;

        // Parse SQL statements properly (handling semicolons inside strings)
        let statements = parse_sql_statements(&sql_content);

        for statement in statements {
            let statement = statement.trim();
            if statement.is_empty() {
                continue;
            }
            // Skip comments
            if statement.starts_with("--") {
                continue;
            }
            // Skip PRAGMA statements (they may fail in different SQLite versions)
            if statement.to_uppercase().starts_with("PRAGMA") {
                let _ = sqlx::query(statement).execute(&pool).await;
                continue;
            }
            // Skip BEGIN/COMMIT (we handle transactions differently)
            let upper = statement.to_uppercase();
            if upper.starts_with("BEGIN") || upper.starts_with("COMMIT") {
                continue;
            }
            if let Err(e) = sqlx::query(statement).execute(&pool).await {
                // Log but don't fail on individual statement errors
                eprintln!("Warning: SQL statement failed: {e}");
            }
        }

        let bus = Arc::new(Mutex::new(Bus::new(1000)));

        Ok(Self {
            pool,
            bus,
            _temp_file: temp_file,
        })
    }
}

impl Emitter for SqlFileEmitter {
    type Reader = BusReaderIterator;

    fn subscribe(&self) -> Self::Reader {
        let bus_reader = self.bus.lock().unwrap().add_rx();
        BusReaderIterator::new(bus_reader)
    }

    fn run(self) {
        // Use tokio runtime to handle async operations in sync context
        let rt = tokio::runtime::Runtime::new().unwrap();
        rt.block_on(async {
            self.emit_events().await;
        });
    }
}

impl SqlFileEmitter {
    async fn emit_events(&self) {
        // Get all block elements ordered by id (which preserves insertion order)
        let blocks = match sqlx::query(
            r#"
            SELECT id, parent_id, block_id, args, content, element_type, block_type, line_idx
            FROM element
            WHERE block_type = 'block'
            ORDER BY id ASC
            "#,
        )
        .fetch_all(&self.pool)
        .await
        {
            Ok(rows) => rows,
            Err(e) => {
                eprintln!("Error fetching blocks from SQLite: {e}");
                return;
            }
        };

        // Build a map of element_id -> children for tree traversal
        let mut children_map: std::collections::HashMap<Option<i64>, Vec<i64>> =
            std::collections::HashMap::new();
        let mut element_data: std::collections::HashMap<i64, ElementData> =
            std::collections::HashMap::new();

        for row in &blocks {
            let id: i64 = row.get("id");
            let parent_id: Option<i64> = row.get("parent_id");
            let block_id: Option<String> = row.get("block_id");
            let args: String = row.get("args");
            let element_type: String = row.get("element_type");
            let line_idx: i64 = row.get("line_idx");

            children_map.entry(parent_id).or_default().push(id);
            element_data.insert(
                id,
                ElementData {
                    id,
                    parent_id,
                    block_id,
                    args,
                    element_type,
                    line_idx,
                },
            );
        }

        // Get text elements
        let texts = match sqlx::query(
            r#"
            SELECT id, parent_id, content, line_idx
            FROM element
            WHERE block_type = 'text'
            ORDER BY id ASC
            "#,
        )
        .fetch_all(&self.pool)
        .await
        {
            Ok(rows) => rows,
            Err(e) => {
                eprintln!("Error fetching text from SQLite: {e}");
                return;
            }
        };

        // Map parent_id -> text content
        let mut text_map: std::collections::HashMap<i64, Vec<String>> =
            std::collections::HashMap::new();
        for row in texts {
            let parent_id: Option<i64> = row.get("parent_id");
            let content: String = row.get("content");
            if let Some(pid) = parent_id {
                text_map.entry(pid).or_default().push(content);
            }
        }

        // Emit events using DFS traversal
        self.emit_tree(None, 1, &children_map, &element_data, &text_map);
    }

    fn emit_tree(
        &self,
        parent_id: Option<i64>,
        level: usize,
        children_map: &std::collections::HashMap<Option<i64>, Vec<i64>>,
        element_data: &std::collections::HashMap<i64, ElementData>,
        text_map: &std::collections::HashMap<i64, Vec<String>>,
    ) {
        let Some(children) = children_map.get(&parent_id) else {
            return;
        };

        for &child_id in children {
            let Some(data) = element_data.get(&child_id) else {
                continue;
            };

            // Parse arguments from JSON
            let (parsed_args, inline_text) = parse_args_json(&data.args);

            // Create MetaLine
            let meta_line = MetaLine {
                type_name: data.element_type.clone(),
                level,
                inline_text,
                id: data
                    .block_id
                    .as_ref()
                    .map(|s| Id::new(s.clone()))
                    .unwrap_or_else(Id::empty),
                line_index: Some(data.line_idx as usize),
                arguments: parsed_args,
                is_void: false,
            };

            // Emit BlockStarted
            let event = Event::BlockStarted { meta_line };
            if let Err(e) = self.bus.lock().unwrap().try_broadcast(event) {
                eprintln!("Failed to broadcast BlockStarted: {e:?}");
            }

            // Emit any text content for this block
            if let Some(texts) = text_map.get(&child_id) {
                for text in texts {
                    let lines: Vec<String> = text.lines().map(String::from).collect();
                    let links = vec![vec![]; lines.len()];
                    let event = Event::TextFinished {
                        lines,
                        links,
                        footnotes: vec![],
                    };
                    if let Err(e) = self.bus.lock().unwrap().try_broadcast(event) {
                        eprintln!("Failed to broadcast TextFinished: {e:?}");
                    }
                }
            }

            // Recurse into children
            self.emit_tree(
                Some(child_id),
                level + 1,
                children_map,
                element_data,
                text_map,
            );

            // Emit BlockFinished
            let event = Event::BlockFinished;
            if let Err(e) = self.bus.lock().unwrap().try_broadcast(event) {
                eprintln!("Failed to broadcast BlockFinished: {e:?}");
            }
        }
    }
}

#[derive(Debug)]
struct ElementData {
    #[allow(dead_code)]
    id: i64,
    #[allow(dead_code)]
    parent_id: Option<i64>,
    block_id: Option<String>,
    args: String,
    element_type: String,
    line_idx: i64,
}

/// Parse the args JSON field from SqlFileConsumer format
fn parse_args_json(json_str: &str) -> (ParsedArguments, QuotedString) {
    let default_args = ParsedArguments {
        args: Vec::new(),
        kwargs: Vec::new(),
    };
    let default_inline = QuotedString::new(String::new(), false);

    let Ok(value) = serde_json::from_str::<serde_json::Value>(json_str) else {
        return (default_args, default_inline);
    };

    // Extract args array
    let args: Vec<QuotedString> = value
        .get("args")
        .and_then(|v| v.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| {
                    if let Some(obj) = v.as_object() {
                        let content = obj.get("content")?.as_str()?.to_string();
                        let quoted = obj.get("quoted").and_then(|v| v.as_bool()).unwrap_or(false);
                        Some(QuotedString::new(content, quoted))
                    } else {
                        v.as_str().map(|s| QuotedString::new(s.to_string(), false))
                    }
                })
                .collect()
        })
        .unwrap_or_default();

    // Extract kwargs
    let kwargs: Vec<(QuotedString, QuotedString)> = value
        .get("kwargs")
        .and_then(|v| v.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| {
                    let arr = v.as_array()?;
                    if arr.len() != 2 {
                        return None;
                    }
                    let key = parse_quoted_string(&arr[0])?;
                    let val = parse_quoted_string(&arr[1])?;
                    Some((key, val))
                })
                .collect()
        })
        .unwrap_or_default();

    // Extract inline_text
    let inline_text = value
        .get("inline_text")
        .and_then(parse_quoted_string)
        .unwrap_or(default_inline);

    (ParsedArguments { args, kwargs }, inline_text)
}

fn parse_quoted_string(value: &serde_json::Value) -> Option<QuotedString> {
    if let Some(obj) = value.as_object() {
        let content = obj.get("content")?.as_str()?.to_string();
        let quoted = obj.get("quoted").and_then(|v| v.as_bool()).unwrap_or(false);
        Some(QuotedString::new(content, quoted))
    } else {
        value
            .as_str()
            .map(|s| QuotedString::new(s.to_string(), false))
    }
}

/// Parse SQL statements properly, handling semicolons inside strings
fn parse_sql_statements(sql: &str) -> Vec<&str> {
    let mut statements = Vec::new();
    let mut start = 0;
    let mut in_string = false;
    let mut chars = sql.char_indices().peekable();

    while let Some((i, c)) = chars.next() {
        match c {
            '\'' if !in_string => {
                in_string = true;
            }
            '\'' if in_string => {
                // Check for escaped quote ''
                if chars.peek().map(|(_, c)| *c) == Some('\'') {
                    chars.next(); // Skip the second quote
                } else {
                    in_string = false;
                }
            }
            ';' if !in_string => {
                let stmt = sql[start..i].trim();
                if !stmt.is_empty() {
                    statements.push(stmt);
                }
                start = i + 1;
            }
            _ => {}
        }
    }

    // Don't forget the last statement if it doesn't end with semicolon
    let last = sql[start..].trim();
    if !last.is_empty() {
        statements.push(last);
    }

    statements
}

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

    #[tokio::test]
    async fn test_sql_file_emitter_from_string() {
        let sql = r#"
            CREATE TABLE element (
                id INTEGER PRIMARY KEY,
                parent_id INTEGER,
                block_id TEXT,
                args JSON,
                content TEXT,
                element_type TEXT NOT NULL,
                block_type TEXT NOT NULL,
                line_idx INTEGER
            );
            INSERT INTO element (id, parent_id, block_id, args, content, element_type, block_type, line_idx)
            VALUES (1, NULL, 'doc1', '{"args":[],"kwargs":[],"inline_text":{"content":"","quoted":false}}', '', 'document', 'block', 0);
            INSERT INTO element (id, parent_id, block_id, args, content, element_type, block_type, line_idx)
            VALUES (2, 1, NULL, '{}', 'Hello world', 'text', 'text', 1);
        "#;

        let emitter = SqlFileEmitter::from_string(sql.to_string()).await.unwrap();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.run();
        });

        let events: Vec<Event> = receiver.collect();

        // Should have: BlockStarted, TextFinished, BlockFinished
        assert_eq!(events.len(), 3);
        assert!(matches!(events[0], Event::BlockStarted { .. }));
        assert!(matches!(events[1], Event::TextFinished { .. }));
        assert!(matches!(events[2], Event::BlockFinished));
    }

    #[tokio::test]
    async fn test_sql_file_emitter_nested() {
        let sql = r#"
            CREATE TABLE element (
                id INTEGER PRIMARY KEY,
                parent_id INTEGER,
                block_id TEXT,
                args JSON,
                content TEXT,
                element_type TEXT NOT NULL,
                block_type TEXT NOT NULL,
                line_idx INTEGER
            );
            INSERT INTO element VALUES (1, NULL, NULL, '{}', '', 'document', 'block', 0);
            INSERT INTO element VALUES (2, 1, NULL, '{}', '', 'section', 'block', 1);
            INSERT INTO element VALUES (3, 2, NULL, '{}', '', 'p', 'block', 2);
            INSERT INTO element VALUES (4, 3, NULL, '{}', 'Text content', 'text', 'text', 3);
        "#;

        let emitter = SqlFileEmitter::from_string(sql.to_string()).await.unwrap();
        let receiver = emitter.subscribe();

        std::thread::spawn(move || {
            emitter.run();
        });

        let events: Vec<Event> = receiver.collect();

        // document start, section start, p start, text, p end, section end, document end
        assert_eq!(events.len(), 7);
    }
}
