//! REST HTTP API handlers for PrismNET
//!
//! Implements REST endpoints for tenant-scoped network resources:
//! - GET/POST /api/v1/vpcs
//! - GET/DELETE /api/v1/vpcs/{id}
//! - GET/POST /api/v1/subnets
//! - DELETE /api/v1/subnets/{id}
//! - GET/POST /api/v1/routers
//! - GET/PATCH/DELETE /api/v1/routers/{id}
//! - GET/POST /api/v1/security-groups
//! - GET/DELETE /api/v1/security-groups/{id}
//! - POST /api/v1/security-groups/{id}/rules
//! - DELETE /api/v1/security-groups/{id}/rules/{rule_id}
//! - GET/POST /api/v1/ports
//! - GET/DELETE /api/v1/ports/{id}
//! - GET/POST /api/v1/service-ip-pools
//! - GET/PATCH/DELETE /api/v1/service-ip-pools/{id}
//! - GET /health

use axum::{
    extract::{Path, Query, State},
    http::HeaderMap,
    http::StatusCode,
    routing::{delete, get, post},
    Json, Router,
};
use iam_service_auth::{resolve_tenant_ids_from_context, AuthService, TenantContext};
use prismnet_api::{
    ipam_service_server::IpamService,
    port_service_server::PortService,
    router_service_server::RouterService,
    security_group_service_server::SecurityGroupService,
    subnet_service_server::SubnetService,
    vpc_service_server::VpcService,
    AddRuleRequest, CreatePortRequest, CreateSecurityGroupRequest, CreateServiceIpPoolRequest,
    CreateRouterRequest, CreateSubnetRequest, CreateVpcRequest, DeletePortRequest,
    DeleteRouterRequest, DeleteSecurityGroupRequest, DeleteServiceIpPoolRequest,
    DeleteSubnetRequest, DeleteVpcRequest, GetPortRequest, GetRouterRequest,
    GetSecurityGroupRequest, GetServiceIpPoolRequest, GetVpcRequest, IpProtocol,
    ListPortsRequest, ListRoutersRequest, ListSecurityGroupsRequest, ListServiceIpPoolsRequest,
    ListSubnetsRequest, ListVpcsRequest, Port as ProtoPort, RemoveRuleRequest,
    Router as ProtoRouter, RuleDirection,
    SecurityGroup as ProtoSecurityGroup, SecurityGroupRule as ProtoSecurityGroupRule,
    ServiceIpPool as ProtoServiceIPPool, ServiceIpPoolType as ProtoServiceIPPoolType,
    Subnet as ProtoSubnet, UpdatePortRequest, UpdateRouterRequest,
    UpdateServiceIpPoolRequest, Vpc as ProtoVpc,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tonic::Code;
use tonic::Request;

use crate::{
    IpamServiceImpl, PortServiceImpl, RouterServiceImpl, SecurityGroupServiceImpl,
    SubnetServiceImpl, VpcServiceImpl,
};

#[derive(Clone)]
pub struct RestApiState {
    pub vpc_service: Arc<VpcServiceImpl>,
    pub subnet_service: Arc<SubnetServiceImpl>,
    pub port_service: Arc<PortServiceImpl>,
    pub router_service: Arc<RouterServiceImpl>,
    pub security_group_service: Arc<SecurityGroupServiceImpl>,
    pub ipam_service: Arc<IpamServiceImpl>,
    pub auth_service: Arc<AuthService>,
}

#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: ErrorDetail,
    pub meta: ResponseMeta,
}

#[derive(Debug, Serialize)]
pub struct ErrorDetail {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,
}

#[derive(Debug, Serialize)]
pub struct ResponseMeta {
    pub request_id: String,
    pub timestamp: String,
}

impl ResponseMeta {
    fn new() -> Self {
        Self {
            request_id: uuid::Uuid::new_v4().to_string(),
            timestamp: chrono::Utc::now().to_rfc3339(),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct SuccessResponse<T> {
    pub data: T,
    pub meta: ResponseMeta,
}

impl<T> SuccessResponse<T> {
    fn new(data: T) -> Self {
        Self {
            data,
            meta: ResponseMeta::new(),
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct ListParams {
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub vpc_id: Option<String>,
    pub subnet_id: Option<String>,
    pub pool_type: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateVpcRequestRest {
    pub name: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub cidr_block: String,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateSubnetRequestRest {
    pub name: String,
    pub vpc_id: String,
    pub cidr_block: String,
    pub gateway_ip: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateRouterRequestRest {
    pub name: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub vpc_id: String,
    pub gateway_cidr: String,
    pub mac_address: String,
    pub external_ip: String,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateRouterRequestRest {
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub name: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreateSecurityGroupRequestRest {
    pub name: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct AddSecurityGroupRuleRequestRest {
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub direction: String,
    pub protocol: Option<String>,
    pub port_range_min: Option<u32>,
    pub port_range_max: Option<u32>,
    pub remote_cidr: Option<String>,
    pub remote_group_id: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct CreatePortRequestRest {
    pub name: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub subnet_id: String,
    pub description: Option<String>,
    pub ip_address: Option<String>,
    #[serde(default)]
    pub security_group_ids: Vec<String>,
    pub admin_state_up: Option<bool>,
}

#[derive(Debug, Deserialize)]
pub struct CreateServiceIpPoolRequestRest {
    pub name: String,
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub cidr_block: String,
    pub description: Option<String>,
    pub pool_type: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct UpdateServiceIpPoolRequestRest {
    pub org_id: Option<String>,
    pub project_id: Option<String>,
    pub name: Option<String>,
    pub description: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct VpcResponse {
    pub id: String,
    pub name: String,
    pub org_id: String,
    pub project_id: String,
    pub cidr_block: String,
    pub description: String,
    pub status: String,
}

impl From<ProtoVpc> for VpcResponse {
    fn from(vpc: ProtoVpc) -> Self {
        Self {
            id: vpc.id,
            name: vpc.name,
            org_id: vpc.org_id,
            project_id: vpc.project_id,
            cidr_block: vpc.cidr_block,
            description: vpc.description,
            status: vpc_status_to_string(vpc.status),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct SubnetResponse {
    pub id: String,
    pub name: String,
    pub vpc_id: String,
    pub cidr_block: String,
    pub gateway_ip: String,
    pub description: String,
    pub status: String,
}

impl From<ProtoSubnet> for SubnetResponse {
    fn from(subnet: ProtoSubnet) -> Self {
        Self {
            id: subnet.id,
            name: subnet.name,
            vpc_id: subnet.vpc_id,
            cidr_block: subnet.cidr_block,
            gateway_ip: subnet.gateway_ip,
            description: subnet.description,
            status: subnet_status_to_string(subnet.status),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct RouterResponse {
    pub id: String,
    pub org_id: String,
    pub project_id: String,
    pub vpc_id: String,
    pub name: String,
    pub description: String,
    pub gateway_cidr: String,
    pub mac_address: String,
    pub external_ip: String,
    pub status: String,
}

impl From<ProtoRouter> for RouterResponse {
    fn from(router: ProtoRouter) -> Self {
        Self {
            id: router.id,
            org_id: router.org_id,
            project_id: router.project_id,
            vpc_id: router.vpc_id,
            name: router.name,
            description: router.description,
            gateway_cidr: router.gateway_cidr,
            mac_address: router.mac_address,
            external_ip: router.external_ip,
            status: router_status_to_string(router.status),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct SecurityGroupRuleResponse {
    pub id: String,
    pub security_group_id: String,
    pub direction: String,
    pub protocol: String,
    pub port_range_min: Option<u32>,
    pub port_range_max: Option<u32>,
    pub remote_cidr: Option<String>,
    pub remote_group_id: Option<String>,
    pub description: String,
}

impl From<ProtoSecurityGroupRule> for SecurityGroupRuleResponse {
    fn from(rule: ProtoSecurityGroupRule) -> Self {
        Self {
            id: rule.id,
            security_group_id: rule.security_group_id,
            direction: rule_direction_to_string(rule.direction),
            protocol: ip_protocol_to_string(rule.protocol),
            port_range_min: (rule.port_range_min > 0).then_some(rule.port_range_min),
            port_range_max: (rule.port_range_max > 0).then_some(rule.port_range_max),
            remote_cidr: (!rule.remote_cidr.is_empty()).then_some(rule.remote_cidr),
            remote_group_id: (!rule.remote_group_id.is_empty()).then_some(rule.remote_group_id),
            description: rule.description,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct SecurityGroupResponse {
    pub id: String,
    pub project_id: String,
    pub name: String,
    pub description: String,
    pub rules: Vec<SecurityGroupRuleResponse>,
}

impl From<ProtoSecurityGroup> for SecurityGroupResponse {
    fn from(security_group: ProtoSecurityGroup) -> Self {
        Self {
            id: security_group.id,
            project_id: security_group.project_id,
            name: security_group.name,
            description: security_group.description,
            rules: security_group
                .rules
                .into_iter()
                .map(SecurityGroupRuleResponse::from)
                .collect(),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct PortResponse {
    pub id: String,
    pub subnet_id: String,
    pub name: String,
    pub description: String,
    pub mac_address: String,
    pub ip_address: String,
    pub device_id: String,
    pub device_type: String,
    pub security_group_ids: Vec<String>,
    pub admin_state_up: bool,
    pub status: String,
}

impl From<ProtoPort> for PortResponse {
    fn from(port: ProtoPort) -> Self {
        Self {
            id: port.id,
            subnet_id: port.subnet_id,
            name: port.name,
            description: port.description,
            mac_address: port.mac_address,
            ip_address: port.ip_address,
            device_id: port.device_id,
            device_type: device_type_to_string(port.device_type),
            security_group_ids: port.security_group_ids,
            admin_state_up: port.admin_state_up,
            status: port_status_to_string(port.status),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct VpcsResponse {
    pub vpcs: Vec<VpcResponse>,
}

#[derive(Debug, Serialize)]
pub struct SubnetsResponse {
    pub subnets: Vec<SubnetResponse>,
}

#[derive(Debug, Serialize)]
pub struct RoutersResponse {
    pub routers: Vec<RouterResponse>,
}

#[derive(Debug, Serialize)]
pub struct SecurityGroupsResponse {
    pub security_groups: Vec<SecurityGroupResponse>,
}

#[derive(Debug, Serialize)]
pub struct PortsResponse {
    pub ports: Vec<PortResponse>,
}

#[derive(Debug, Serialize)]
pub struct ServiceIpPoolResponse {
    pub id: String,
    pub org_id: String,
    pub project_id: String,
    pub name: String,
    pub description: String,
    pub cidr_block: String,
    pub pool_type: String,
    pub allocated_ips: Vec<String>,
    pub status: String,
}

impl From<ProtoServiceIPPool> for ServiceIpPoolResponse {
    fn from(pool: ProtoServiceIPPool) -> Self {
        Self {
            id: pool.id,
            org_id: pool.org_id,
            project_id: pool.project_id,
            name: pool.name,
            description: pool.description,
            cidr_block: pool.cidr_block,
            pool_type: service_ip_pool_type_to_string(pool.pool_type),
            allocated_ips: pool.allocated_ips,
            status: service_ip_pool_status_to_string(pool.status),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct ServiceIpPoolsResponse {
    pub pools: Vec<ServiceIpPoolResponse>,
}

pub fn build_router(state: RestApiState) -> Router {
    Router::new()
        .route("/api/v1/vpcs", get(list_vpcs).post(create_vpc))
        .route("/api/v1/vpcs/{id}", get(get_vpc).delete(delete_vpc))
        .route("/api/v1/subnets", get(list_subnets).post(create_subnet))
        .route("/api/v1/subnets/{id}", delete(delete_subnet))
        .route("/api/v1/routers", get(list_routers).post(create_router))
        .route(
            "/api/v1/routers/{id}",
            get(get_router).patch(update_router).delete(delete_router),
        )
        .route(
            "/api/v1/security-groups",
            get(list_security_groups).post(create_security_group),
        )
        .route(
            "/api/v1/security-groups/{id}",
            get(get_security_group).delete(delete_security_group),
        )
        .route(
            "/api/v1/security-groups/{id}/rules",
            post(add_security_group_rule),
        )
        .route(
            "/api/v1/security-groups/{id}/rules/{rule_id}",
            delete(remove_security_group_rule),
        )
        .route("/api/v1/ports", get(list_ports).post(create_port))
        .route("/api/v1/ports/{id}", get(get_port).delete(delete_port))
        .route(
            "/api/v1/service-ip-pools",
            get(list_service_ip_pools).post(create_service_ip_pool),
        )
        .route(
            "/api/v1/service-ip-pools/{id}",
            get(get_service_ip_pool)
                .patch(update_service_ip_pool)
                .delete(delete_service_ip_pool),
        )
        .route("/health", get(health_check))
        .with_state(state)
}

async fn health_check() -> (StatusCode, Json<SuccessResponse<serde_json::Value>>) {
    (
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "status": "healthy" }))),
    )
}

async fn list_vpcs(
    State(state): State<RestApiState>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<VpcsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(ListVpcsRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        page_size: 100,
        page_token: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .vpc_service
        .list_vpcs(req)
        .await
        .map_err(map_tonic_status)?;

    let vpcs = response
        .into_inner()
        .vpcs
        .into_iter()
        .map(VpcResponse::from)
        .collect();

    Ok(Json(SuccessResponse::new(VpcsResponse { vpcs })))
}

async fn create_vpc(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateVpcRequestRest>,
) -> Result<(StatusCode, Json<SuccessResponse<VpcResponse>>), (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(CreateVpcRequest {
        name: req.name,
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        cidr_block: req.cidr_block,
        description: req.description.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .vpc_service
        .create_vpc(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let vpc = response
        .into_inner()
        .vpc
        .ok_or_else(|| error_response(StatusCode::INTERNAL_SERVER_ERROR, "CREATE_FAILED", "No VPC returned"))?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(VpcResponse::from(vpc))),
    ))
}

async fn get_vpc(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<VpcResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(GetVpcRequest {
        id,
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .vpc_service
        .get_vpc(req)
        .await
        .map_err(map_not_found("VPC not found", "GET_FAILED"))?;

    let vpc = response
        .into_inner()
        .vpc
        .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "VPC not found"))?;

    Ok(Json(SuccessResponse::new(VpcResponse::from(vpc))))
}

async fn delete_vpc(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(DeleteVpcRequest {
        id: id.clone(),
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state
        .vpc_service
        .delete_vpc(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

async fn list_subnets(
    State(state): State<RestApiState>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<SubnetsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(ListSubnetsRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vpc_id: params.vpc_id.unwrap_or_default(),
        page_size: 100,
        page_token: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .subnet_service
        .list_subnets(req)
        .await
        .map_err(map_tonic_status)?;

    let subnets = response
        .into_inner()
        .subnets
        .into_iter()
        .map(SubnetResponse::from)
        .collect();

    Ok(Json(SuccessResponse::new(SubnetsResponse { subnets })))
}

async fn create_subnet(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateSubnetRequestRest>,
) -> Result<(StatusCode, Json<SuccessResponse<SubnetResponse>>), (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(&state, &headers, None, None).await?;
    let mut grpc_req = Request::new(CreateSubnetRequest {
        vpc_id: req.vpc_id,
        name: req.name,
        cidr_block: req.cidr_block,
        gateway_ip: req.gateway_ip.unwrap_or_default(),
        description: req.description.unwrap_or_default(),
        dhcp_enabled: true,
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .subnet_service
        .create_subnet(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let subnet = response
        .into_inner()
        .subnet
        .ok_or_else(|| error_response(StatusCode::INTERNAL_SERVER_ERROR, "CREATE_FAILED", "No subnet returned"))?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(SubnetResponse::from(subnet))),
    ))
}

async fn delete_subnet(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(DeleteSubnetRequest {
        id: id.clone(),
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vpc_id: params.vpc_id.unwrap_or_default(),
    });
    req.extensions_mut().insert(tenant);

    state
        .subnet_service
        .delete_subnet(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

async fn list_routers(
    State(state): State<RestApiState>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<RoutersResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(ListRoutersRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vpc_id: params.vpc_id.unwrap_or_default(),
        page_size: 100,
        page_token: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .router_service
        .list_routers(req)
        .await
        .map_err(map_tonic_status)?;

    let routers = response
        .into_inner()
        .routers
        .into_iter()
        .map(RouterResponse::from)
        .collect();

    Ok(Json(SuccessResponse::new(RoutersResponse { routers })))
}

async fn create_router(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateRouterRequestRest>,
) -> Result<(StatusCode, Json<SuccessResponse<RouterResponse>>), (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(CreateRouterRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        vpc_id: req.vpc_id,
        name: req.name,
        description: req.description.unwrap_or_default(),
        gateway_cidr: req.gateway_cidr,
        mac_address: req.mac_address,
        external_ip: req.external_ip,
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .router_service
        .create_router(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let router = response.into_inner().router.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "CREATE_FAILED",
            "No router returned",
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(RouterResponse::from(router))),
    ))
}

async fn get_router(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<RouterResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(GetRouterRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id,
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .router_service
        .get_router(req)
        .await
        .map_err(map_not_found("Router not found", "GET_FAILED"))?;

    let router = response
        .into_inner()
        .router
        .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Router not found"))?;

    Ok(Json(SuccessResponse::new(RouterResponse::from(router))))
}

async fn update_router(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
    Json(req): Json<UpdateRouterRequestRest>,
) -> Result<Json<SuccessResponse<RouterResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(UpdateRouterRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id,
        name: req.name.unwrap_or_default(),
        description: req.description.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .router_service
        .update_router(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let router = response.into_inner().router.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "UPDATE_FAILED",
            "No router returned",
        )
    })?;

    Ok(Json(SuccessResponse::new(RouterResponse::from(router))))
}

async fn delete_router(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(DeleteRouterRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id: id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state
        .router_service
        .delete_router(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

async fn list_security_groups(
    State(state): State<RestApiState>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<SecurityGroupsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(ListSecurityGroupsRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        page_size: 100,
        page_token: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .security_group_service
        .list_security_groups(req)
        .await
        .map_err(map_tonic_status)?;

    let security_groups = response
        .into_inner()
        .security_groups
        .into_iter()
        .map(SecurityGroupResponse::from)
        .collect();

    Ok(Json(SuccessResponse::new(SecurityGroupsResponse {
        security_groups,
    })))
}

async fn create_security_group(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateSecurityGroupRequestRest>,
) -> Result<
    (StatusCode, Json<SuccessResponse<SecurityGroupResponse>>),
    (StatusCode, Json<ErrorResponse>),
> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(CreateSecurityGroupRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        name: req.name,
        description: req.description.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .security_group_service
        .create_security_group(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let security_group = response.into_inner().security_group.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "CREATE_FAILED",
            "No security group returned",
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(SecurityGroupResponse::from(
            security_group,
        ))),
    ))
}

async fn get_security_group(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<SecurityGroupResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(GetSecurityGroupRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id,
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .security_group_service
        .get_security_group(req)
        .await
        .map_err(map_not_found("Security group not found", "GET_FAILED"))?;

    let security_group = response.into_inner().security_group.ok_or_else(|| {
        error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Security group not found")
    })?;

    Ok(Json(SuccessResponse::new(SecurityGroupResponse::from(
        security_group,
    ))))
}

async fn delete_security_group(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(DeleteSecurityGroupRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id: id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state
        .security_group_service
        .delete_security_group(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

async fn add_security_group_rule(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
    Json(req): Json<AddSecurityGroupRuleRequestRest>,
) -> Result<
    (StatusCode, Json<SuccessResponse<SecurityGroupRuleResponse>>),
    (StatusCode, Json<ErrorResponse>),
> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(AddRuleRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        security_group_id: id,
        direction: parse_rule_direction(&req.direction)?,
        protocol: parse_ip_protocol(req.protocol.as_deref())?,
        port_range_min: req.port_range_min.unwrap_or(0),
        port_range_max: req.port_range_max.unwrap_or(0),
        remote_cidr: req.remote_cidr.unwrap_or_default(),
        remote_group_id: req.remote_group_id.unwrap_or_default(),
        description: req.description.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .security_group_service
        .add_rule(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let rule = response.into_inner().rule.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "CREATE_FAILED",
            "No security group rule returned",
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(SecurityGroupRuleResponse::from(rule))),
    ))
}

async fn remove_security_group_rule(
    State(state): State<RestApiState>,
    Path((id, rule_id)): Path<(String, String)>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(RemoveRuleRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        security_group_id: id.clone(),
        rule_id: rule_id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state
        .security_group_service
        .remove_rule(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({
            "security_group_id": id,
            "rule_id": rule_id,
            "deleted": true
        }))),
    ))
}

async fn list_ports(
    State(state): State<RestApiState>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<PortsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let subnet_id = required_query_field(params.subnet_id.clone(), "subnet_id")?;
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(ListPortsRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        subnet_id,
        device_id: String::new(),
        page_size: 100,
        page_token: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .port_service
        .list_ports(req)
        .await
        .map_err(map_tonic_status)?;

    let ports = response
        .into_inner()
        .ports
        .into_iter()
        .map(PortResponse::from)
        .collect();

    Ok(Json(SuccessResponse::new(PortsResponse { ports })))
}

async fn create_port(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreatePortRequestRest>,
) -> Result<(StatusCode, Json<SuccessResponse<PortResponse>>), (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let admin_state_up = req.admin_state_up.unwrap_or(true);

    let mut grpc_req = Request::new(CreatePortRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        subnet_id: req.subnet_id.clone(),
        name: req.name,
        description: req.description.unwrap_or_default(),
        ip_address: req.ip_address.unwrap_or_default(),
        security_group_ids: req.security_group_ids,
    });
    grpc_req.extensions_mut().insert(tenant.clone());

    let mut port = state
        .port_service
        .create_port(grpc_req)
        .await
        .map_err(map_tonic_status)?
        .into_inner()
        .port
        .ok_or_else(|| error_response(StatusCode::INTERNAL_SERVER_ERROR, "CREATE_FAILED", "No port returned"))?;

    if !admin_state_up {
        let mut update_req = Request::new(UpdatePortRequest {
            org_id: tenant.org_id.clone(),
            project_id: tenant.project_id.clone(),
            subnet_id: req.subnet_id,
            id: port.id.clone(),
            name: String::new(),
            description: String::new(),
            security_group_ids: Vec::new(),
            admin_state_up: false,
        });
        update_req.extensions_mut().insert(tenant);
        port = state
            .port_service
            .update_port(update_req)
            .await
            .map_err(map_tonic_status)?
            .into_inner()
            .port
            .ok_or_else(|| error_response(StatusCode::INTERNAL_SERVER_ERROR, "UPDATE_FAILED", "No port returned"))?;
    }

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(PortResponse::from(port))),
    ))
}

async fn get_port(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<PortResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let subnet_id = required_query_field(params.subnet_id.clone(), "subnet_id")?;
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(GetPortRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        subnet_id,
        id,
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .port_service
        .get_port(req)
        .await
        .map_err(map_not_found("Port not found", "GET_FAILED"))?;

    let port = response
        .into_inner()
        .port
        .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Port not found"))?;

    Ok(Json(SuccessResponse::new(PortResponse::from(port))))
}

async fn delete_port(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let subnet_id = required_query_field(params.subnet_id.clone(), "subnet_id")?;
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(DeletePortRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        subnet_id,
        id: id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state
        .port_service
        .delete_port(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

async fn list_service_ip_pools(
    State(state): State<RestApiState>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<ServiceIpPoolsResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(ListServiceIpPoolsRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        pool_type: parse_optional_service_ip_pool_type(params.pool_type.as_deref())?,
        page_size: 100,
        page_token: String::new(),
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .ipam_service
        .list_service_ip_pools(req)
        .await
        .map_err(map_tonic_status)?;

    let pools = response
        .into_inner()
        .pools
        .into_iter()
        .map(ServiceIpPoolResponse::from)
        .collect();

    Ok(Json(SuccessResponse::new(ServiceIpPoolsResponse { pools })))
}

async fn create_service_ip_pool(
    State(state): State<RestApiState>,
    headers: HeaderMap,
    Json(req): Json<CreateServiceIpPoolRequestRest>,
) -> Result<
    (StatusCode, Json<SuccessResponse<ServiceIpPoolResponse>>),
    (StatusCode, Json<ErrorResponse>),
> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(CreateServiceIpPoolRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        name: req.name,
        description: req.description.unwrap_or_default(),
        cidr_block: req.cidr_block,
        pool_type: parse_service_ip_pool_type(req.pool_type.as_deref())?,
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .ipam_service
        .create_service_ip_pool(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let pool = response.into_inner().pool.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "CREATE_FAILED",
            "No Service IP Pool returned",
        )
    })?;

    Ok((
        StatusCode::CREATED,
        Json(SuccessResponse::new(ServiceIpPoolResponse::from(pool))),
    ))
}

async fn get_service_ip_pool(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<Json<SuccessResponse<ServiceIpPoolResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(GetServiceIpPoolRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id,
    });
    req.extensions_mut().insert(tenant);

    let response = state
        .ipam_service
        .get_service_ip_pool(req)
        .await
        .map_err(map_not_found("Service IP Pool not found", "GET_FAILED"))?;

    let pool = response.into_inner().pool.ok_or_else(|| {
        error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Service IP Pool not found")
    })?;

    Ok(Json(SuccessResponse::new(ServiceIpPoolResponse::from(pool))))
}

async fn update_service_ip_pool(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    headers: HeaderMap,
    Json(req): Json<UpdateServiceIpPoolRequestRest>,
) -> Result<Json<SuccessResponse<ServiceIpPoolResponse>>, (StatusCode, Json<ErrorResponse>)> {
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        req.org_id.as_deref(),
        req.project_id.as_deref(),
    )
    .await?;
    let mut grpc_req = Request::new(UpdateServiceIpPoolRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id,
        name: req.name.unwrap_or_default(),
        description: req.description.unwrap_or_default(),
    });
    grpc_req.extensions_mut().insert(tenant);

    let response = state
        .ipam_service
        .update_service_ip_pool(grpc_req)
        .await
        .map_err(map_tonic_status)?;

    let pool = response.into_inner().pool.ok_or_else(|| {
        error_response(
            StatusCode::INTERNAL_SERVER_ERROR,
            "UPDATE_FAILED",
            "No Service IP Pool returned",
        )
    })?;

    Ok(Json(SuccessResponse::new(ServiceIpPoolResponse::from(pool))))
}

async fn delete_service_ip_pool(
    State(state): State<RestApiState>,
    Path(id): Path<String>,
    Query(params): Query<ListParams>,
    headers: HeaderMap,
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)>
{
    let tenant = resolve_rest_tenant(
        &state,
        &headers,
        params.org_id.as_deref(),
        params.project_id.as_deref(),
    )
    .await?;
    let mut req = Request::new(DeleteServiceIpPoolRequest {
        org_id: tenant.org_id.clone(),
        project_id: tenant.project_id.clone(),
        id: id.clone(),
    });
    req.extensions_mut().insert(tenant);

    state
        .ipam_service
        .delete_service_ip_pool(req)
        .await
        .map_err(map_tonic_status)?;

    Ok((
        StatusCode::OK,
        Json(SuccessResponse::new(serde_json::json!({ "id": id, "deleted": true }))),
    ))
}

fn error_response(
    status: StatusCode,
    code: &str,
    message: &str,
) -> (StatusCode, Json<ErrorResponse>) {
    (
        status,
        Json(ErrorResponse {
            error: ErrorDetail {
                code: code.to_string(),
                message: message.to_string(),
                details: None,
            },
            meta: ResponseMeta::new(),
        }),
    )
}

async fn resolve_rest_tenant(
    state: &RestApiState,
    headers: &HeaderMap,
    req_org_id: Option<&str>,
    req_project_id: Option<&str>,
) -> Result<TenantContext, (StatusCode, Json<ErrorResponse>)> {
    let tenant = state
        .auth_service
        .authenticate_headers(headers)
        .await
        .map_err(map_auth_status)?;
    resolve_tenant_ids_from_context(
        &tenant,
        req_org_id.unwrap_or(""),
        req_project_id.unwrap_or(""),
    )
    .map_err(map_auth_status)?;

    Ok(tenant)
}

fn map_auth_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
    map_tonic_status(status)
}

fn map_tonic_status(status: tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
    let status_code = match status.code() {
        Code::Unauthenticated => StatusCode::UNAUTHORIZED,
        Code::PermissionDenied => StatusCode::FORBIDDEN,
        Code::InvalidArgument => StatusCode::BAD_REQUEST,
        Code::NotFound => StatusCode::NOT_FOUND,
        Code::AlreadyExists => StatusCode::CONFLICT,
        Code::ResourceExhausted => StatusCode::TOO_MANY_REQUESTS,
        Code::FailedPrecondition => StatusCode::UNPROCESSABLE_ENTITY,
        _ => StatusCode::INTERNAL_SERVER_ERROR,
    };
    let code = match status.code() {
        Code::Unauthenticated => "UNAUTHENTICATED",
        Code::PermissionDenied => "FORBIDDEN",
        Code::InvalidArgument => "INVALID_ARGUMENT",
        Code::NotFound => "NOT_FOUND",
        Code::AlreadyExists => "ALREADY_EXISTS",
        Code::ResourceExhausted => "RESOURCE_EXHAUSTED",
        Code::FailedPrecondition => "FAILED_PRECONDITION",
        _ => "INTERNAL",
    };

    error_response(status_code, code, status.message())
}

fn map_not_found(
    not_found_message: &'static str,
    default_code: &'static str,
) -> impl Fn(tonic::Status) -> (StatusCode, Json<ErrorResponse>) {
    move |status| {
        if status.code() == Code::NotFound {
            error_response(StatusCode::NOT_FOUND, "NOT_FOUND", not_found_message)
        } else {
            error_response(
                StatusCode::INTERNAL_SERVER_ERROR,
                default_code,
                status.message(),
            )
        }
    }
}

fn required_query_field(
    value: Option<String>,
    field_name: &str,
) -> Result<String, (StatusCode, Json<ErrorResponse>)> {
    value.ok_or_else(|| {
        error_response(
            StatusCode::BAD_REQUEST,
            "INVALID_ARGUMENT",
            &format!("{} is required", field_name),
        )
    })
}

fn parse_rule_direction(direction: &str) -> Result<i32, (StatusCode, Json<ErrorResponse>)> {
    match direction.to_ascii_lowercase().as_str() {
        "ingress" => Ok(RuleDirection::Ingress as i32),
        "egress" => Ok(RuleDirection::Egress as i32),
        _ => Err(error_response(
            StatusCode::BAD_REQUEST,
            "INVALID_ARGUMENT",
            "direction must be ingress or egress",
        )),
    }
}

fn parse_ip_protocol(protocol: Option<&str>) -> Result<i32, (StatusCode, Json<ErrorResponse>)> {
    match protocol.unwrap_or("any").to_ascii_lowercase().as_str() {
        "any" => Ok(IpProtocol::Any as i32),
        "tcp" => Ok(IpProtocol::Tcp as i32),
        "udp" => Ok(IpProtocol::Udp as i32),
        "icmp" => Ok(IpProtocol::Icmp as i32),
        "icmpv6" => Ok(IpProtocol::Icmpv6 as i32),
        _ => Err(error_response(
            StatusCode::BAD_REQUEST,
            "INVALID_ARGUMENT",
            "protocol must be any, tcp, udp, icmp, or icmpv6",
        )),
    }
}

fn parse_service_ip_pool_type(
    pool_type: Option<&str>,
) -> Result<i32, (StatusCode, Json<ErrorResponse>)> {
    match pool_type.unwrap_or("cluster_ip").to_ascii_lowercase().as_str() {
        "cluster_ip" => Ok(ProtoServiceIPPoolType::ClusterIp as i32),
        "load_balancer" => Ok(ProtoServiceIPPoolType::LoadBalancer as i32),
        "node_port" => Ok(ProtoServiceIPPoolType::NodePort as i32),
        _ => Err(error_response(
            StatusCode::BAD_REQUEST,
            "INVALID_ARGUMENT",
            "pool_type must be cluster_ip, load_balancer, or node_port",
        )),
    }
}

fn parse_optional_service_ip_pool_type(
    pool_type: Option<&str>,
) -> Result<i32, (StatusCode, Json<ErrorResponse>)> {
    match pool_type {
        None => Ok(0),
        Some(value) => parse_service_ip_pool_type(Some(value)),
    }
}

fn vpc_status_to_string(status: i32) -> String {
    match status {
        1 => "provisioning",
        2 => "active",
        3 => "updating",
        4 => "deleting",
        5 => "error",
        _ => "unknown",
    }
    .to_string()
}

fn subnet_status_to_string(status: i32) -> String {
    match status {
        1 => "provisioning",
        2 => "active",
        3 => "updating",
        4 => "deleting",
        5 => "error",
        _ => "unknown",
    }
    .to_string()
}

fn router_status_to_string(status: i32) -> String {
    match status {
        1 => "provisioning",
        2 => "active",
        3 => "updating",
        4 => "deleting",
        5 => "error",
        _ => "unknown",
    }
    .to_string()
}

fn port_status_to_string(status: i32) -> String {
    match status {
        1 => "build",
        2 => "active",
        3 => "down",
        4 => "error",
        _ => "unknown",
    }
    .to_string()
}

fn device_type_to_string(device_type: i32) -> String {
    match device_type {
        1 => "none",
        2 => "vm",
        3 => "router",
        4 => "load_balancer",
        5 => "dhcp_server",
        6 => "other",
        _ => "unspecified",
    }
    .to_string()
}

fn rule_direction_to_string(direction: i32) -> String {
    match direction {
        1 => "ingress",
        2 => "egress",
        _ => "unspecified",
    }
    .to_string()
}

fn ip_protocol_to_string(protocol: i32) -> String {
    match protocol {
        1 => "any",
        2 => "tcp",
        3 => "udp",
        4 => "icmp",
        5 => "icmpv6",
        _ => "unspecified",
    }
    .to_string()
}

fn service_ip_pool_type_to_string(pool_type: i32) -> String {
    match pool_type {
        1 => "cluster_ip",
        2 => "load_balancer",
        3 => "node_port",
        _ => "unspecified",
    }
    .to_string()
}

fn service_ip_pool_status_to_string(status: i32) -> String {
    match status {
        1 => "provisioning",
        2 => "active",
        3 => "updating",
        4 => "deleting",
        5 => "error",
        _ => "unknown",
    }
    .to_string()
}

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

    #[test]
    fn parse_optional_service_ip_pool_type_defaults_to_unspecified() {
        assert_eq!(parse_optional_service_ip_pool_type(None).unwrap(), 0);
        assert_eq!(
            parse_optional_service_ip_pool_type(Some("load_balancer")).unwrap(),
            ProtoServiceIPPoolType::LoadBalancer as i32
        );
    }

    #[test]
    fn map_tonic_status_preserves_failed_precondition() {
        let (status, Json(body)) =
            map_tonic_status(tonic::Status::failed_precondition("pool still allocated"));
        assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
        assert_eq!(body.error.code, "FAILED_PRECONDITION");
    }
}
