// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[cfg(test)]
mod error_handling_tests {
    use crate::get_global_db;
    use rustfs_s3select_api::{
        QueryError, SelectError,
        query::{Context, Query},
    };
    use s3s::dto::{
        CSVInput, ExpressionType, FileHeaderInfo, InputSerialization, SelectObjectContentInput, SelectObjectContentRequest,
    };
    use std::sync::Arc;

    fn create_test_input_with_sql(sql: &str) -> SelectObjectContentInput {
        SelectObjectContentInput {
            bucket: "test-bucket".to_string(),
            expected_bucket_owner: None,
            key: "test.csv".to_string(),
            sse_customer_algorithm: None,
            sse_customer_key: None,
            sse_customer_key_md5: None,
            request: SelectObjectContentRequest {
                expression: sql.to_string(),
                expression_type: ExpressionType::from_static("SQL"),
                input_serialization: InputSerialization {
                    csv: Some(CSVInput {
                        file_header_info: Some(FileHeaderInfo::from_static(FileHeaderInfo::USE)),
                        ..Default::default()
                    }),
                    ..Default::default()
                },
                output_serialization: s3s::dto::OutputSerialization::default(),
                request_progress: None,
                scan_range: None,
            },
        }
    }

    #[tokio::test]
    async fn test_syntax_error_handling() {
        let invalid_sqls = vec![
            "INVALID SQL",
            "SELECT FROM",
            "SELECT * FORM S3Object", // typo in FROM
            "SELECT * FROM",
            "SELECT * FROM S3Object WHERE",
            "SELECT COUNT( FROM S3Object", // missing closing parenthesis
        ];

        for sql in invalid_sqls {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true).await.unwrap();
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let result = db.execute(&query).await;
            assert!(result.is_err(), "Expected error for SQL: {sql}");
        }
    }

    #[tokio::test]
    async fn test_multi_statement_error() {
        let multi_statement_sqls = vec![
            "SELECT * FROM S3Object; SELECT 1;",
            "SELECT 1; SELECT 2; SELECT 3;",
            "SELECT * FROM S3Object; DROP TABLE test;",
        ];

        for sql in multi_statement_sqls {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true).await.unwrap();
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let result = db.execute(&query).await;
            assert!(result.is_err(), "Expected multi-statement error for SQL: {sql}");

            if let Err(QueryError::MultiStatement { num, .. }) = result {
                assert!(num >= 2, "Expected at least 2 statements, got: {num}");
            }
        }
    }

    #[tokio::test]
    async fn test_unsupported_operations() {
        let unsupported_sqls = vec![
            "INSERT INTO S3Object VALUES (1, 'test')",
            "UPDATE S3Object SET name = 'test'",
            "DELETE FROM S3Object",
            "DROP TABLE S3Object",
        ];

        for sql in unsupported_sqls {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true).await.unwrap();
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let result = db.execute(&query).await;
            // These should either fail with syntax error or not implemented error
            assert!(result.is_err(), "Expected error for unsupported SQL: {sql}");
        }
    }

    #[tokio::test]
    async fn test_non_select_statement_is_typed_unsupported_structure() {
        let sql = "CREATE TABLE test (id INT)";
        let input = create_test_input_with_sql(sql);
        let db = get_global_db(input.clone(), true).await.unwrap();
        let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

        let error = match db.execute(&query).await {
            Err(error) => error,
            Ok(_) => panic!("non-SELECT statement must fail"),
        };

        assert!(matches!(error.select_error(), SelectError::UnsupportedSqlStructure { .. }));
    }

    #[tokio::test]
    async fn test_function_argument_coercion_failure_is_typed() {
        for sql in ["SELECT ROUND(3.14, 1.1) FROM S3Object", "SELECT SQRT(1, 2) FROM S3Object"] {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true)
                .await
                .expect("test database should initialize");
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let error = match db.execute(&query).await {
                Err(error) => error,
                Ok(_) => panic!("invalid function arguments must fail during planning: {sql}"),
            };

            assert_eq!(
                error.select_error(),
                SelectError::IncorrectSqlFunctionArgumentType,
                "unexpected planner error for {sql}: {error:?}"
            );
        }
    }

    #[tokio::test]
    async fn test_other_planner_failures_remain_invalid_query() {
        for sql in [
            "SELECT DEFINITELY_UNKNOWN_FUNCTION(1) FROM S3Object",
            "SELECT 1 + 'text' FROM S3Object",
        ] {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true)
                .await
                .expect("test database should initialize");
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let error = match db.execute(&query).await {
                Err(error) => error,
                Ok(_) => panic!("invalid query must fail during planning: {sql}"),
            };

            assert_eq!(
                error.select_error(),
                SelectError::InvalidQuery,
                "unexpected planner error for {sql}: {error:?}"
            );
        }
    }

    #[tokio::test]
    async fn test_invalid_column_references() {
        let invalid_column_sqls = vec![
            "SELECT nonexistent_column FROM S3Object",
            "SELECT * FROM S3Object WHERE nonexistent_column = 1",
            "SELECT * FROM S3Object ORDER BY nonexistent_column",
            "SELECT * FROM S3Object GROUP BY nonexistent_column",
        ];

        for sql in invalid_column_sqls {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true).await.unwrap();
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let result = db.execute(&query).await;
            // These might succeed or fail depending on schema inference
            // The test verifies that the system handles them gracefully
            match result {
                Ok(_) => {
                    // If it succeeds, verify we can get results
                    let handle = result.unwrap();
                    let output = handle.result().chunk_result().await;
                    // Should either succeed with empty results or fail gracefully
                    let _ = output;
                }
                Err(_) => {
                    // Expected to fail - this is acceptable
                }
            }
        }
    }

    #[tokio::test]
    async fn test_complex_query_error_recovery() {
        let complex_invalid_sql = r#"
            SELECT 
                name,
                age,
                INVALID_FUNCTION(salary) as invalid_calc,
                department
            FROM S3Object 
            WHERE age > 'invalid_number'
            GROUP BY department, nonexistent_column
            HAVING COUNT(*) > INVALID_FUNCTION()
            ORDER BY invalid_column
        "#;

        let input = create_test_input_with_sql(complex_invalid_sql);
        let db = get_global_db(input.clone(), true).await.unwrap();
        let query = Query::new(Context { input: Arc::new(input) }, complex_invalid_sql.to_string());

        let result = db.execute(&query).await;
        assert!(result.is_err(), "Expected error for complex invalid SQL");
    }

    #[tokio::test]
    async fn test_empty_query() {
        let empty_sqls = vec!["", "   ", "\n\t  \n"];

        for sql in empty_sqls {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true).await.unwrap();
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let result = db.execute(&query).await;
            assert!(
                matches!(result, Err(QueryError::Parser { .. })),
                "Expected parser error for empty SQL: {sql:?}"
            );
        }
    }

    #[tokio::test]
    async fn test_very_long_query() {
        // Create a very long but valid query
        let mut long_sql = "SELECT ".to_string();
        for i in 0..1000 {
            if i > 0 {
                long_sql.push_str(", ");
            }
            long_sql.push_str(&format!("'column_{i}' as col_{i}"));
        }
        long_sql.push_str(" FROM S3Object LIMIT 1");

        let input = create_test_input_with_sql(&long_sql);
        let db = get_global_db(input.clone(), true).await.unwrap();
        let query = Query::new(Context { input: Arc::new(input) }, long_sql);

        let result = db.execute(&query).await;
        // This should either succeed or fail gracefully
        match result {
            Ok(handle) => {
                let output = handle.result().chunk_result().await;
                assert!(output.is_ok(), "Query execution should complete successfully");
            }
            Err(_) => {
                // Acceptable to fail due to resource constraints
            }
        }
    }

    #[tokio::test]
    async fn test_sql_injection_patterns() {
        let injection_patterns = vec![
            "SELECT * FROM S3Object WHERE name = 'test'; DROP TABLE users; --",
            "SELECT * FROM S3Object UNION SELECT * FROM information_schema.tables",
            "SELECT * FROM S3Object WHERE 1=1 OR 1=1",
        ];

        for sql in injection_patterns {
            let input = create_test_input_with_sql(sql);
            let db = get_global_db(input.clone(), true).await.unwrap();
            let query = Query::new(Context { input: Arc::new(input) }, sql.to_string());

            let result = db.execute(&query).await;
            // These should be handled safely - either succeed with limited scope or fail
            match result {
                Ok(_) => {
                    // If successful, it should only access S3Object data
                }
                Err(_) => {
                    // Expected to fail for security reasons
                }
            }
        }
    }
}
