use serde::{Deserialize, Serialize};

use crate::streaming::{ExecutionMode, TransportBehavior};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Protocol {
    Rest,
    Graphql,
    Grpc,
    Websocket,
    Soap,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Patch,
    Delete,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GraphqlOperationType {
    Query,
    Mutation,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthKind {
    Bearer,
    Basic,
    ApiKeyHeader,
    ApiKeyQuery,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExportMode {
    Portable,
    Bundle,
}

impl Protocol {
    pub fn supports_execution_mode(self, mode: ExecutionMode) -> bool {
        match self {
            Self::Rest => true,
            Self::Graphql => matches!(mode, ExecutionMode::Unary),
            Self::Grpc => true,
            Self::Websocket => !matches!(mode, ExecutionMode::Unary),
            Self::Soap => matches!(mode, ExecutionMode::Unary | ExecutionMode::AsyncJob),
        }
    }

    pub fn supports_transport_behavior(self, behavior: TransportBehavior) -> bool {
        match self {
            Self::Rest => true,
            Self::Graphql => matches!(behavior, TransportBehavior::RequestResponse),
            Self::Grpc => !matches!(behavior, TransportBehavior::DeferredResult),
            Self::Websocket => !matches!(behavior, TransportBehavior::RequestResponse),
            Self::Soap => !matches!(behavior, TransportBehavior::StatefulSession),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Protocol;
    use crate::streaming::{ExecutionMode, TransportBehavior};

    #[test]
    fn graphql_support_matrix_is_restricted() {
        assert!(Protocol::Graphql.supports_execution_mode(ExecutionMode::Unary));
        assert!(!Protocol::Graphql.supports_execution_mode(ExecutionMode::Window));
        assert!(!Protocol::Graphql.supports_execution_mode(ExecutionMode::Session));
        assert!(!Protocol::Graphql.supports_transport_behavior(TransportBehavior::ServerStream));
    }

    #[test]
    fn grpc_supports_session_but_not_deferred_result() {
        assert!(Protocol::Grpc.supports_execution_mode(ExecutionMode::Session));
        assert!(!Protocol::Grpc.supports_transport_behavior(TransportBehavior::DeferredResult));
    }

    #[test]
    fn websocket_requires_streaming_modes() {
        assert!(!Protocol::Websocket.supports_execution_mode(ExecutionMode::Unary));
        assert!(Protocol::Websocket.supports_execution_mode(ExecutionMode::Window));
        assert!(Protocol::Websocket.supports_execution_mode(ExecutionMode::Session));
        assert!(Protocol::Websocket.supports_execution_mode(ExecutionMode::AsyncJob));
        assert!(
            !Protocol::Websocket.supports_transport_behavior(TransportBehavior::RequestResponse)
        );
        assert!(Protocol::Websocket.supports_transport_behavior(TransportBehavior::ServerStream));
        assert!(
            Protocol::Websocket.supports_transport_behavior(TransportBehavior::StatefulSession)
        );
        assert!(Protocol::Websocket.supports_transport_behavior(TransportBehavior::DeferredResult));
    }

    #[test]
    fn soap_is_request_response_first_with_async_job_support() {
        assert!(Protocol::Soap.supports_execution_mode(ExecutionMode::Unary));
        assert!(!Protocol::Soap.supports_execution_mode(ExecutionMode::Window));
        assert!(!Protocol::Soap.supports_execution_mode(ExecutionMode::Session));
        assert!(Protocol::Soap.supports_execution_mode(ExecutionMode::AsyncJob));
        assert!(Protocol::Soap.supports_transport_behavior(TransportBehavior::RequestResponse));
        assert!(Protocol::Soap.supports_transport_behavior(TransportBehavior::ServerStream));
        assert!(!Protocol::Soap.supports_transport_behavior(TransportBehavior::StatefulSession));
        assert!(Protocol::Soap.supports_transport_behavior(TransportBehavior::DeferredResult));
    }
}
