use actix_web::dev::ServiceResponse;
use actix_web::{App, http::StatusCode, test, web::Data};
use athena_rs::AppState;
use athena_rs::api::gateway::{
    delete::delete_data,
    fetch::{fetch_data_route, gateway_update_route, get_data_route, proxy_fetch_data_route},
    insert::insert_data,
    postgrest::{
        postgrest_delete_route, postgrest_get_route, postgrest_patch_route, postgrest_post_route,
    },
    query::gateway_query_route,
};
use athena_rs::api::pipelines::run_pipeline;
use athena_rs::drivers::postgresql::sqlx_driver::PostgresClientRegistry;
use moka::future::Cache;
use reqwest::Client;
use serde_json::{Value, json};
use std::sync::Arc;
use std::time::Instant;

/// Build a test `AppState` with minimal configuration.
///
/// Creates an `AppState` instance with empty caches, an unconfigured HTTP client,
/// and an empty PostgreSQL client registry. Useful for gateway route tests.
///
/// # Returns
///
/// A `Data<AppState>` wrapped test state instance.
fn build_test_state() -> Data<AppState> {
    let cache: Arc<Cache<String, Value>> = Arc::new(Cache::builder().build());
    let immortal_cache: Arc<Cache<String, Value>> = Arc::new(Cache::builder().build());
    let client: Client = Client::builder()
        .build()
        .expect("Failed to build HTTP client");
    let pg_registry: Arc<PostgresClientRegistry> = Arc::new(PostgresClientRegistry::empty());
    let jdbc_pool_cache = Arc::new(Cache::builder().max_capacity(64).build());
    Data::new(AppState {
        cache,
        immortal_cache,
        client,
        process_start_time_seconds: 0,
        process_started_at: Instant::now(),
        pg_registry,
        jdbc_pool_cache,
        gateway_force_camel_case_to_snake_case: false,
        gateway_auto_cast_uuid_filter_values_to_text: true,
        pipeline_registry: None,
        logging_client_name: None,
        gateway_auth_client_name: None,
        gateway_jdbc_allow_private_hosts: true,
        gateway_jdbc_allowed_hosts: vec![],
        prometheus_metrics_enabled: false,
        metrics_state: Arc::new(athena_rs::api::metrics::MetricsState::new()),
    })
}

macro_rules! test_app {
    ($state:expr) => {
        App::new()
            .app_data($state.clone())
            .service(fetch_data_route)
            .service(proxy_fetch_data_route)
            .service(gateway_update_route)
            .service(get_data_route)
            .service(insert_data)
            .service(delete_data)
            .service(postgrest_get_route)
            .service(postgrest_post_route)
            .service(postgrest_patch_route)
            .service(postgrest_delete_route)
            .service(gateway_query_route)
            .service(run_pipeline)
    };
}

/// Test that the fetch route rejects requests without a `table_name`.
#[actix_web::test]
async fn fetch_route_requires_table_name() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::post()
        .uri("/gateway/data")
        .insert_header(("X-Athena-Client", "supabase"))
        .set_json(json!({}))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "table_name is required");
}

/// Ensure the REST compatibility route requires the Athena client header.
#[actix_web::test]
async fn rest_route_requires_client_header() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::get().uri("/rest/v1/users").to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "X-Athena-Client header is required");
}

/// Make sure PATCH requests fail when filters are missing.
#[actix_web::test]
async fn rest_patch_requires_filters() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::patch()
        .uri("/rest/v1/users")
        .insert_header(("X-Athena-Client", "supabase"))
        .set_json(json!({ "status": "active" }))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "filters are required for update");
}

/// Test that custom Supabase fetch requests require credentials headers.
#[actix_web::test]
async fn custom_supabase_fetch_route_needs_credentials() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "table_name": "users",
        "conditions": [{"eq_column": "workspace_id", "eq_value": "abc"}]
    });
    let req = test::TestRequest::post()
        .uri("/gateway/data")
        .insert_header(("X-Athena-Client", "custom_supabase"))
        .set_json(payload)
        .to_request();
    let resp: ServiceResponse = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "Missing x-supabase-url or x-supabase-key headers"
    );
}

/// Test that the `/gateway/fetch` alias produces the same error as `/gateway/data`.
#[actix_web::test]
async fn proxy_fetch_route_alias_produces_same_error() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload = json!({
        "table_name": "users",
        "conditions": [{"eq_column": "workspace_id", "eq_value": "abc"}]
    });
    let req = test::TestRequest::post()
        .uri("/gateway/fetch")
        .insert_header(("X-Athena-Client", "custom_supabase"))
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "Missing x-supabase-url or x-supabase-key headers"
    );
}

/// Test that the update route rejects requests without a body.
#[actix_web::test]
async fn update_route_requires_body() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::post()
        .uri("/gateway/update")
        .insert_header(("X-Athena-Client", "railway_direct"))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "request body is required for /gateway/update"
    );
}

/// Test that the update route rejects requests without a `table_name`.
#[actix_web::test]
async fn update_route_requires_table_name() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "columns": [{ "status": "done" }],
        "conditions": [{ "eq_column": "ticket_id", "eq_value": "f90f2386-9414-4e9f-a9a8-3824150b4883" }]
    });
    let req = test::TestRequest::post()
        .uri("/gateway/update")
        .insert_header(("X-Athena-Client", "railway_direct"))
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "table_name is required");
}

/// Test that the update route requires an update payload (columns or data/set).
#[actix_web::test]
async fn update_route_requires_update_payload() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "table_name": "users",
        "conditions": [{"eq_column": "workspace_id", "eq_value": "abc"}]
    });
    let req = test::TestRequest::post()
        .uri("/gateway/update")
        .insert_header(("X-Athena-Client", "railway_direct"))
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "update payload required: provide 'columns' (array of objects with column names and values), or 'data' / 'set' object"
    );
}

/// Test that the update route requires at least one condition.
#[actix_web::test]
async fn update_route_requires_conditions() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "table_name": "tickets",
        "columns": [{ "status": "done" }],
        "conditions": []
    });
    let req = test::TestRequest::post()
        .uri("/gateway/update")
        .insert_header(("X-Athena-Client", "railway_direct"))
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "at least one condition is required for update (e.g. eq_column / eq_value)"
    );
}

/// Test that the update route requires a configured PostgreSQL client.
#[actix_web::test]
async fn update_route_needs_configured_client() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "table_name": "tickets",
        "columns": [{ "status": "done", "closed_at": "1771873681" }],
        "conditions": [{ "eq_column": "ticket_id", "eq_value": "f90f2386-9414-4e9f-a9a8-3824150b4883" }]
    });
    let req = test::TestRequest::post()
        .uri("/gateway/update")
        .insert_header(("X-Athena-Client", "railway_direct"))
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "Postgres client 'railway_direct' is not configured"
    );
}

/// Test that the update route accepts a `data` object as the update payload.
#[actix_web::test]
async fn update_route_accepts_data_object() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "table_name": "tickets",
        "data": { "status": "done" },
        "conditions": [{ "eq_column": "ticket_id", "eq_value": "f90f2386-9414-4e9f-a9a8-3824150b4883" }]
    });
    let req = test::TestRequest::post()
        .uri("/gateway/update")
        .insert_header(("X-Athena-Client", "railway_direct"))
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "Postgres client 'railway_direct' is not configured"
    );
}

/// Test that the GET data route requires a `view` query parameter.
#[actix_web::test]
async fn get_data_route_requires_view_parameter() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::get()
        .uri("/data")
        .insert_header(("X-Athena-Client", "supabase"))
        .to_request();
    let resp: ServiceResponse = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "Missing required parameter: view");
}

/// Test that the insert route with API key fallback requires an `insert_body` field.
#[actix_web::test]
async fn insert_route_apikey_fallback_requires_insert_body() {
    unsafe {
        std::env::set_var("SUITSBOOKS_API_ADMIN_KEY", "admin-secret");
    }
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::put()
        .uri("/gateway/insert")
        .insert_header(("Content-Type", "application/json"))
        .insert_header(("apikey", "admin-secret"))
        .insert_header(("X-Athena-Client", "supabase"))
        .set_json(json!({ "table_name": "users" }))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["message"],
        "insert_body is required in the request body"
    );
}

/// Test that the fetch route requires `X-Athena-Client` and rejects blank/missing values.
#[actix_web::test]
async fn fetch_route_requires_client_header() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::post()
        .uri("/gateway/fetch")
        .insert_header(("Content-Type", "application/json"))
        .set_json(json!({
            "table_name": "users",
            "conditions": [{"eq_column": "id", "eq_value": "1"}]
        }))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["code"], "missing_client_header");
}

/// Test that the insert route requires `X-Athena-Client` even with API-key fallback auth.
#[actix_web::test]
async fn insert_route_requires_client_header() {
    unsafe {
        std::env::set_var("SUITSBOOKS_API_ADMIN_KEY", "admin-secret");
    }
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::put()
        .uri("/gateway/insert")
        .insert_header(("Content-Type", "application/json"))
        .insert_header(("apikey", "admin-secret"))
        .set_json(json!({
            "table_name": "users",
            "insert_body": { "name": "Alice" }
        }))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["code"], "missing_client_header");
}

/// Test that the delete route rejects requests without an `X-User-Id` header.
#[actix_web::test]
async fn delete_route_rejects_missing_user_id() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::delete()
        .uri("/gateway/delete")
        .set_json(json!({
            "table_name": "users",
            "resource_id": "user-123"
        }))
        .to_request();
    let resp: actix_web::dev::ServiceResponse = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "X-User-Id header not found in the request");
}

/// Test that the query route requires a configured PostgreSQL client.
#[actix_web::test]
async fn query_route_needs_configured_client() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::post()
        .uri("/gateway/query")
        .insert_header(("X-Athena-Client", "reporting"))
        .set_json(json!({ "query": "SELECT 1" }))
        .to_request();
    let resp: actix_web::dev::ServiceResponse = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(
        body["error"],
        "Postgres client 'reporting' is not configured"
    );
}

/// Test that POST /pipelines requires X-Athena-Client header.
#[actix_web::test]
async fn pipelines_route_requires_x_athena_client() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "source": { "table_name": "users", "columns": ["id"], "conditions": [] },
        "sink": { "table_name": "users_backup" }
    });
    let req = test::TestRequest::post()
        .uri("/pipelines")
        .set_json(payload)
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "X-Athena-Client header is required");
}

/// Test that POST /pipelines requires a request body.
#[actix_web::test]
async fn pipelines_route_requires_body() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let req = test::TestRequest::post()
        .uri("/pipelines")
        .insert_header(("X-Athena-Client", "supabase"))
        .to_request();
    let resp = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert_eq!(body["error"], "Request body is required");
}

/// Test that POST /pipelines rejects unknown pipeline name when using pipeline reference.
#[actix_web::test]
async fn pipelines_route_rejects_unknown_pipeline_name() {
    let state: Data<AppState> = build_test_state();
    let app = test::init_service(test_app!(state)).await;

    let payload: Value = json!({
        "pipeline": "nonexistent_pipeline"
    });
    let req = test::TestRequest::post()
        .uri("/pipelines")
        .insert_header(("X-Athena-Client", "supabase"))
        .set_json(payload)
        .to_request();
    let resp: ServiceResponse = test::call_service(&app, req).await;

    assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
    let body: Value = test::read_body_json(resp).await;
    assert!(
        body["error"]
            .as_str()
            .unwrap_or("")
            .contains("unknown pipeline")
    );
}
