use anyhow::{Context, Result};
use rusqlite::Connection;
use std::str::FromStr;
use uuid::Uuid;

use crate::filetree::node::{Aliases, FileNode, NodeType};

pub mod convert;
pub mod mode;
pub mod modes;
pub mod node;

pub struct FileTree {
    pub user_id: String,
    pub nodes: Vec<FileNode>,
}

const CREATE_TABLES: &str = "
CREATE TABLE IF NOT EXISTS file_registry (
    file_uuid TEXT PRIMARY KEY,
    original_name TEXT NOT NULL,
    file_size INTEGER,
    file_type TEXT,
    registered_at TEXT NOT NULL DEFAULT (datetime('now')),
    last_seen_at TEXT,
    status TEXT NOT NULL DEFAULT 'active'
);

CREATE TABLE IF NOT EXISTS file_nodes (
    node_id TEXT PRIMARY KEY,
    label TEXT NOT NULL,
    aliases_json TEXT NOT NULL DEFAULT '{}',
    file_uuid TEXT,
    sha256 TEXT,
    parent_id TEXT,
    children_json TEXT NOT NULL DEFAULT '[]',
    node_type TEXT NOT NULL DEFAULT 'folder',
    icon TEXT,
    color TEXT,
    bg_color TEXT,
    file_size INTEGER,
    registered_at TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    sort_order INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS file_locations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    file_uuid TEXT NOT NULL,
    location TEXT NOT NULL,
    label TEXT,
    added_at TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE(file_uuid, location)
);
";

impl FileTree {
    pub fn user_db_path(user_id: &str) -> String {
        format!("data/users/{}.sqlite", user_id)
    }

    pub fn init_user_db(user_id: &str) -> Result<Connection> {
        let db_path = Self::user_db_path(user_id);
        let parent = std::path::Path::new(&db_path).parent().unwrap();
        std::fs::create_dir_all(parent)?;

        let conn = Connection::open(&db_path)?;
        conn.execute_batch(CREATE_TABLES)?;
        Ok(conn)
    }

    pub fn open_user_db(user_id: &str) -> Result<Connection> {
        let db_path = Self::user_db_path(user_id);
        Connection::open(&db_path).with_context(|| format!("Failed to open {}", db_path))
    }

    pub fn load(conn: &Connection, user_id: &str) -> Result<Self> {
        let mut stmt = conn.prepare(
            "SELECT node_id, label, aliases_json, file_uuid, sha256, parent_id, children_json,
                    node_type, icon, color, bg_color, file_size, registered_at,
                    created_at, updated_at, sort_order
             FROM file_nodes ORDER BY sort_order ASC, created_at ASC",
        )?;

        let nodes: Vec<FileNode> = stmt
            .query_map([], |row| {
                let children_json: String = row.get(6)?;
                let children: Vec<String> =
                    serde_json::from_str(&children_json).unwrap_or_default();
                Ok(FileNode {
                    node_id: row.get(0)?,
                    label: row.get(1)?,
                    aliases: Aliases::from_json(&row.get::<_, String>(2)?),
                    file_uuid: row.get(3)?,
                    sha256: row.get(4)?,
                    parent_id: row.get(5)?,
                    children,
                    node_type: NodeType::from_str(&row.get::<_, String>(7)?)
                        .unwrap_or(NodeType::Folder),
                    icon: row.get(8)?,
                    color: row.get(9)?,
                    bg_color: row.get(10)?,
                    file_size: row.get(11)?,
                    registered_at: row.get(12)?,
                    created_at: row.get(13)?,
                    updated_at: row.get(14)?,
                    sort_order: row.get(15)?,
                })
            })?
            .filter_map(|r| r.ok())
            .collect();

        Ok(FileTree {
            user_id: user_id.to_string(),
            nodes,
        })
    }

    pub fn insert_node(&mut self, conn: &Connection, node: &FileNode) -> Result<()> {
        conn.execute(
            "INSERT INTO file_nodes (node_id, label, aliases_json, file_uuid, sha256, parent_id,
             children_json, node_type, icon, color, bg_color, file_size, registered_at,
             created_at, updated_at, sort_order)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)",
            rusqlite::params![
                node.node_id,
                node.label,
                node.aliases.to_json(),
                node.file_uuid,
                node.sha256,
                node.parent_id,
                serde_json::to_string(&node.children).unwrap_or_else(|_| "[]".to_string()),
                node.node_type.as_str(),
                node.icon,
                node.color,
                node.bg_color,
                node.file_size,
                node.registered_at,
                node.created_at,
                node.updated_at,
                node.sort_order,
            ],
        )?;
        self.nodes.push(node.clone());
        Ok(())
    }

    pub fn update_node(
        &mut self,
        conn: &Connection,
        node_id: &str,
        updated: &FileNode,
    ) -> Result<()> {
        conn.execute(
            "UPDATE file_nodes SET label=?1, aliases_json=?2, file_uuid=?3, sha256=?4, parent_id=?5,
             children_json=?6, node_type=?7, icon=?8, color=?9, bg_color=?10,
             file_size=?11, registered_at=?12,
             updated_at=?13, sort_order=?14
             WHERE node_id=?15",
            rusqlite::params![
                updated.label,
                updated.aliases.to_json(),
                updated.file_uuid,
                updated.sha256,
                updated.parent_id,
                serde_json::to_string(&updated.children)
                    .unwrap_or_else(|_| "[]".to_string()),
                updated.node_type.as_str(),
                updated.icon,
                updated.color,
                updated.bg_color,
                updated.file_size,
                updated.registered_at,
                updated.updated_at,
                updated.sort_order,
                node_id,
            ],
        )?;

        if let Some(n) = self.nodes.iter_mut().find(|n| n.node_id == node_id) {
            *n = updated.clone();
        }
        Ok(())
    }

    pub fn update_node_alias(
        &mut self,
        conn: &Connection,
        node_id: &str,
        lang: &str,
        value: &str,
    ) -> Result<()> {
        let node = self
            .nodes
            .iter_mut()
            .find(|n| n.node_id == node_id)
            .context("Node not found")?;

        node.aliases.set(lang, value);

        let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
        node.updated_at = now.clone();

        conn.execute(
            "UPDATE file_nodes SET aliases_json=?1, updated_at=?2 WHERE node_id=?3",
            rusqlite::params![node.aliases.to_json(), now, node_id],
        )?;

        Ok(())
    }

    pub fn delete_node(&mut self, conn: &Connection, node_id: &str) -> Result<()> {
        conn.execute("DELETE FROM file_nodes WHERE node_id=?1", [node_id])?;
        self.nodes.retain(|n| n.node_id != node_id);
        Ok(())
    }

    pub fn move_node(
        &mut self,
        conn: &Connection,
        node_id: &str,
        new_parent_id: Option<String>,
    ) -> Result<()> {
        let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();

        conn.execute(
            "UPDATE file_nodes SET parent_id=?1, updated_at=?2 WHERE node_id=?3",
            rusqlite::params![new_parent_id, now, node_id],
        )?;

        if let Some(n) = self.nodes.iter_mut().find(|n| n.node_id == node_id) {
            n.parent_id = new_parent_id;
            n.updated_at = now;
        }
        Ok(())
    }

    pub fn build_tree(&self) -> Vec<FileNode> {
        let mut roots: Vec<FileNode> = self
            .nodes
            .iter()
            .filter(|n| n.parent_id.is_none())
            .cloned()
            .collect();

        for root in &mut roots {
            self.fill_children(root);
        }
        roots
    }

    fn fill_children(&self, node: &mut FileNode) {
        let child_ids: Vec<String> = node.children.clone();
        let mut children: Vec<FileNode> = self
            .nodes
            .iter()
            .filter(|n| {
                n.parent_id.as_deref() == Some(&node.node_id) && child_ids.contains(&n.node_id)
            })
            .cloned()
            .collect();

        for child in &mut children {
            self.fill_children(child);
        }

        node.children = children.iter().map(|c| c.node_id.clone()).collect();
        node.children
            .extend(children.iter().flat_map(|c| c.children.clone()));
    }

    pub fn new_folder(label: &str, parent_id: Option<String>) -> FileNode {
        let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
        FileNode {
            node_id: Uuid::new_v4().to_string(),
            label: label.to_string(),
            aliases: Aliases::empty(),
            file_uuid: None,
            sha256: None,
            parent_id,
            children: vec![],
            node_type: NodeType::Folder,
            icon: None,
            color: None,
            bg_color: None,
            file_size: None,
            registered_at: None,
            created_at: now.clone(),
            updated_at: now,
            sort_order: 0,
        }
    }

    #[allow(clippy::too_many_arguments)]
    pub fn new_file_node(
        label: &str,
        file_uuid: &str,
        sha256: Option<&str>,
        original_name: &str,
        file_size: Option<i64>,
        file_type: Option<&str>,
        registered_at: Option<&str>,
        parent_id: Option<String>,
    ) -> (FileNode, Option<String>) {
        let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
        let reg_at = registered_at
            .map(|s| s.to_string())
            .unwrap_or_else(|| now.clone());
        let node = FileNode {
            node_id: Uuid::new_v4().to_string(),
            label: label.to_string(),
            aliases: Aliases::empty(),
            file_uuid: Some(file_uuid.to_string()),
            sha256: sha256.map(|s| s.to_string()),
            parent_id,
            children: vec![],
            node_type: NodeType::File,
            icon: None,
            color: None,
            bg_color: None,
            file_size,
            registered_at: Some(reg_at),
            created_at: now.clone(),
            updated_at: now.clone(),
            sort_order: 0,
        };

        let register_sql =
            format!(
            "INSERT OR REPLACE INTO file_registry (file_uuid, original_name, file_size, file_type,
             registered_at, status) VALUES ('{}', '{}', {}, {}, '{}', 'active')",
            file_uuid,
            original_name.replace('\'', "''"),
            file_size.map_or("NULL".to_string(), |s| s.to_string()),
            file_type.map_or("NULL".to_string(), |t| format!("'{}'", t.replace('\'', "''"))),
            now,
        );

        (node, Some(register_sql))
    }

    pub fn add_location(
        conn: &Connection,
        file_uuid: &str,
        location: &str,
        label: Option<&str>,
    ) -> Result<()> {
        conn.execute(
            "INSERT OR IGNORE INTO file_locations (file_uuid, location, label) VALUES (?1, ?2, ?3)",
            rusqlite::params![file_uuid, location, label],
        )?;
        Ok(())
    }

    pub fn get_file_info(conn: &Connection, file_uuid: &str) -> Result<serde_json::Value> {
        let mut virtual_paths: Vec<String> = Vec::new();
        let mut stmt = conn.prepare(
            "SELECT fn.node_id, fn.label, fn.parent_id FROM file_nodes fn WHERE fn.file_uuid = ?1",
        )?;
        let node_rows: Vec<(String, String, Option<String>)> = stmt
            .query_map([file_uuid], |row| {
                Ok((
                    row.get::<_, String>(0)?,
                    row.get::<_, String>(1)?,
                    row.get::<_, Option<String>>(2)?,
                ))
            })?
            .filter_map(|r| r.ok())
            .collect();

        for (_nid, _label, _parent_id) in &node_rows {
            let mut path_parts: Vec<String> = vec![];
            let mut current = _parent_id.clone();
            while let Some(pid) = current {
                let folder: Option<(String, Option<String>)> = conn
                    .query_row(
                        "SELECT label, parent_id FROM file_nodes WHERE node_id = ?1",
                        [&pid],
                        |row| Ok((row.get(0)?, row.get(1)?)),
                    )
                    .ok();
                if let Some((name, next_pid)) = folder {
                    path_parts.push(name);
                    current = next_pid;
                } else {
                    break;
                }
            }
            path_parts.reverse();
            virtual_paths.push(path_parts.join(" / "));
        }

        let mut real_locations: Vec<serde_json::Value> = Vec::new();
        let mut lstmt = conn.prepare(
            "SELECT location, label, added_at FROM file_locations WHERE file_uuid = ?1 ORDER BY added_at",
        )?;
        let locs: Vec<(String, Option<String>, String)> = lstmt
            .query_map([file_uuid], |row| {
                Ok((
                    row.get::<_, String>(0)?,
                    row.get::<_, Option<String>>(1)?,
                    row.get::<_, String>(2)?,
                ))
            })?
            .filter_map(|r| r.ok())
            .collect();
        for (loc, lbl, added) in &locs {
            real_locations.push(serde_json::json!({
                "path": loc,
                "label": lbl,
                "added_at": added,
            }));
        }

        Ok(serde_json::json!({
            "file_uuid": file_uuid,
            "virtual_paths": virtual_paths,
            "real_locations": real_locations,
            "node_count": node_rows.len(),
            "location_count": locs.len(),
        }))
    }
}

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

    fn temp_db() -> (Connection, String) {
        let user_id = format!("test_{}", Uuid::new_v4());
        let conn = FileTree::init_user_db(&user_id).unwrap();
        (conn, user_id)
    }

    #[test]
    fn test_init_and_load_empty_tree() {
        let (conn, user_id) = temp_db();
        let tree = FileTree::load(&conn, &user_id).unwrap();
        assert_eq!(tree.user_id, user_id);
        assert!(tree.nodes.is_empty());
    }

    #[test]
    fn test_insert_and_load_node() {
        let (conn, user_id) = temp_db();
        let mut tree = FileTree::load(&conn, &user_id).unwrap();

        let folder = FileTree::new_folder("Videos", None);
        tree.insert_node(&conn, &folder).unwrap();

        let loaded = FileTree::load(&conn, &user_id).unwrap();
        assert_eq!(loaded.nodes.len(), 1);
        assert_eq!(loaded.nodes[0].label, "Videos");
        assert_eq!(loaded.nodes[0].node_type, NodeType::Folder);
    }

    #[test]
    fn test_update_node() {
        let (conn, user_id) = temp_db();
        let mut tree = FileTree::load(&conn, &user_id).unwrap();

        let mut folder = FileTree::new_folder("Videos", None);
        tree.insert_node(&conn, &folder).unwrap();

        folder.label = "Movies".to_string();
        folder.icon = Some("📽️".to_string());
        folder.color = Some("#ff0000".to_string());
        tree.update_node(&conn, &folder.node_id, &folder).unwrap();

        let loaded = FileTree::load(&conn, &user_id).unwrap();
        assert_eq!(loaded.nodes[0].label, "Movies");
        assert_eq!(loaded.nodes[0].icon, Some("📽️".to_string()));
        assert_eq!(loaded.nodes[0].color, Some("#ff0000".to_string()));
    }

    #[test]
    fn test_delete_node() {
        let (conn, user_id) = temp_db();
        let mut tree = FileTree::load(&conn, &user_id).unwrap();

        let folder = FileTree::new_folder("Temp", None);
        let node_id = folder.node_id.clone();
        tree.insert_node(&conn, &folder).unwrap();

        tree.delete_node(&conn, &node_id).unwrap();

        let loaded = FileTree::load(&conn, &user_id).unwrap();
        assert_eq!(loaded.nodes.len(), 0);
    }

    #[test]
    fn test_move_node() {
        let (conn, user_id) = temp_db();
        let mut tree = FileTree::load(&conn, &user_id).unwrap();

        let root = FileTree::new_folder("Root", None);
        let child = FileTree::new_folder("Child", Some(root.node_id.clone()));

        tree.insert_node(&conn, &root).unwrap();
        tree.insert_node(&conn, &child).unwrap();

        tree.move_node(&conn, &child.node_id, None).unwrap();

        let loaded = FileTree::load(&conn, &user_id).unwrap();
        let moved = loaded.nodes.iter().find(|n| n.label == "Child").unwrap();
        assert!(moved.parent_id.is_none());
    }

    #[test]
    fn test_update_alias() {
        let (conn, user_id) = temp_db();
        let mut tree = FileTree::load(&conn, &user_id).unwrap();

        let folder = FileTree::new_folder("Videos", None);
        tree.insert_node(&conn, &folder).unwrap();

        tree.update_node_alias(&conn, &folder.node_id, "zh_tw", "影片")
            .unwrap();

        let loaded = FileTree::load(&conn, &user_id).unwrap();
        assert_eq!(
            loaded.nodes[0].aliases.get("zh_tw").map(|s| s.as_str()),
            Some("影片")
        );
    }

    #[test]
    fn test_build_tree() {
        let (conn, user_id) = temp_db();
        let mut tree = FileTree::load(&conn, &user_id).unwrap();

        let root = FileTree::new_folder("Root", None);
        let child1 = FileTree::new_folder("Child1", Some(root.node_id.clone()));
        let child2 = FileTree::new_folder("Child2", Some(root.node_id.clone()));
        let grandchild = FileTree::new_folder("Grandchild", Some(child1.node_id.clone()));

        // Set parent-child relationships
        let mut root_with_children = root.clone();
        root_with_children.children = vec![child1.node_id.clone(), child2.node_id.clone()];
        let mut child1_with_children = child1.clone();
        child1_with_children.children = vec![grandchild.node_id.clone()];

        tree.insert_node(&conn, &root_with_children).unwrap();
        tree.insert_node(&conn, &child1_with_children).unwrap();
        tree.insert_node(&conn, &child2).unwrap();
        tree.insert_node(&conn, &grandchild).unwrap();

        let roots = tree.build_tree();
        assert_eq!(roots.len(), 1);
        assert_eq!(roots[0].label, "Root");
    }
}
