// tests/tables_data/handlers/post_table_data_test.rs
use rstest::{fixture, rstest};
use sqlx::PgPool;
use std::collections::HashMap;
use prost_types::Value;
use prost_types::value::Kind;
use common::proto::komp_ac::tables_data::{PostTableDataRequest, PostTableDataResponse};
use common::proto::komp_ac::table_definition::TableLink;
use common::proto::komp_ac::table_definition::{
    PostTableDefinitionRequest, ColumnDefinition as TableColumnDefinition
};
use server::tables_data::handlers::post_table_data;
use server::table_definition::handlers::post_table_definition;
use crate::common::setup_test_db;
use tonic;
use chrono::Utc;
use sqlx::types::chrono::DateTime;
use tokio::sync::mpsc;
use server::indexer::IndexCommand;
use sqlx::Row;
use rand::distr::Alphanumeric;
use rand::Rng;
use rust_decimal::prelude::FromPrimitive;

// Helper function to generate unique identifiers for test isolation
fn generate_unique_id() -> String {
    rand::rng()
        .sample_iter(&Alphanumeric)
        .take(8)
        .map(char::from)
        .collect::<String>()
        .to_lowercase()
}

// Helper function to convert string to protobuf Value
fn string_to_proto_value(s: String) -> Value {
    Value {
        kind: Some(Kind::StringValue(s)),
    }
}

// Helper function to convert HashMap<String, String> to HashMap<String, Value>
fn convert_to_proto_values(data: HashMap<String, String>) -> HashMap<String, Value> {
    data.into_iter()
        .map(|(k, v)| (k, string_to_proto_value(v)))
        .collect()
}

// Create the table definition for adresar test with unique name
async fn create_adresar_table(pool: &PgPool, table_name: &str, profile_name: &str) -> Result<(), tonic::Status> {
    let table_def_request = PostTableDefinitionRequest {
        profile_name: profile_name.into(),
        table_name: table_name.into(),
        columns: vec![
            TableColumnDefinition {
                name: "firma".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "kz".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "drc".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "ulica".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "psc".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "mesto".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "stat".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "banka".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "ucet".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "skladm".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "ico".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "kontakt".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "telefon".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "skladu".into(),
                field_type: "text".into(),
            },
            TableColumnDefinition {
                name: "fax".into(),
                field_type: "text".into(),
            },
        ],
        indexes: vec![],
        links: vec![],
    };

    post_table_definition(pool, table_def_request).await?;
    Ok(())
}

// Test context structure to hold unique identifiers
#[derive(Clone)]
struct TestContext {
    pool: PgPool,
    profile_name: String,
    table_name: String,
    indexer_tx: mpsc::Sender<IndexCommand>,
}

// Fixtures
#[fixture]
async fn test_context() -> TestContext {
    let pool = setup_test_db().await;
    let unique_id = generate_unique_id();
    let profile_name = format!("test_profile_{}", unique_id);
    let table_name = format!("adresar_test_{}", unique_id);
    
    // Create the table for this specific test
    create_adresar_table(&pool, &table_name, &profile_name).await
        .expect("Failed to create test table");
    
    let (tx, _rx) = mpsc::channel(100);
    
    TestContext {
        pool,
        profile_name,
        table_name,
        indexer_tx: tx,
    }
}

#[fixture]
async fn closed_test_context() -> TestContext {
    let mut context = test_context().await;
    context.pool.close().await;
    context
}

#[fixture]
fn valid_request() -> HashMap<String, String> {
    let mut map = HashMap::new();
    map.insert("firma".into(), "Test Company".into());
    map.insert("kz".into(), "KZ123".into());
    map.insert("drc".into(), "DRC456".into());
    map.insert("ulica".into(), "Test Street".into());
    map.insert("psc".into(), "12345".into());
    map.insert("mesto".into(), "Test City".into());
    map.insert("stat".into(), "Test Country".into());
    map.insert("banka".into(), "Test Bank".into());
    map.insert("ucet".into(), "123456789".into());
    map.insert("skladm".into(), "Warehouse M".into());
    map.insert("ico".into(), "12345678".into());
    map.insert("kontakt".into(), "John Doe".into());
    map.insert("telefon".into(), "+421123456789".into());
    map.insert("skladu".into(), "Warehouse U".into());
    map.insert("fax".into(), "+421123456700".into());
    map
}

#[fixture]
fn minimal_request() -> HashMap<String, String> {
    let mut map = HashMap::new();
    map.insert("firma".into(), "Required Only".into());
    map
}

fn create_table_request(context: &TestContext, data: HashMap<String, String>) -> PostTableDataRequest {
    PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data: convert_to_proto_values(data),
    }
}

async fn assert_table_response(context: &TestContext, response: &PostTableDataResponse, expected: &HashMap<String, String>) {
    // Use dynamic query since table is created at runtime with unique names
    let query = format!(
        r#"SELECT * FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let row = sqlx::query(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    // Get values from Row dynamically
    let firma: String = row.get("firma");
    let deleted: bool = row.get("deleted");

    assert_eq!(firma, expected["firma"]);
    assert!(!deleted);

    // Check optional fields
    let check_field = |field: &str, expected_value: &str| {
        let db_value: Option<String> = row.get(field);
        assert_eq!(db_value.as_deref().unwrap_or(""), expected_value);
    };

    check_field("kz", expected.get("kz").unwrap_or(&String::new()));
    check_field("drc", expected.get("drc").unwrap_or(&String::new()));
    check_field("ulica", expected.get("ulica").unwrap_or(&String::new()));
    check_field("psc", expected.get("psc").unwrap_or(&String::new()));
    check_field("mesto", expected.get("mesto").unwrap_or(&String::new()));
    check_field("stat", expected.get("stat").unwrap_or(&String::new()));
    check_field("banka", expected.get("banka").unwrap_or(&String::new()));
    check_field("ucet", expected.get("ucet").unwrap_or(&String::new()));
    check_field("skladm", expected.get("skladm").unwrap_or(&String::new()));
    check_field("ico", expected.get("ico").unwrap_or(&String::new()));
    check_field("kontakt", expected.get("kontakt").unwrap_or(&String::new()));
    check_field("telefon", expected.get("telefon").unwrap_or(&String::new()));
    check_field("skladu", expected.get("skladu").unwrap_or(&String::new()));
    check_field("fax", expected.get("fax").unwrap_or(&String::new()));

    // Handle timestamp conversion
    let created_at: Option<chrono::DateTime<Utc>> = row.get("created_at");
    assert!(created_at.unwrap() <= Utc::now());
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_success(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let request = create_table_request(&context, valid_request.clone());
    let response = post_table_data(&context.pool, request, &context.indexer_tx).await.unwrap();

    assert!(response.inserted_id > 0);
    assert!(response.success);
    assert_eq!(response.message, "Data inserted successfully");
    assert_table_response(&context, &response, &valid_request).await;
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_whitespace_trimming(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let mut request = valid_request;
    request.insert("firma".into(), "  Test Company  ".into());
    request.insert("telefon".into(), "  +421123456789  ".into());

    let response = post_table_data(&context.pool, create_table_request(&context, request), &context.indexer_tx).await.unwrap();

    let query = format!(
        r#"SELECT firma, telefon FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let row = sqlx::query(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let firma: String = row.get("firma");
    let telefon: Option<String> = row.get("telefon");

    assert_eq!(firma, "Test Company");
    assert_eq!(telefon.unwrap(), "+421123456789");
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_empty_optional_fields(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let mut request = valid_request;
    request.insert("telefon".into(), "   ".into());

    let response = post_table_data(&context.pool, create_table_request(&context, request), &context.indexer_tx).await.unwrap();

    let query = format!(
        r#"SELECT telefon FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let telefon: Option<String> = sqlx::query_scalar(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    assert!(telefon.is_none());
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_minimal_request(
    #[future] test_context: TestContext,
    minimal_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let response = post_table_data(&context.pool, create_table_request(&context, minimal_request.clone()), &context.indexer_tx).await.unwrap();
    assert!(response.inserted_id > 0);
    assert_table_response(&context, &response, &minimal_request).await;
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_telefon_length_limit(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let mut request = valid_request;
    request.insert("telefon".into(), "1".repeat(16));

    let result = post_table_data(&context.pool, create_table_request(&context, request), &context.indexer_tx).await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::Internal);
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_special_characters(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let mut request = valid_request;
    request.insert("ulica".into(), "Náměstí 28. října 123/456".into());

    let response = post_table_data(&context.pool, create_table_request(&context, request), &context.indexer_tx).await.unwrap();

    let query = format!(
        r#"SELECT ulica FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let row = sqlx::query(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let ulica: Option<String> = row.get("ulica");
    assert_eq!(ulica.unwrap(), "Náměstí 28. října 123/456");
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_database_error(
    #[future] closed_test_context: TestContext,
    minimal_request: HashMap<String, String>,
) {
    let context = closed_test_context.await;
    let result = post_table_data(&context.pool, create_table_request(&context, minimal_request), &context.indexer_tx).await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code(), tonic::Code::Internal);
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_optional_fields_null_vs_empty(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let mut request = valid_request;
    request.insert("telefon".into(), "".into());

    let response = post_table_data(&context.pool, create_table_request(&context, request), &context.indexer_tx).await.unwrap();

    let query = format!(
        r#"SELECT telefon FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let telefon: Option<String> = sqlx::query_scalar(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    assert!(telefon.is_none());
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_field_length_limits(
    #[future] test_context: TestContext,
    valid_request: HashMap<String, String>,
) {
    let context = test_context.await;
    let mut request = valid_request;
    request.insert("firma".into(), "a".repeat(255));
    request.insert("telefon".into(), "1".repeat(15)); // Within limits

    let response = post_table_data(&context.pool, create_table_request(&context, request), &context.indexer_tx).await.unwrap();

    let query = format!(
        r#"SELECT firma, telefon FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let row = sqlx::query(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let firma: String = row.get("firma");
    let telefon: Option<String> = row.get("telefon");

    assert_eq!(firma.len(), 255);
    assert_eq!(telefon.unwrap().len(), 15);
}

#[rstest]
#[tokio::test]
async fn test_create_table_data_with_null_values(
    #[future] test_context: TestContext,
) {
    let context = test_context.await;

    // Create a request with some null values
    let mut data = HashMap::new();
    data.insert("firma".into(), string_to_proto_value("Test Company".into()));
    data.insert("telefon".into(), Value { kind: Some(Kind::NullValue(0)) }); // Explicit null
    data.insert("ulica".into(), Value { kind: None }); // Another way to represent null

    let request = PostTableDataRequest {
        profile_name: context.profile_name.clone(),
        table_name: context.table_name.clone(),
        data,
    };

    let response = post_table_data(&context.pool, request, &context.indexer_tx).await.unwrap();

    let query = format!(
        r#"SELECT firma, telefon, ulica FROM "{}"."{}" WHERE id = $1"#,
        context.profile_name, context.table_name
    );
    
    let row = sqlx::query(&query)
        .bind(response.inserted_id)
        .fetch_one(&context.pool)
        .await
        .unwrap();

    let firma: String = row.get("firma");
    let telefon: Option<String> = row.get("telefon");
    let ulica: Option<String> = row.get("ulica");

    assert_eq!(firma, "Test Company");
    assert!(telefon.is_none());
    assert!(ulica.is_none());
}

include!("post_table_data_test2.rs");
include!("post_table_data_test3.rs");
include!("post_table_data_test4.rs");
include!("post_table_data_test5.rs");
