use std::collections::HashMap;

use crate::error::RpcStatusCode;

/// Metadata attached to RPC requests and responses (maps to HTTP headers).
pub type Metadata = HashMap<String, String>;

/// An RPC request from client to server.
#[derive(Debug, Clone)]
pub struct RpcRequest {
    /// Fully qualified method path: `"service_name/method_name"`.
    pub method: String,
    /// Key-value metadata (transmitted as HTTP headers with `x-rpc-` prefix).
    pub metadata: Metadata,
    /// Serialized request payload.
    pub body: Vec<u8>,
}

/// An RPC response from server to client.
#[derive(Debug, Clone)]
pub struct RpcResponse {
    /// Status of the RPC call.
    pub status: RpcStatus,
    /// Key-value metadata (transmitted as HTTP headers).
    pub metadata: Metadata,
    /// Serialized response payload.
    pub body: Vec<u8>,
}

/// The status of a completed RPC call.
#[derive(Debug, Clone)]
pub struct RpcStatus {
    pub code: RpcStatusCode,
    pub message: String,
}

impl RpcRequest {
    pub fn new(method: impl Into<String>, body: Vec<u8>) -> Self {
        Self {
            method: method.into(),
            metadata: Metadata::new(),
            body,
        }
    }

    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }

    /// Extract the service name from the method path.
    pub fn service_name(&self) -> Option<&str> {
        self.method.split('/').next()
    }

    /// Extract the method name from the method path.
    pub fn method_name(&self) -> Option<&str> {
        self.method.split('/').nth(1)
    }
}

impl RpcResponse {
    pub fn ok(body: Vec<u8>) -> Self {
        Self {
            status: RpcStatus {
                code: RpcStatusCode::Ok,
                message: String::new(),
            },
            metadata: Metadata::new(),
            body,
        }
    }

    pub fn error(code: RpcStatusCode, message: impl Into<String>) -> Self {
        Self {
            status: RpcStatus {
                code,
                message: message.into(),
            },
            metadata: Metadata::new(),
            body: Vec::new(),
        }
    }

    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.metadata.insert(key.into(), value.into());
        self
    }
}

impl RpcStatus {
    pub fn ok() -> Self {
        Self {
            code: RpcStatusCode::Ok,
            message: String::new(),
        }
    }

    pub fn is_ok(&self) -> bool {
        self.code == RpcStatusCode::Ok
    }
}
