use std::{
    fs::File,
    io::{Error, Read},
    path::Path,
    str::FromStr,
};

use crate::{config::_get_client_id, get_basepath};
use futures::FutureExt;
use serde::{Deserialize, Serialize};
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteConnectOptions, Sqlite, SqlitePool};
use tauri::{ipc::RuntimeCapability, App, AssetResolver, Manager, Url};

struct DBFileEntry {
    id: u64,
    relative_file_path: String,
    file_name: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum DBError {
    DatabaseLocationError(String),
    DatabaseConnectionError(String),
    DatabaseQueryError(String),
}

fn get_database_path(app_handle: &tauri::AppHandle) -> Result<String, DBError> {
    if let Some(basepath) = get_basepath(app_handle.clone()) {
        let db_path = Path::new(&basepath).join("db.sqlite");

        let resolved_db_path = match app_handle
            .path()
            .resolve(db_path, tauri::path::BaseDirectory::Home)
        {
            Ok(resolved_knowledgebase_path) => resolved_knowledgebase_path,

            Err(e) => return Err(DBError::DatabaseLocationError(e.to_string())),
        };
        let unicode_db_path = resolved_db_path.to_string_lossy().as_ref().to_string();
        return Ok(unicode_db_path);
    }
    Err(DBError::DatabaseLocationError(
        "No knowledgebase defined".to_string(),
    ))
}
#[tauri::command]
pub async fn initialize_database(app_handle: tauri::AppHandle) -> Result<(), DBError> {
    let unicode_db_path = get_database_path(&app_handle)?;
    if !Sqlite::database_exists(&unicode_db_path)
        .await
        .unwrap_or(false)
    {
        match Sqlite::create_database(&unicode_db_path).await {
            Ok(_) => println!("Create db success"),
            Err(error) => panic!("error: {}", error),
        }
    }
    let db = SqlitePool::connect(&unicode_db_path)
        .await
        .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;
    let _ = sqlx::query(
        "CREATE TABLE IF NOT EXISTS files
        (
            id INTEGER PRIMARY KEY NOT NULL,
            relative_path TEXT NOT NULL,
            file_name TEXT NOT NULL
        );",
    )
    .execute(&db)
    .await
    .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;

    let _ = sqlx::query(
        "CREATE TABLE IF NOT EXISTS file_diffs
        (
            id INTEGER NOT NULL,
            client_id TEXT NOT NULL,
            diff_text TEXT NOT NULL,
            current_diff BOOLEAN,
            parent_diff_id INTEGER,
            file_id INTEGER,
            FOREIGN KEY (parent_diff_id) REFERENCES file_diffs(id),
            FOREIGN KEY (file_id) REFERENCES files(id),
            PRIMARY KEY (id, client_id)
        )
        ;",
    )
    .execute(&db)
    .await
    .unwrap();
    // .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
    db.close().await;
    Ok(())
}

async fn get_file_id(
    app_handle: &tauri::AppHandle,
    relative_file_path: &str,
    file_name: &str,
) -> Result<i32, DBError> {
    let unicode_db_path = get_database_path(&app_handle)?;
    let db = SqlitePool::connect(&unicode_db_path)
        .await
        .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;

    let (id, _, _): (i32, String, String) = sqlx::query_as(&format!(
        "
        SELECT *
        FROM files
        WHERE relative_path IS '{relative_file_path}'
        AND file_name IS '{file_name}'
        "
    ))
    .fetch_one(&db)
    .await
    .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
    db.close().await;
    Ok(id)
}
pub async fn add_file(
    app_handle: &tauri::AppHandle,
    relative_file_path: &str,
    file_name: &str,
) -> Result<(), DBError> {
    if get_file_id(app_handle, relative_file_path, file_name)
        .await
        .is_ok()
    {
        return Ok(());
    }
    let unicode_db_path = get_database_path(&app_handle)?;
    let db = SqlitePool::connect(&unicode_db_path)
        .await
        .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;

    let id = sqlx::query(&format!(
        "
        INSERT INTO
          files (relative_path, file_name)
        VALUES
          ('{relative_file_path}', '{file_name}');"
    ))
    .execute(&db)
    .await
    .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
    db.close().await;
    Ok(())
}
async fn get_last_file_diff(
    app_handle: &tauri::AppHandle,
    relative_file_path: &str,
    file_name: &str,
) -> Result<i32, DBError> {
    let unicode_db_path = get_database_path(&app_handle)?;
    let db = SqlitePool::connect(&unicode_db_path)
        .await
        .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;

    let (parent_diff_id, _, _, _, _, _): (i32, String, String, bool, i32, i32) =
        sqlx::query_as(&format!(
            "
        SELECT *
        FROM file_diffs
        WHERE relative_path IS '{relative_file_path}'
        AND file_name IS '{file_name}'
        AND working_diff is 1
        "
        ))
        .fetch_one(&db)
        .await
        .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
    db.close().await;
    Ok(parent_diff_id)
}
#[tauri::command]
pub async fn store_diff(
    app_handle: &tauri::AppHandle,
    relative_file_path: &str,
    file_name: &str,
    diff: String,
) -> Result<(), DBError> {
    let unicode_db_path = get_database_path(&app_handle)?;
    let client_id = _get_client_id(&app_handle);
    let db = SqlitePool::connect(&unicode_db_path)
        .await
        .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;

    // 1 insert into file_diffs with parent
    // 2 make last diff not currennt diff
    //
    // id INTEGER NOT NULL,
    // client_id TEXT NOT NULL,
    // diff_text TEXT NOT NULL,
    // current_diff BOOLEAN,
    // FOREIGN KEY (id)
    //    REFERENCES file_diffs (parent_diff_id)
    // FOREIGN KEY (id)
    //    REFERENCES files (file_id)
    // PRIMARY KEY (id, client_id)
    let file_id = get_file_id(app_handle, relative_file_path, file_name).await?;
    let _ = sqlx::query(&format!(
        "
        UPDATE file_diffs
        SET current_diff = 0
        WHERE relative_path IS '{relative_file_path}'
        AND file_name IS '{file_name}'
        AND working_diff is 1
        "
    ))
    .execute(&db)
    .await
    .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
    let _ = sqlx::query(&format!(
        "
        INSERT INTO
          file_diffs (client_id, diff_text, current_diff, file_id)
        VALUES
          ('{client_id}', '{diff}', 1, );"
    ))
    .execute(&db)
    .await
    .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;

    db.close().await;

    Ok(())
}
