// tests/tables_data/handlers/get_table_data_by_position_test.rs
use rstest::{fixture, rstest};
use tonic;
use sqlx::PgPool;
use common::proto::komp_ac::tables_data::GetTableDataByPositionRequest;
use common::proto::komp_ac::table_definition::{PostTableDefinitionRequest, ColumnDefinition};
use server::table_definition::handlers::post_table_definition;
use server::tables_data::handlers::get_table_data_by_position;
use crate::common::setup_test_db;
use chrono::Utc;
use serde_json::json;

#[fixture]
async fn pool() -> PgPool {
    setup_test_db().await
}

#[fixture]
async fn closed_pool(#[future] pool: PgPool) -> PgPool {
    let pool = pool.await;
    pool.close().await;
    pool
}

async fn setup_test_environment(pool: &PgPool) -> (String, String, i64) {
    // Create unique profile and table names
    let profile_name = format!("test_profile_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));
    let table_name = format!("test_table_{}", Utc::now().timestamp_nanos_opt().unwrap_or(0));

    // Use the table definition handler to create the table properly
    let request = PostTableDefinitionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        columns: vec![
            ColumnDefinition {
                name: "firma".to_string(),
                field_type: "text".to_string(),
            }
        ],
        indexes: vec![],
        links: vec![],
    };

    post_table_definition(pool, request).await.unwrap();

    // Get the schema_id for cleanup
    let schema_id = sqlx::query_scalar!(
        "SELECT id FROM schemas WHERE name = $1",
        profile_name
    )
    .fetch_one(pool)
    .await
    .unwrap();

    (profile_name, table_name, schema_id)
}

async fn cleanup_test_environment(pool: &PgPool, schema_id: i64, profile_name: &str) {
    let mut tx = pool.begin().await.unwrap();

    // Cleanup order matters!
    sqlx::query(&format!(r#"DROP SCHEMA IF EXISTS "{}" CASCADE"#, profile_name))
        .execute(&mut *tx)
        .await
        .unwrap();

    sqlx::query!("DELETE FROM table_definitions WHERE schema_id = $1", schema_id)
        .execute(&mut *tx)
        .await
        .unwrap();

    sqlx::query!("DELETE FROM schemas WHERE id = $1", schema_id)
        .execute(&mut *tx)
        .await
        .unwrap();

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

#[rstest]
#[tokio::test]
async fn test_retrieves_correct_record_by_position(
    #[future] pool: PgPool,
) {
    let pool = pool.await;
    let (profile_name, table_name, schema_id) = setup_test_environment(&pool).await;

    // Insert test data
    let mut tx = pool.begin().await.unwrap();
    let id1: i64 = sqlx::query_scalar(&format!(
        r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 1') RETURNING id"#,
        profile_name,
        table_name
    ))
    .fetch_one(&mut *tx)
    .await
    .unwrap();

    let id2: i64 = sqlx::query_scalar(&format!(
        r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 2') RETURNING id"#,
        profile_name,
        table_name
    ))
    .fetch_one(&mut *tx)
    .await
    .unwrap();
    tx.commit().await.unwrap();

    // Test position 1
    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: 1,
    };
    let response = get_table_data_by_position(&pool, request).await.unwrap();
    assert_eq!(response.data["id"], id1.to_string());

    // Test position 2
    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: 2,
    };
    let response = get_table_data_by_position(&pool, request).await.unwrap();
    assert_eq!(response.data["id"], id2.to_string());

    cleanup_test_environment(&pool, schema_id, &profile_name).await;
}

#[rstest]
#[tokio::test]
async fn test_excludes_deleted_records(
    #[future] pool: PgPool,
) {
    let pool = pool.await;
    let (profile_name, table_name, schema_id) = setup_test_environment(&pool).await;

    // Insert test data
    let mut tx = pool.begin().await.unwrap();
    let id1: i64 = sqlx::query_scalar(&format!(
        r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 1') RETURNING id"#,
        profile_name,
        table_name
    ))
    .fetch_one(&mut *tx)
    .await
    .unwrap();

    // Insert and delete a record
    let deleted_id: i64 = sqlx::query_scalar(&format!(
        r#"INSERT INTO "{}"."{}" (firma) VALUES ('Deleted') RETURNING id"#,
        profile_name,
        table_name
    ))
    .fetch_one(&mut *tx)
    .await
    .unwrap();

    let id2: i64 = sqlx::query_scalar(&format!(
        r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 2') RETURNING id"#,
        profile_name,
        table_name
    ))
    .fetch_one(&mut *tx)
    .await
    .unwrap();
    
    sqlx::query(&format!(
        r#"UPDATE "{}"."{}" SET deleted = true WHERE id = $1"#,
        profile_name,
        table_name
    ))
    .bind(deleted_id)
    .execute(&mut *tx)
    .await
    .unwrap();

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

    // Test positions
    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: 1,
    };
    let response = get_table_data_by_position(&pool, request).await.unwrap();
    assert_eq!(response.data["id"], id1.to_string());

    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: 2,
    };
    let response = get_table_data_by_position(&pool, request).await.unwrap();
    assert_eq!(response.data["id"], id2.to_string());

    cleanup_test_environment(&pool, schema_id, &profile_name).await;
}

#[rstest]
#[tokio::test]
async fn test_invalid_position(
    #[future] pool: PgPool,
) {
    let pool = pool.await;
    let (profile_name, table_name, schema_id) = setup_test_environment(&pool).await;

    // Test position 0
    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: 0,
    };
    let result = get_table_data_by_position(&pool, request).await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument);

    // Test negative position
    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: -1,
    };
    let result = get_table_data_by_position(&pool, request).await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::InvalidArgument);

    cleanup_test_environment(&pool, schema_id, &profile_name).await;
}

#[rstest]
#[tokio::test]
async fn test_position_out_of_bounds(
    #[future] pool: PgPool,
) {
    let pool = pool.await;
    let (profile_name, table_name, schema_id) = setup_test_environment(&pool).await;

    // Insert one record
    let mut tx = pool.begin().await.unwrap();
    sqlx::query(&format!(
        r#"INSERT INTO "{}"."{}" (firma) VALUES ('Test 1')"#,
        profile_name,
        table_name
    ))
    .execute(&mut *tx)
    .await
    .unwrap();
    tx.commit().await.unwrap();

    // Test position beyond count
    let request = GetTableDataByPositionRequest {
        profile_name: profile_name.clone(),
        table_name: table_name.clone(),
        position: 2,
    };
    let result = get_table_data_by_position(&pool, request).await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound);

    cleanup_test_environment(&pool, schema_id, &profile_name).await;
}

#[rstest]
#[tokio::test]
async fn test_table_not_in_profile(
    #[future] pool: PgPool,
) {
    let pool = pool.await;
    let (profile_name, _, schema_id) = setup_test_environment(&pool).await;

    // Test with non-existent table
    let request = GetTableDataByPositionRequest {
        profile_name,
        table_name: "non_existent_table".to_string(),
        position: 1,
    };
    let result = get_table_data_by_position(&pool, request).await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound);

    cleanup_test_environment(&pool, schema_id, "dummy_table").await;
}

#[rstest]
#[tokio::test]
async fn test_profile_not_found(
    #[future] pool: PgPool,
) {
    let pool = pool.await;

    let request = GetTableDataByPositionRequest {
        profile_name: "nonexistent_profile".to_string(),
        table_name: "adresar".to_string(),
        position: 1,
    };
    let result = get_table_data_by_position(&pool, request).await;

    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::NotFound);
}

#[rstest]
#[tokio::test]
async fn test_database_error(
    #[future] closed_pool: PgPool,
) {
    let closed_pool = closed_pool.await;
    let request = GetTableDataByPositionRequest {
        profile_name: "test".to_string(),
        table_name: "test".to_string(),
        position: 1,
    };
    let result = get_table_data_by_position(&closed_pool, request).await;

    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::Internal);
}
