//! Public gateway-facing SDK request types and route constants.
use crate::client::backend::QueryResult;
use crate::client::query_builder::{Condition, ConditionOperator, OrderDirection};
use serde_json::{Map, Value, json};

/// Structured gateway namespace with canonical paths and endpoint builders.
#[derive(Debug, Clone, Copy, Default)]
pub struct Gateway;

impl Gateway {
    /// Canonical gateway route for fetch/select operations.
    pub const FETCH_PATH: &str = "/gateway/fetch";
    /// Canonical gateway route for insert operations.
    pub const INSERT_PATH: &str = "/gateway/insert";
    /// Canonical gateway route for update operations.
    pub const UPDATE_PATH: &str = "/gateway/update";
    /// Canonical gateway route for delete operations.
    pub const DELETE_PATH: &str = "/gateway/delete";
    /// Canonical gateway route for raw query fallback.
    pub const QUERY_PATH: &str = "/gateway/query";
    /// Canonical gateway route for SQL execution.
    pub const SQL_PATH: &str = "/gateway/sql";
    /// Canonical gateway route for RPC execution.
    pub const RPC_PATH: &str = "/gateway/rpc";
    /// Backward-compatible SQL route still used by older servers.
    pub const LEGACY_SQL_PATH: &str = "/query/sql";

    /// Build fully-qualified gateway endpoints from a base URL.
    pub fn routes(base_url: &str) -> GatewayRoutes {
        GatewayRoutes::for_base_url(base_url)
    }

    /// Join base URL and route path into a stable endpoint URL.
    pub fn endpoint(base_url: &str, path: &str) -> String {
        format!(
            "{}/{}",
            base_url.trim_end_matches('/'),
            path.trim_start_matches('/')
        )
    }

    /// Resolve the canonical route path for an operation.
    pub fn path_for(operation: GatewayOperation) -> &'static str {
        operation.gateway_path()
    }

    /// Namespaced request factory.
    pub fn request() -> GatewayRequestFactory {
        GatewayRequestFactory
    }

    /// Const request namespace (`Gateway::REQUEST.delete(...)`).
    pub const REQUEST: GatewayRequestFactory = GatewayRequestFactory;
}

/// Canonical gateway route for fetch/select operations.
pub const GATEWAY_FETCH_PATH: &str = Gateway::FETCH_PATH;
/// Canonical gateway route for insert operations.
pub const GATEWAY_INSERT_PATH: &str = Gateway::INSERT_PATH;
/// Canonical gateway route for update operations.
pub const GATEWAY_UPDATE_PATH: &str = Gateway::UPDATE_PATH;
/// Canonical gateway route for delete operations.
pub const GATEWAY_DELETE_PATH: &str = Gateway::DELETE_PATH;
/// Canonical gateway route for raw query fallback.
pub const GATEWAY_QUERY_PATH: &str = Gateway::QUERY_PATH;
/// Canonical gateway route for SQL execution.
pub const GATEWAY_SQL_PATH: &str = Gateway::SQL_PATH;
/// Canonical gateway route for RPC execution.
pub const GATEWAY_RPC_PATH: &str = Gateway::RPC_PATH;
/// Backward-compatible SQL route still used by older servers.
pub const LEGACY_SQL_PATH: &str = Gateway::LEGACY_SQL_PATH;

/// Materialized full gateway endpoints for a given base URL.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GatewayRoutes {
    pub fetch: String,
    pub insert: String,
    pub update: String,
    pub delete: String,
    pub query: String,
    pub sql: String,
    pub rpc: String,
}

impl GatewayRoutes {
    /// Build fully-qualified gateway endpoints from a base URL.
    pub fn for_base_url(base_url: &str) -> Self {
        Self {
            fetch: Gateway::endpoint(base_url, Gateway::FETCH_PATH),
            insert: Gateway::endpoint(base_url, Gateway::INSERT_PATH),
            update: Gateway::endpoint(base_url, Gateway::UPDATE_PATH),
            delete: Gateway::endpoint(base_url, Gateway::DELETE_PATH),
            query: Gateway::endpoint(base_url, Gateway::QUERY_PATH),
            sql: Gateway::endpoint(base_url, Gateway::SQL_PATH),
            rpc: Gateway::endpoint(base_url, Gateway::RPC_PATH),
        }
    }
}

/// Join base URL and route path into a stable endpoint URL.
pub fn build_gateway_endpoint(base_url: &str, path: &str) -> String {
    Gateway::endpoint(base_url, path)
}

/// Supported SDK gateway operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GatewayOperation {
    Fetch,
    Insert,
    Update,
    Delete,
    Sql,
    Rpc,
}

/// Path resolver trait for gateway operations and requests.
pub trait GatewayPath {
    /// Return the canonical HTTP path for this operation/request.
    fn gateway_path(&self) -> &'static str;
}

/// Canonical request contract implemented by all typed gateway requests.
pub trait GatewayDriverRequest: GatewayPath + Clone + Into<GatewayRequest> {
    /// Canonical operation represented by this request type.
    const OPERATION: GatewayOperation;

    /// Return the operation represented by this concrete request value.
    fn operation(&self) -> GatewayOperation {
        Self::OPERATION
    }

    /// Return the canonical HTTP path for this request type.
    fn path() -> &'static str {
        Gateway::path_for(Self::OPERATION)
    }
}

impl GatewayPath for GatewayOperation {
    fn gateway_path(&self) -> &'static str {
        match self {
            GatewayOperation::Fetch => Gateway::FETCH_PATH,
            GatewayOperation::Insert => Gateway::INSERT_PATH,
            GatewayOperation::Update => Gateway::UPDATE_PATH,
            GatewayOperation::Delete => Gateway::DELETE_PATH,
            GatewayOperation::Sql => Gateway::SQL_PATH,
            GatewayOperation::Rpc => Gateway::RPC_PATH,
        }
    }
}

/// Canonical query result type returned by gateway SDK methods.
pub type GatewayQueryResult = QueryResult;

/// Typed payload variants for gateway request dispatch.
#[derive(Debug, Clone)]
pub enum GatewayRequestPayload {
    Fetch(GatewayFetchRequest),
    Insert(GatewayInsertRequest),
    Update(GatewayUpdateRequest),
    Delete(GatewayDeleteRequest),
    Sql(GatewaySqlRequest),
    Rpc(GatewayRpcRequest),
}

/// Unified request wrapper for SDK gateway operations.
#[derive(Debug, Clone)]
pub struct GatewayRequest {
    payload: GatewayRequestPayload,
}

impl GatewayRequest {
    /// Create a typed fetch request wrapper.
    pub fn fetch(table: impl Into<String>) -> Self {
        GatewayFetchRequest::new(table).into()
    }

    /// Create a typed insert request wrapper.
    pub fn insert(table: impl Into<String>, payload: Value) -> Self {
        GatewayInsertRequest::new(table, payload).into()
    }

    /// Create a typed update request wrapper.
    pub fn update(table: impl Into<String>, payload: Value) -> Self {
        GatewayUpdateRequest::new(table, payload).into()
    }

    /// Create a typed delete request wrapper.
    pub fn delete(table: impl Into<String>) -> Self {
        GatewayDeleteRequest::new(table).into()
    }

    /// Create a typed SQL request wrapper.
    pub fn sql(query: impl Into<String>) -> Self {
        GatewaySqlRequest::new(query).into()
    }

    /// Create a typed RPC request wrapper.
    pub fn rpc(function: impl Into<String>, args: Value) -> Self {
        GatewayRpcRequest::new(function, args).into()
    }

    /// Return the operation represented by this request.
    pub fn operation(&self) -> GatewayOperation {
        match &self.payload {
            GatewayRequestPayload::Fetch(_) => GatewayOperation::Fetch,
            GatewayRequestPayload::Insert(_) => GatewayOperation::Insert,
            GatewayRequestPayload::Update(_) => GatewayOperation::Update,
            GatewayRequestPayload::Delete(_) => GatewayOperation::Delete,
            GatewayRequestPayload::Sql(_) => GatewayOperation::Sql,
            GatewayRequestPayload::Rpc(_) => GatewayOperation::Rpc,
        }
    }

    /// Resolve the canonical route path for this request.
    pub fn path(&self) -> &'static str {
        self.operation().gateway_path()
    }

    /// Borrow the typed payload.
    pub fn payload(&self) -> &GatewayRequestPayload {
        &self.payload
    }

    /// Consume into the typed payload.
    pub fn into_payload(self) -> GatewayRequestPayload {
        self.payload
    }
}

/// Canonical request namespace types (`Fetch`, `Insert`, `Update`, `Delete`, `Sql`).
pub mod request {
    pub type Fetch = super::GatewayFetchRequest;
    pub type Insert = super::GatewayInsertRequest;
    pub type Update = super::GatewayUpdateRequest;
    pub type Delete = super::GatewayDeleteRequest;
    pub type Sql = super::GatewaySqlRequest;
    pub type Rpc = super::GatewayRpcRequest;
}

/// Namespaced request constructor API for `Gateway::request()`.
#[derive(Debug, Clone, Copy, Default)]
pub struct GatewayRequestFactory;

impl GatewayRequestFactory {
    /// Build a typed fetch request for `/gateway/fetch`.
    pub fn fetch(&self, table: impl Into<String>) -> request::Fetch {
        GatewayFetchRequest::new(table)
    }

    /// Build a typed insert request for `/gateway/insert`.
    pub fn insert(&self, table: impl Into<String>, payload: Value) -> request::Insert {
        GatewayInsertRequest::new(table, payload)
    }

    /// Build a typed update request for `/gateway/update`.
    pub fn update(&self, table: impl Into<String>, payload: Value) -> request::Update {
        GatewayUpdateRequest::new(table, payload)
    }

    /// Build a typed delete request for `/gateway/delete`.
    pub fn delete(&self, table: impl Into<String>) -> request::Delete {
        GatewayDeleteRequest::new(table)
    }

    /// Build a typed SQL request for `/gateway/sql`.
    pub fn sql(&self, query: impl Into<String>) -> request::Sql {
        GatewaySqlRequest::new(query)
    }

    /// Build a typed RPC request for `/gateway/rpc`.
    pub fn rpc(&self, function: impl Into<String>, args: Value) -> request::Rpc {
        GatewayRpcRequest::new(function, args)
    }
}

impl From<GatewayRequestPayload> for GatewayRequest {
    fn from(payload: GatewayRequestPayload) -> Self {
        Self { payload }
    }
}

impl From<GatewayFetchRequest> for GatewayRequest {
    fn from(request: GatewayFetchRequest) -> Self {
        GatewayRequestPayload::Fetch(request).into()
    }
}

impl From<GatewayInsertRequest> for GatewayRequest {
    fn from(request: GatewayInsertRequest) -> Self {
        GatewayRequestPayload::Insert(request).into()
    }
}

impl From<GatewayUpdateRequest> for GatewayRequest {
    fn from(request: GatewayUpdateRequest) -> Self {
        GatewayRequestPayload::Update(request).into()
    }
}

impl From<GatewayDeleteRequest> for GatewayRequest {
    fn from(request: GatewayDeleteRequest) -> Self {
        GatewayRequestPayload::Delete(request).into()
    }
}

impl From<GatewaySqlRequest> for GatewayRequest {
    fn from(request: GatewaySqlRequest) -> Self {
        GatewayRequestPayload::Sql(request).into()
    }
}

impl From<GatewayRpcRequest> for GatewayRequest {
    fn from(request: GatewayRpcRequest) -> Self {
        GatewayRequestPayload::Rpc(request).into()
    }
}

/// Typed fetch request for `/gateway/fetch`.
#[derive(Debug, Clone)]
pub struct GatewayFetchRequest {
    pub table: String,
    pub columns: Vec<String>,
    pub raw_select: Option<String>,
    pub conditions: Vec<Condition>,
    pub order_by: Vec<(String, OrderDirection)>,
    pub limit: Option<usize>,
    pub offset: Option<usize>,
}

impl GatewayFetchRequest {
    /// Create a new fetch request.
    pub fn new(table: impl Into<String>) -> Self {
        Self {
            table: table.into(),
            columns: Vec::new(),
            raw_select: None,
            conditions: Vec::new(),
            order_by: Vec::new(),
            limit: None,
            offset: None,
        }
    }

    /// Alias for `new`.
    pub fn fetch(table: impl Into<String>) -> Self {
        Self::new(table)
    }

    /// Set requested columns.
    pub fn columns(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
        self.columns = columns.into_iter().map(Into::into).collect();
        self
    }

    /// Set raw PostgREST select projection.
    pub fn raw_select(mut self, select: impl Into<String>) -> Self {
        self.raw_select = Some(select.into());
        self
    }

    fn add_condition(
        mut self,
        column: impl Into<String>,
        operator: ConditionOperator,
        values: Vec<Value>,
    ) -> Self {
        self.conditions
            .push(Condition::new(column.into(), operator, values));
        self
    }

    /// Add one condition.
    pub fn where_condition(mut self, condition: Condition) -> Self {
        self.conditions.push(condition);
        self
    }

    /// Add multiple conditions.
    pub fn where_conditions(mut self, conditions: impl IntoIterator<Item = Condition>) -> Self {
        self.conditions.extend(conditions);
        self
    }

    /// Add an equality condition.
    pub fn where_eq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Eq, vec![value.into()])
    }

    /// Add a not-equal condition.
    pub fn where_neq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Neq, vec![value.into()])
    }

    /// Add a greater-than condition.
    pub fn where_gt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Gt, vec![value.into()])
    }

    /// Add a less-than condition.
    pub fn where_lt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Lt, vec![value.into()])
    }

    /// Add an IN condition.
    pub fn where_in(self, column: impl Into<String>, values: Vec<Value>) -> Self {
        self.add_condition(column, ConditionOperator::In, values)
    }

    /// Add an ORDER BY clause.
    pub fn order_by(mut self, column: impl Into<String>, direction: OrderDirection) -> Self {
        self.order_by.push((column.into(), direction));
        self
    }

    /// Set result limit.
    pub fn limit(mut self, limit: usize) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Set result offset.
    pub fn offset(mut self, offset: usize) -> Self {
        self.offset = Some(offset);
        self
    }
}

impl GatewayPath for GatewayFetchRequest {
    fn gateway_path(&self) -> &'static str {
        Gateway::FETCH_PATH
    }
}

impl GatewayDriverRequest for GatewayFetchRequest {
    const OPERATION: GatewayOperation = GatewayOperation::Fetch;
}

/// Marker scope for insert mutations (no filter scope fields).
#[derive(Debug, Clone, Default)]
pub struct GatewayInsertScope;

/// Scope fields for update mutations.
#[derive(Debug, Clone, Default)]
pub struct GatewayUpdateScope {
    pub row_id: Option<String>,
    pub conditions: Vec<Condition>,
    pub allow_unfiltered: bool,
}

/// Shared mutation request implementation used by both insert and update operations.
#[derive(Debug, Clone)]
pub struct GatewayMutationRequest<S> {
    pub table: String,
    pub payload: Value,
    pub scope: S,
}

impl<S: Default> GatewayMutationRequest<S> {
    /// Create a new mutation request.
    pub fn new(table: impl Into<String>, payload: Value) -> Self {
        Self {
            table: table.into(),
            payload,
            scope: S::default(),
        }
    }

    /// Replace payload.
    pub fn payload(mut self, payload: Value) -> Self {
        self.payload = payload;
        self
    }
}

/// Typed insert request for `/gateway/insert`.
pub type GatewayInsertRequest = GatewayMutationRequest<GatewayInsertScope>;

impl GatewayInsertRequest {
    /// Alias for `new`.
    pub fn insert(table: impl Into<String>, payload: Value) -> Self {
        Self::new(table, payload)
    }
}

impl GatewayPath for GatewayInsertRequest {
    fn gateway_path(&self) -> &'static str {
        Gateway::INSERT_PATH
    }
}

impl GatewayDriverRequest for GatewayInsertRequest {
    const OPERATION: GatewayOperation = GatewayOperation::Insert;
}

/// Typed update request for `/gateway/update`.
pub type GatewayUpdateRequest = GatewayMutationRequest<GatewayUpdateScope>;

impl GatewayUpdateRequest {
    /// Alias for `new`.
    pub fn update(table: impl Into<String>, payload: Value) -> Self {
        Self::new(table, payload)
    }

    /// Target a specific row id (`id = <row_id>`).
    pub fn row_id(mut self, row_id: impl Into<String>) -> Self {
        self.scope.row_id = Some(row_id.into());
        self
    }

    /// ## `where_condition`
    /// Add a condition.
    ///
    /// # Arguments
    ///
    /// * `column` - The column.
    /// * `operator` - The operator.
    /// * `values` - The values.
    ///
    /// # Returns
    ///
    /// A `GatewayUpdateRequest` containing the updated request.
    ///
    /// # Examples
    ///
    /// ```
    /// let request = GatewayUpdateRequest::new("users", json!({ "status": "active" }));
    /// let request = request.add_condition("email", ConditionOperator::Eq, vec![Value::String("alice@example.com".to_string())]);
    /// assert_eq!(request.scope.conditions.len(), 1);
    /// assert_eq!(request.scope.conditions[0].column, "email");
    /// assert_eq!(request.scope.conditions[0].operator, ConditionOperator::Eq);
    /// assert_eq!(request.scope.conditions[0].values, vec![Value::String("alice@example.com".to_string())]);
    /// ```
    ///
    fn add_condition(
        mut self,
        column: impl Into<String>,
        operator: ConditionOperator,
        values: Vec<Value>,
    ) -> Self {
        self.scope
            .conditions
            .push(Condition::new(column.into(), operator, values));
        self
    }

    /// Add one condition.
    pub fn where_condition(mut self, condition: Condition) -> Self {
        self.scope.conditions.push(condition);
        self
    }

    /// Add multiple conditions.
    pub fn where_conditions(mut self, conditions: impl IntoIterator<Item = Condition>) -> Self {
        self.scope.conditions.extend(conditions);
        self
    }

    /// Add an equality condition.
    pub fn where_eq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Eq, vec![value.into()])
    }

    /// Add a not-equal condition.
    pub fn where_neq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Neq, vec![value.into()])
    }

    /// Add a greater-than condition.
    pub fn where_gt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Gt, vec![value.into()])
    }

    /// Add a less-than condition.
    pub fn where_lt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Lt, vec![value.into()])
    }

    /// Add an IN condition.
    pub fn where_in(self, column: impl Into<String>, values: Vec<Value>) -> Self {
        self.add_condition(column, ConditionOperator::In, values)
    }

    /// Explicitly allow unfiltered updates.
    pub fn unsafe_unfiltered(mut self) -> Self {
        self.scope.allow_unfiltered = true;
        self
    }
}

impl GatewayPath for GatewayUpdateRequest {
    /// ## `gateway_path`
    /// Return the canonical HTTP path for this operation/request.
    ///
    /// # Returns
    ///
    /// A `&'static str` containing the canonical HTTP path.
    ///
    /// # Examples
    ///
    /// ```
    /// let request = GatewayUpdateRequest::new("users", json!({ "status": "active" }));
    /// assert_eq!(request.gateway_path(), Gateway::UPDATE_PATH);
    /// ```
    ///
    fn gateway_path(&self) -> &'static str {
        Gateway::UPDATE_PATH
    }
}

impl GatewayDriverRequest for GatewayUpdateRequest {
    const OPERATION: GatewayOperation = GatewayOperation::Update;
}

/// Typed delete request for `/gateway/delete`.
#[derive(Debug, Clone)]
pub struct GatewayDeleteRequest {
    pub table: String,
    pub row_id: Option<String>,
    pub conditions: Vec<Condition>,
    pub allow_unfiltered: bool,
}

impl GatewayDeleteRequest {
    /// Create a new delete request.
    pub fn new(table: impl Into<String>) -> Self {
        Self {
            table: table.into(),
            row_id: None,
            conditions: Vec::new(),
            allow_unfiltered: false,
        }
    }

    /// Alias for `new`.
    pub fn delete(table: impl Into<String>) -> Self {
        Self::new(table)
    }

    /// Target a specific row id (`id = <row_id>`).
    pub fn row_id(mut self, row_id: impl Into<String>) -> Self {
        self.row_id = Some(row_id.into());
        self
    }

    fn add_condition(
        mut self,
        column: impl Into<String>,
        operator: ConditionOperator,
        values: Vec<Value>,
    ) -> Self {
        self.conditions
            .push(Condition::new(column.into(), operator, values));
        self
    }

    /// Add one condition.
    pub fn where_condition(mut self, condition: Condition) -> Self {
        self.conditions.push(condition);
        self
    }

    /// Add multiple conditions.
    pub fn where_conditions(mut self, conditions: impl IntoIterator<Item = Condition>) -> Self {
        self.conditions.extend(conditions);
        self
    }

    /// Add an equality condition.
    pub fn where_eq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Eq, vec![value.into()])
    }

    /// Add a not-equal condition.
    pub fn where_neq(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Neq, vec![value.into()])
    }

    /// Add a greater-than condition.
    pub fn where_gt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Gt, vec![value.into()])
    }

    /// Add a less-than condition.
    pub fn where_lt(self, column: impl Into<String>, value: impl Into<Value>) -> Self {
        self.add_condition(column, ConditionOperator::Lt, vec![value.into()])
    }

    /// Add an IN condition.
    pub fn where_in(self, column: impl Into<String>, values: Vec<Value>) -> Self {
        self.add_condition(column, ConditionOperator::In, values)
    }

    /// Explicitly allow unfiltered deletes.
    pub fn unsafe_unfiltered(mut self) -> Self {
        self.allow_unfiltered = true;
        self
    }
}

impl GatewayPath for GatewayDeleteRequest {
    fn gateway_path(&self) -> &'static str {
        Gateway::DELETE_PATH
    }
}

impl GatewayDriverRequest for GatewayDeleteRequest {
    const OPERATION: GatewayOperation = GatewayOperation::Delete;
}

/// Typed SQL request for `/gateway/sql`.
#[derive(Debug, Clone)]
pub struct GatewaySqlRequest {
    pub query: String,
}

impl GatewaySqlRequest {
    /// Create a new SQL request.
    pub fn new(query: impl Into<String>) -> Self {
        Self {
            query: query.into(),
        }
    }

    /// Alias for `new`.
    pub fn sql(query: impl Into<String>) -> Self {
        Self::new(query)
    }
}

impl GatewayPath for GatewaySqlRequest {
    fn gateway_path(&self) -> &'static str {
        Gateway::SQL_PATH
    }
}

impl GatewayDriverRequest for GatewaySqlRequest {
    const OPERATION: GatewayOperation = GatewayOperation::Sql;
}

/// RPC filter operators supported by `/gateway/rpc`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GatewayRpcFilterOperator {
    Eq,
    Neq,
    Gt,
    Gte,
    Lt,
    Lte,
    In,
    Like,
    ILike,
    Is,
}

impl GatewayRpcFilterOperator {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Eq => "eq",
            Self::Neq => "neq",
            Self::Gt => "gt",
            Self::Gte => "gte",
            Self::Lt => "lt",
            Self::Lte => "lte",
            Self::In => "in",
            Self::Like => "like",
            Self::ILike => "ilike",
            Self::Is => "is",
        }
    }
}

/// RPC filter descriptor used by [`GatewayRpcRequest`].
#[derive(Debug, Clone, PartialEq)]
pub struct GatewayRpcFilter {
    pub column: String,
    pub operator: GatewayRpcFilterOperator,
    pub value: Value,
}

/// Typed RPC request for `/gateway/rpc`.
#[derive(Debug, Clone)]
pub struct GatewayRpcRequest {
    pub function: String,
    pub schema: String,
    pub args: Value,
    pub select: Option<String>,
    pub filters: Vec<GatewayRpcFilter>,
    pub count: Option<String>,
    pub limit: Option<usize>,
    pub offset: Option<usize>,
    pub order: Option<(String, OrderDirection)>,
}

impl GatewayRpcRequest {
    /// Create a new RPC request.
    pub fn new(function: impl Into<String>, args: Value) -> Self {
        Self {
            function: function.into(),
            schema: "public".to_string(),
            args,
            select: None,
            filters: Vec::new(),
            count: None,
            limit: None,
            offset: None,
            order: None,
        }
    }

    /// Alias for `new`.
    pub fn rpc(function: impl Into<String>, args: Value) -> Self {
        Self::new(function, args)
    }

    /// Set schema override.
    pub fn schema(mut self, schema: impl Into<String>) -> Self {
        self.schema = schema.into();
        self
    }

    /// Set select projection.
    pub fn select(mut self, select: impl Into<String>) -> Self {
        self.select = Some(select.into());
        self
    }

    /// Add one filter descriptor.
    pub fn filter(
        mut self,
        column: impl Into<String>,
        operator: GatewayRpcFilterOperator,
        value: Value,
    ) -> Self {
        self.filters.push(GatewayRpcFilter {
            column: column.into(),
            operator,
            value,
        });
        self
    }

    /// Enable exact count.
    pub fn count_exact(mut self) -> Self {
        self.count = Some("exact".to_string());
        self
    }

    /// Set result ordering.
    pub fn order_by(mut self, column: impl Into<String>, direction: OrderDirection) -> Self {
        self.order = Some((column.into(), direction));
        self
    }

    /// Set result limit.
    pub fn limit(mut self, limit: usize) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Set result offset.
    pub fn offset(mut self, offset: usize) -> Self {
        self.offset = Some(offset);
        self
    }

    /// Convert to gateway JSON body.
    pub fn to_body_json(&self) -> Value {
        let mut object = Map::new();
        object.insert("function".to_string(), Value::String(self.function.clone()));
        object.insert("schema".to_string(), Value::String(self.schema.clone()));
        object.insert("args".to_string(), self.args.clone());

        if let Some(select) = self.select.clone() {
            object.insert("select".to_string(), Value::String(select));
        }
        if !self.filters.is_empty() {
            object.insert(
                "filters".to_string(),
                Value::Array(
                    self.filters
                        .iter()
                        .map(|filter| {
                            json!({
                                "column": filter.column,
                                "operator": filter.operator.as_str(),
                                "value": filter.value,
                            })
                        })
                        .collect(),
                ),
            );
        }
        if let Some(count) = self.count.clone() {
            object.insert("count".to_string(), Value::String(count));
        }
        if let Some(limit) = self.limit {
            object.insert("limit".to_string(), json!(limit));
        }
        if let Some(offset) = self.offset {
            object.insert("offset".to_string(), json!(offset));
        }
        if let Some((column, direction)) = &self.order {
            object.insert(
                "order".to_string(),
                json!({
                    "column": column,
                    "ascending": matches!(direction, OrderDirection::Asc),
                }),
            );
        }
        Value::Object(object)
    }
}

impl GatewayPath for GatewayRpcRequest {
    fn gateway_path(&self) -> &'static str {
        Gateway::RPC_PATH
    }
}

impl GatewayDriverRequest for GatewayRpcRequest {
    const OPERATION: GatewayOperation = GatewayOperation::Rpc;
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn gateway_request_fetch_sets_fetch_shape() {
        let request = GatewayRequest::fetch("users");
        assert_eq!(request.operation(), GatewayOperation::Fetch);
        let GatewayRequestPayload::Fetch(fetch) = request.payload() else {
            panic!("expected fetch payload");
        };
        assert_eq!(fetch.table, "users");
        assert!(fetch.columns.is_empty());
    }

    #[test]
    fn gateway_request_insert_sets_insert_shape() {
        let request = GatewayRequest::insert("users", json!({ "email": "a@example.com" }));
        assert_eq!(request.operation(), GatewayOperation::Insert);
        let GatewayRequestPayload::Insert(insert) = request.payload() else {
            panic!("expected insert payload");
        };
        assert_eq!(insert.table, "users");
        assert_eq!(insert.payload["email"], json!("a@example.com"));
    }

    #[test]
    fn gateway_request_update_sets_update_shape() {
        let request = GatewayRequest::update("users", json!({ "status": "active" }));
        assert_eq!(request.operation(), GatewayOperation::Update);
        let GatewayRequestPayload::Update(update) = request.payload() else {
            panic!("expected update payload");
        };
        assert_eq!(update.table, "users");
        assert_eq!(update.payload["status"], json!("active"));
    }

    #[test]
    fn gateway_request_delete_sets_delete_shape() {
        let request = GatewayRequest::delete("users");
        assert_eq!(request.operation(), GatewayOperation::Delete);
        let GatewayRequestPayload::Delete(delete) = request.payload() else {
            panic!("expected delete payload");
        };
        assert_eq!(delete.table, "users");
        assert!(delete.conditions.is_empty());
    }

    #[test]
    fn gateway_request_sql_sets_sql_shape() {
        let request = GatewayRequest::sql("select 1");
        assert_eq!(request.operation(), GatewayOperation::Sql);
        let GatewayRequestPayload::Sql(sql) = request.payload() else {
            panic!("expected sql payload");
        };
        assert_eq!(sql.query, "select 1");
    }

    #[test]
    fn gateway_request_rpc_sets_rpc_shape() {
        let request = GatewayRequest::rpc("hello_world", json!({ "name": "Athena" }));
        assert_eq!(request.operation(), GatewayOperation::Rpc);
        let GatewayRequestPayload::Rpc(rpc) = request.payload() else {
            panic!("expected rpc payload");
        };
        assert_eq!(rpc.function, "hello_world");
        assert_eq!(rpc.args["name"], json!("Athena"));
    }

    #[test]
    fn gateway_fetch_request_builder_sets_expected_shape() {
        let request = GatewayFetchRequest::new("users")
            .columns(["id", "email"])
            .where_eq("status", json!("active"))
            .order_by("id", OrderDirection::Desc)
            .limit(10)
            .offset(5);

        assert_eq!(request.table, "users");
        assert_eq!(request.columns, vec!["id".to_string(), "email".to_string()]);
        assert_eq!(request.conditions.len(), 1);
        assert_eq!(request.limit, Some(10));
        assert_eq!(request.offset, Some(5));
    }

    #[test]
    fn gateway_operation_resolves_canonical_paths() {
        assert_eq!(GatewayOperation::Fetch.gateway_path(), Gateway::FETCH_PATH);
        assert_eq!(
            GatewayOperation::Insert.gateway_path(),
            Gateway::INSERT_PATH
        );
        assert_eq!(
            GatewayOperation::Update.gateway_path(),
            Gateway::UPDATE_PATH
        );
        assert_eq!(
            GatewayOperation::Delete.gateway_path(),
            Gateway::DELETE_PATH
        );
        assert_eq!(GatewayOperation::Sql.gateway_path(), Gateway::SQL_PATH);
        assert_eq!(GatewayOperation::Rpc.gateway_path(), Gateway::RPC_PATH);
    }

    #[test]
    fn gateway_request_path_matches_operation_path() {
        let request = GatewayRequest::delete("users");
        assert_eq!(request.path(), Gateway::DELETE_PATH);
    }

    #[test]
    fn gateway_request_factory_builds_typed_requests() {
        let update = Gateway::request().update("users", json!({ "status": "active" }));
        assert_eq!(update.table, "users");
        assert_eq!(update.payload["status"], json!("active"));

        let delete = Gateway::request().delete("users");
        assert_eq!(delete.table, "users");

        let delete_via_const = Gateway::REQUEST.delete("users");
        assert_eq!(delete_via_const.table, "users");

        let rpc = Gateway::request().rpc("hello_world", json!({}));
        assert_eq!(rpc.function, "hello_world");
    }

    #[test]
    fn rpc_request_serializes_filters_and_count() {
        let request = GatewayRpcRequest::new("echo_all_cities", json!({}))
            .schema("public")
            .select("name,population")
            .filter("name", GatewayRpcFilterOperator::ILike, json!("%shire%"))
            .order_by("name", OrderDirection::Desc)
            .limit(20)
            .offset(5)
            .count_exact();

        let body = request.to_body_json();
        assert_eq!(body["function"], json!("echo_all_cities"));
        assert_eq!(body["schema"], json!("public"));
        assert_eq!(body["select"], json!("name,population"));
        assert_eq!(body["count"], json!("exact"));
        assert_eq!(body["limit"], json!(20));
        assert_eq!(body["offset"], json!(5));
        assert_eq!(body["filters"][0]["column"], json!("name"));
        assert_eq!(body["filters"][0]["operator"], json!("ilike"));
        assert_eq!(body["order"]["column"], json!("name"));
        assert_eq!(body["order"]["ascending"], json!(false));
    }

    #[test]
    fn typed_request_trait_resolves_static_paths() {
        assert_eq!(
            <request::Fetch as GatewayDriverRequest>::path(),
            Gateway::FETCH_PATH
        );
        assert_eq!(
            <request::Insert as GatewayDriverRequest>::path(),
            Gateway::INSERT_PATH
        );
        assert_eq!(
            <request::Update as GatewayDriverRequest>::path(),
            Gateway::UPDATE_PATH
        );
        assert_eq!(
            <request::Delete as GatewayDriverRequest>::path(),
            Gateway::DELETE_PATH
        );
        assert_eq!(
            <request::Sql as GatewayDriverRequest>::path(),
            Gateway::SQL_PATH
        );
        assert_eq!(
            <request::Rpc as GatewayDriverRequest>::path(),
            Gateway::RPC_PATH
        );
    }
}
