use flaredb_client::RdbClient;
use flaredb_proto::sqlrpc::sql_service_server::SqlService as SqlServiceTrait;
use flaredb_proto::sqlrpc::{
    DdlResult, DmlResult, QueryResult as ProtoQueryResult, Row, SqlRequest, SqlResponse,
    SqlValue as ProtoValue,
};
use flaredb_sql::executor::{ExecutionResult, SqlExecutor};
use flaredb_sql::types::Value;
use std::sync::Arc;
use tokio::sync::Mutex;
use tonic::{Request, Response, Status};

pub struct SqlServiceImpl {
    /// Address of the local FlareDB server
    server_addr: String,
    /// ChainFire/PD endpoints used for region-aware routing
    pd_endpoints: Vec<String>,
}

impl SqlServiceImpl {
    pub fn new(server_addr: String, pd_endpoints: Vec<String>) -> Self {
        Self {
            server_addr,
            pd_endpoints,
        }
    }

    fn value_to_proto(value: &Value) -> ProtoValue {
        match value {
            Value::Null => ProtoValue {
                value: None,
                is_null: true,
            },
            Value::Integer(i) => ProtoValue {
                value: Some(flaredb_proto::sqlrpc::sql_value::Value::IntValue(*i)),
                is_null: false,
            },
            Value::Text(s) => ProtoValue {
                value: Some(flaredb_proto::sqlrpc::sql_value::Value::TextValue(
                    s.clone(),
                )),
                is_null: false,
            },
            Value::Boolean(b) => ProtoValue {
                value: Some(flaredb_proto::sqlrpc::sql_value::Value::BoolValue(*b)),
                is_null: false,
            },
            Value::Timestamp(ts) => ProtoValue {
                value: Some(flaredb_proto::sqlrpc::sql_value::Value::TimestampValue(*ts)),
                is_null: false,
            },
        }
    }
}

#[tonic::async_trait]
impl SqlServiceTrait for SqlServiceImpl {
    async fn execute(&self, request: Request<SqlRequest>) -> Result<Response<SqlResponse>, Status> {
        let req = request.into_inner();

        // Use PD-backed routing so SQL works for both strong and eventual namespaces.
        let client = RdbClient::connect_with_pd_namespace(
            self.server_addr.clone(),
            self.pd_endpoints.join(","),
            req.namespace.clone(),
        )
        .await
        .map_err(|e| Status::internal(format!("Failed to connect to FlareDB: {}", e)))?;

        // Create executor
        let executor = SqlExecutor::new(Arc::new(Mutex::new(client)));

        // Execute SQL
        let result = executor
            .execute(&req.sql)
            .await
            .map_err(|e| Status::internal(format!("SQL execution error: {}", e)))?;

        // Convert result to protobuf response
        let response = match result {
            ExecutionResult::DdlSuccess(message) => SqlResponse {
                result: Some(flaredb_proto::sqlrpc::sql_response::Result::DdlResult(
                    DdlResult { message },
                )),
            },
            ExecutionResult::DmlSuccess(rows_affected) => SqlResponse {
                result: Some(flaredb_proto::sqlrpc::sql_response::Result::DmlResult(
                    DmlResult { rows_affected },
                )),
            },
            ExecutionResult::Query(query_result) => {
                let rows: Vec<Row> = query_result
                    .rows
                    .iter()
                    .map(|row_values| Row {
                        values: row_values.iter().map(Self::value_to_proto).collect(),
                    })
                    .collect();

                SqlResponse {
                    result: Some(flaredb_proto::sqlrpc::sql_response::Result::QueryResult(
                        ProtoQueryResult {
                            columns: query_result.columns.to_vec(),
                            rows,
                        },
                    )),
                }
            } // Errors are returned via Result::Err and handled by .map_err() above
        };

        Ok(Response::new(response))
    }
}
