use sqlx::Row;
use sqlx::SqlitePool;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use std::borrow::Cow;
use std::path::Path;
use std::str::FromStr;

pub mod migration;

/// データベースを初期化し、コネクションプールを返す。
/// 埋め込み次元が変更されている場合は vec_items テーブルを再作成する。
pub async fn initialize_database(
    db_path: &Path,
    extension_path: &Path,
    dimension: usize,
) -> Result<SqlitePool, String> {
    // ディレクトリの作成
    if let Some(parent) = db_path.parent() {
        if !parent.exists() {
            std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
        }
    }

    let db_path_str = db_path.to_str().ok_or("Invalid DB path")?;
    let ext_path_str = extension_path.to_str().ok_or("Invalid extension path")?;

    let opts = SqliteConnectOptions::from_str(&format!("sqlite://{}?mode=rwc", db_path_str))
        .map_err(|e| e.to_string())?
        .extension(ext_path_str.to_owned());

    let pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect_with(opts)
        .await
        .map_err(|e| e.to_string())?;

    // スキーマ初期化
    init_schema(&pool, dimension).await?;

    Ok(pool)
}

async fn init_schema(pool: &SqlitePool, dimension: usize) -> Result<(), String> {
    // PRAGMA設定
    sqlx::query("PRAGMA journal_mode = WAL")
        .execute(pool)
        .await
        .map_err(|e| e.to_string())?;

    // マイグレーションの実行
    migration::run_migrations(pool).await?;

    // 内部管理（バージョン等）テーブル
    sqlx::query(
        "CREATE TABLE IF NOT EXISTS internal_metadata (
            key TEXT PRIMARY KEY,
            value TEXT
        )",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // バージョン情報の書き込み (0.3.0)
    sqlx::query("INSERT OR REPLACE INTO internal_metadata (key, value) VALUES ('version', '0.3.0')")
        .execute(pool)
        .await
        .map_err(|e| e.to_string())?;

    // ドキュメント（親メタデータ）テーブル
    sqlx::query(
        "CREATE TABLE IF NOT EXISTS documents (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            path TEXT UNIQUE,
            mime TEXT,
            created_at TEXT DEFAULT (datetime('now', 'localtime')),
            updated_at TEXT DEFAULT (datetime('now', 'localtime'))
        )",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // アイテム（チャンク）テーブル
    sqlx::query(
        "CREATE TABLE IF NOT EXISTS items (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            document_id INTEGER NOT NULL,
            chunk_index INTEGER NOT NULL,
            content TEXT NOT NULL,
            created_at TEXT DEFAULT (datetime('now', 'localtime')),
            updated_at TEXT DEFAULT (datetime('now', 'localtime')),
            FOREIGN KEY(document_id) REFERENCES documents(id) ON DELETE CASCADE
        )",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // LSA ベクトル保存テーブル (意味検索用)
    sqlx::query(
        "CREATE TABLE IF NOT EXISTS items_lsa (
            id INTEGER PRIMARY KEY,
            vector BLOB NOT NULL,
            FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE
        )",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // FTS5 テーブル (高速キーワード検索 & BM25 スコアリング用)
    sqlx::query(
        "CREATE VIRTUAL TABLE IF NOT EXISTS items_fts USING fts5(
            content,
            tokenize='trigram'
        )",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // トリガー作成 (documents)
    sqlx::query(
        "CREATE TRIGGER IF NOT EXISTS update_documents_updated_at
        AFTER UPDATE ON documents
        FOR EACH ROW
        BEGIN
            UPDATE documents SET updated_at = datetime('now', 'localtime') WHERE id = OLD.id;
        END",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // トリガー作成 (items)
    sqlx::query(
        "CREATE TRIGGER IF NOT EXISTS update_items_updated_at
        AFTER UPDATE ON items
        FOR EACH ROW
        BEGIN
            UPDATE items SET updated_at = datetime('now', 'localtime') WHERE id = OLD.id;
        END",
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;

    // vec_items の次元数チェックと初期化
    check_and_init_vector_table(pool, dimension).await?;

    // items にあって items_fts にない行を同期（既存DB・マイグレーション後の検索を有効にする）
    sync_items_fts(pool).await?;

    Ok(())
}

/// items の内容を items_fts に反映する。items に存在するが items_fts に rowid が無い行を挿入する。
async fn sync_items_fts(pool: &SqlitePool) -> Result<(), String> {
    let n = heal_items_fts(pool).await?;
    if n > 0 {
        log::info!("[db] items_fts: synced {} rows from items.", n);
    }
    Ok(())
}

/// items → items_fts の同期を手動実行（テスト／ヒール用）。同期した行数を返す。
pub async fn heal_items_fts(pool: &SqlitePool) -> Result<u64, String> {
    let updated = sqlx::query(
        "INSERT INTO items_fts(rowid, content) SELECT i.id, i.content FROM items i WHERE i.id NOT IN (SELECT rowid FROM items_fts)"
    )
    .execute(pool)
    .await
    .map_err(|e| e.to_string())?;
    Ok(updated.rows_affected())
}

/// documents テーブルの件数を返す。ハンドラ・ツール・LSA 再学習閾値で共通利用。
pub async fn get_document_count(pool: &SqlitePool) -> Result<i64, String> {
    sqlx::query_scalar("SELECT COUNT(*) FROM documents")
        .fetch_one(pool)
        .await
        .map_err(|e| e.to_string())
}

/// 指定 id の item の content, path, mime, document_id, category を取得。検索結果の行構築で共通利用。
pub async fn get_item_content_with_doc(
    pool: &SqlitePool,
    item_id: i64,
) -> Result<Option<(String, String, Option<String>, i64, String)>, String> {
    let row = sqlx::query(
        "SELECT i.content, d.path, d.mime, i.document_id, COALESCE(d.category, '') FROM items i JOIN documents d ON i.document_id = d.id WHERE i.id = ?",
    )
    .bind(item_id)
    .fetch_optional(pool)
    .await
    .map_err(|e| e.to_string())?;
    Ok(row.map(|r| {
        (
            r.get::<String, _>(0),
            r.get::<String, _>(1),
            r.get::<Option<String>, _>(2),
            r.get::<i64, _>(3),
            r.get::<String, _>(4),
        )
    }))
}

/// path で document の id を取得。無ければ None。照合時は正規化した path を使う。
pub async fn get_document_id_by_path(pool: &SqlitePool, path: &str) -> Result<Option<i64>, String> {
    let path = normalize_document_path(path);
    let row = sqlx::query_scalar("SELECT id FROM documents WHERE path = ?")
        .bind(&path)
        .fetch_optional(pool)
        .await
        .map_err(|e| e.to_string())?;
    Ok(row)
}

/// ドキュメント path の正規化（保存・照合で一貫して使用）。区切りは常に / に統一する。
pub fn normalize_document_path(path: &str) -> String {
    let t = path.trim().replace('\\', "/");
    let t = t.trim_end_matches('/');
    t.to_string()
}

/// 指定ディレクトリ配下の document の id 一覧を返す（path が prefix と一致するか、prefix の直下・サブディレクトリにあるもの）。
/// path は normalize_document_path で正規化された形式（/ 区切り）で格納・照合する。
pub fn escape_like(s: &str) -> String {
    s.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_")
}

pub async fn get_document_ids_under_path_prefix(pool: &SqlitePool, path_prefix: &str) -> Result<Vec<i64>, String> {
    let prefix = normalize_document_path(path_prefix);
    if prefix.is_empty() {
        return Ok(vec![]);
    }
    let like_pattern = format!("{}/%", escape_like(&prefix));
    let rows = sqlx::query_scalar::<_, i64>("SELECT id FROM documents WHERE path = ? OR path LIKE ? ESCAPE '\\'")
        .bind(&prefix)
        .bind(&like_pattern)
        .fetch_all(pool)
        .await
        .map_err(|e| e.to_string())?;
    Ok(rows)
}

async fn check_and_init_vector_table(pool: &SqlitePool, dimension: usize) -> Result<(), String> {
    // 現在のテーブル定義を確認
    let row = sqlx::query("SELECT sql FROM sqlite_master WHERE type='table' AND name='vec_items'")
        .fetch_optional(pool)
        .await
        .map_err(|e| e.to_string())?;

    let should_recreate = if let Some(row) = row {
        let sql: String = row.get(0);
        // "FLOAT[640]" のような文字列が含まれているかチェック
        let expected = format!("FLOAT[{}]", dimension);
        if !sql.contains(&expected) {
            log::info!("Dimension mismatch detected (expected {}). Rebuilding vec_items...", dimension);
            true
        } else {
            false
        }
    } else {
        true
    };

    if should_recreate {
        let mut tx = pool.begin().await.map_err(|e| e.to_string())?;

        sqlx::query("DROP TABLE IF EXISTS vec_items")
            .execute(&mut *tx)
            .await
            .map_err(|e| e.to_string())?;

        let create_sql = format!(
            "CREATE VIRTUAL TABLE vec_items USING vec0(id INTEGER PRIMARY KEY, embedding FLOAT[{}])",
            dimension
        );
        sqlx::query(&create_sql)
            .execute(&mut *tx)
            .await
            .map_err(|e| e.to_string())?;

        // 既存の items があればベクトルを再生成する必要があるが、
        // ここでは空のテーブル作成に留める。
        // （/v1/embeddings が必要になるため、再生成は別のユーティリティで行うのが安全）

        tx.commit().await.map_err(|e| e.to_string())?;
    }

    Ok(())
}

/// 全データのベクトルを再生成する
pub async fn rebuild_vector_data<F, Fut>(
    pool: &SqlitePool,
    dimension: usize,
    embed_fn: F,
) -> Result<(), String> 
where 
    F: Fn(String) -> Fut,
    Fut: std::future::Future<Output = Result<Vec<f32>, String>>,
{
    // 強制的に再作成
    {
        let mut tx = pool.begin().await.map_err(|e| e.to_string())?;
        sqlx::query("DROP TABLE IF EXISTS vec_items")
            .execute(&mut *tx)
            .await
            .map_err(|e| e.to_string())?;
        let create_sql = format!(
            "CREATE VIRTUAL TABLE vec_items USING vec0(id INTEGER PRIMARY KEY, embedding FLOAT[{}])",
            dimension
        );
        sqlx::query(&create_sql)
            .execute(&mut *tx)
            .await
            .map_err(|e| e.to_string())?;
        tx.commit().await.map_err(|e| e.to_string())?;
    }

    let rows = sqlx::query("SELECT id, content FROM items")
        .fetch_all(pool)
        .await
        .map_err(|e| e.to_string())?;

    for row in rows {
        let id: i64 = row.get("id");
        let content: String = row.get("content");
        let emb = embed_fn(content).await?;
        
        sqlx::query("INSERT INTO vec_items (id, embedding) VALUES (?, ?)")
            .bind(id)
            .bind(serde_json::to_string(&emb).unwrap_or_default())
            .execute(pool)
            .await
            .map_err(|e| e.to_string())?;
    }

    Ok(())
}

/// itemsテーブルにあってvec_itemsテーブルにないデータを同期する（ヒーリング）
pub async fn sync_vectors<F, Fut>(
    pool: &SqlitePool,
    embed_fn: F,
) -> Result<usize, String>
where
    F: Fn(String) -> Fut,
    Fut: std::future::Future<Output = Result<Vec<f32>, String>>,
{
    // vec_itemsに存在しないIDを抽出
    let rows = sqlx::query(
        "SELECT i.id, i.content 
         FROM items i 
         LEFT JOIN vec_items v ON i.id = v.id 
         WHERE v.id IS NULL"
    )
    .fetch_all(pool)
    .await
    .map_err(|e| e.to_string())?;

    let count = rows.len();
    if count == 0 {
        return Ok(0);
    }

    log::info!("Healing {} missing vectors...", count);

    for row in rows {
        let id: i64 = row.get("id");
        let content: String = row.get("content");
        
        match embed_fn(content).await {
            Ok(emb) => {
                sqlx::query("INSERT INTO vec_items (id, embedding) VALUES (?, ?)")
                    .bind(id)
                    .bind(serde_json::to_string(&emb).unwrap_or_default())
                    .execute(pool)
                    .await
                    .map_err(|e| e.to_string())?;
            }
            Err(e) => {
                log::error!("Failed to generate embedding for item {}: {}", id, e);
            }
        }
    }

    Ok(count)
}

// 下位互換性（既存の依存箇所が多いため、一時的に定義を残すか、順次書き換える）
pub async fn init_pool(
    db_path: &str,
    extension_path: impl Into<Cow<'static, str>>,
) -> Result<SqlitePool, sqlx::Error> {
    let opts = SqliteConnectOptions::from_str(&format!("sqlite://{}?mode=rwc", db_path))?
        .extension(extension_path);

    SqlitePoolOptions::new().connect_with(opts).await
}

#[cfg(test)]
mod tests {
    use super::*;
    // use std::fs;
    use tempfile::tempdir;

    #[tokio::test]
    async fn test_dimension_change_and_rebuild() {
        let dir = tempdir().unwrap();
        let db_path = dir.path().join("test_telos.db");
        
        // DLLのパス取得（ビルドディレクトリにあることが前提）
        // テスト環境では $OUT_DIR や $CARGO_MANIFEST_DIR 基準で探す
        let manifest_dir = env!("CARGO_MANIFEST_DIR");
        let ext_path = Path::new(manifest_dir).join("../../node_modules/sqlite-vec-windows-x64/vec0.dll");

        if !ext_path.exists() {
            println!("Skipping test: vec0.dll not found at {:?}", ext_path);
            return;
        }

        // 1. 最初は次元数 384 で初期化
        let pool = initialize_database(&db_path, &ext_path, 384).await.unwrap();
        
        // テーブル定義の確認
        let row: (String,) = sqlx::query_as("SELECT sql FROM sqlite_master WHERE name='vec_items'")
            .fetch_one(&pool).await.unwrap();
        assert!(row.0.contains("FLOAT[384]"));
        
        // データ挿入
        let doc_id = sqlx::query("INSERT INTO documents (path) VALUES ('test_path')")
            .execute(&pool).await.unwrap().last_insert_rowid();
        sqlx::query("INSERT INTO items (document_id, chunk_index, content) VALUES (?, ?, ?)")
            .bind(doc_id)
            .bind(0)
            .bind("test content")
            .execute(&pool).await.unwrap();
        
        pool.close().await;

        // 2. 次は次元数 768 で再初期化（不整合検知 -> 再作成）
        let pool2 = initialize_database(&db_path, &ext_path, 768).await.unwrap();
        let row2: (String,) = sqlx::query_as("SELECT sql FROM sqlite_master WHERE name='vec_items'")
            .fetch_one(&pool2).await.unwrap();
        assert!(row2.0.contains("FLOAT[768]"));

        // items テーブルは維持されていることを確認
        let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM items")
            .fetch_one(&pool2).await.unwrap();
        assert_eq!(count.0, 1);
        
        pool2.close().await;
    }

    /// vec0 拡張で vec_items に INSERT した行が COMMIT 後に見えるか検証（永続化不具合の原因切り分け用）
    #[tokio::test]
    async fn test_vec_items_insert_persists_after_commit() {
        let dir = tempdir().unwrap();
        let db_path = dir.path().join("test_vec_persist.db");
        let manifest_dir = env!("CARGO_MANIFEST_DIR");
        let ext_path = Path::new(manifest_dir).join("../../node_modules/sqlite-vec-windows-x64/vec0.dll");
        if !ext_path.exists() {
            println!("Skipping test: vec0.dll not found at {:?}", ext_path);
            return;
        }
        let pool = initialize_database(&db_path, &ext_path, 768).await.unwrap();

        // 768次元の JSON 配列（vec0 の embedding 形式）
        let embedding: Vec<f32> = (0..768).map(|i| (i as f32) * 0.001).collect();
        let emb_json = serde_json::to_string(&embedding).unwrap();

        // トランザクション内で INSERT → 同一 tx で COUNT → COMMIT
        let mut tx = pool.begin().await.unwrap();
        let r = sqlx::query("INSERT INTO vec_items (id, embedding) VALUES (?, ?)")
            .bind(1i64)
            .bind(&emb_json)
            .execute(&mut *tx)
            .await;
        assert!(r.is_ok(), "INSERT should succeed: {:?}", r.err());
        let rows = r.unwrap().rows_affected();
        assert_eq!(rows, 1, "rows_affected should be 1");

        let count_in_tx: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM vec_items")
            .fetch_one(&mut *tx)
            .await
            .unwrap();
        assert_eq!(count_in_tx.0, 1, "COUNT(*) within tx should be 1 (vec0 visibility in tx)");

        tx.commit().await.unwrap();

        // COMMIT 後に別クエリで COUNT（プールの別コネクション）
        let count_after: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM vec_items")
            .fetch_one(&pool)
            .await
            .unwrap();
        assert_eq!(count_after.0, 1, "COUNT(*) after commit should be 1 (vec0 must persist)");

        pool.close().await;
    }
}
