#[cfg(debug_assertions)]
use opentelemetry::KeyValue;

pub mod values {
    pub const UNKNOWN: &str = "UNKNOWN";

    #[derive(Clone, Copy, Debug, strum::IntoStaticStr)]
    pub enum SupergraphPollResult {
        #[strum(serialize = "updated")]
        Updated,
        #[strum(serialize = "not_modified")]
        NotModified,
        #[strum(serialize = "error")]
        Error,
    }

    impl SupergraphPollResult {
        pub fn as_str(self) -> &'static str {
            self.into()
        }
    }

    #[derive(Clone, Copy, Debug, strum::IntoStaticStr)]
    pub enum SupergraphProcessStatus {
        #[strum(serialize = "ok")]
        Ok,
        #[strum(serialize = "error")]
        Error,
    }

    impl SupergraphProcessStatus {
        pub fn as_str(self) -> &'static str {
            self.into()
        }
    }

    #[derive(Clone, Copy, Debug, strum::IntoStaticStr)]
    pub enum GraphQLResponseStatus {
        #[strum(serialize = "ok")]
        Ok,
        #[strum(serialize = "error")]
        Error,
    }

    impl GraphQLResponseStatus {
        pub fn as_str(self) -> &'static str {
            self.into()
        }
    }

    #[derive(Clone, Copy, Debug, strum::IntoStaticStr)]
    pub enum CacheResult {
        #[strum(serialize = "hit")]
        Hit,
        #[strum(serialize = "miss")]
        Miss,
    }

    impl CacheResult {
        pub fn as_str(self) -> &'static str {
            self.into()
        }
    }

    /// Why a client subscription ended, recorded on the `ended_total` counter.
    ///
    /// Defaults to `ClientDisconnected` when a guard drops without an explicit
    /// reason being set (e.g. the client closed the connection or the stream
    /// was dropped), so every guard drop is still attributed to a reason.
    #[derive(Clone, Copy, Debug, strum::IntoStaticStr)]
    pub enum SubscriptionEndReason {
        #[strum(serialize = "completed")]
        Completed,
        #[strum(serialize = "error")]
        Error,
        #[strum(serialize = "client_disconnected")]
        ClientDisconnected,
    }

    impl SubscriptionEndReason {
        pub fn as_str(self) -> &'static str {
            self.into()
        }
    }

    /// Circuit breaker state exposed via metrics.
    ///
    /// The internal recloser state has three values (Closed, HalfOpen, Open),
    /// but for the externally-visible metric we collapse `Closed` and
    /// `HalfOpen` into the same logical "closed" bucket
    /// both mean "requests are being attempted".
    #[derive(Clone, Copy, Debug, strum::IntoStaticStr)]
    pub enum CircuitBreakerState {
        #[strum(serialize = "closed")]
        Closed,
        #[strum(serialize = "open")]
        Open,
    }

    impl CircuitBreakerState {
        pub fn as_str(self) -> &'static str {
            self.into()
        }

        pub fn as_u8(self) -> u8 {
            match self {
                CircuitBreakerState::Closed => 0,
                CircuitBreakerState::Open => 1,
            }
        }

        pub fn from_u8(value: u8) -> Self {
            match value {
                0 => CircuitBreakerState::Closed,
                _ => CircuitBreakerState::Open,
            }
        }
    }
}

pub mod labels {
    pub const CODE: &str = "code";
    pub const RESULT: &str = "result";
    pub const STATUS: &str = "status";
    pub const ERROR_TYPE: &str = "error.type";
    pub const SUBGRAPH_NAME: &str = "subgraph.name";
    pub const SUBSCRIPTION_TRANSPORT: &str = "subscription.transport";
    pub const SUBSCRIPTION_END_REASON: &str = "subscription.end_reason";
    pub const HTTP_REQUEST_METHOD: &str = "http.request.method";
    pub const HTTP_RESPONSE_STATUS_CODE: &str = "http.response.status_code";
    pub const HTTP_ROUTE: &str = "http.route";
    pub const URL_SCHEME: &str = "url.scheme";
    pub const NETWORK_PROTOCOL_NAME: &str = "network.protocol.name";
    pub const NETWORK_PROTOCOL_VERSION: &str = "network.protocol.version";
    pub const SERVER_ADDRESS: &str = "server.address";
    pub const SERVER_PORT: &str = "server.port";
    pub const GRAPHQL_OPERATION_TYPE: &str = "graphql.operation.type";
    pub const GRAPHQL_OPERATION_NAME: &str = "graphql.operation.name";
    pub const GRAPHQL_RESPONSE_STATUS: &str = "graphql.response.status";
    pub const COST_RESULT: &str = "cost.result";
    pub const COPROCESSOR_STAGE: &str = "coprocessor.stage";
    pub const CIRCUIT_BREAKER_FROM_STATE: &str = "circuit_breaker.from_state";
    pub const CIRCUIT_BREAKER_TO_STATE: &str = "circuit_breaker.to_state";
}

pub mod units {
    pub const DEMAND_CONTROL_COST_UNIT: &str = "{cost}";
    pub const BYTES: &str = "By";
    pub const SECONDS: &str = "s";
    pub const SUBSCRIPTIONS: &str = "{subscription}";
    pub const CONNECTIONS: &str = "{connection}";
    pub const MESSAGES: &str = "{message}";
}

pub mod names {
    pub const GRAPHQL_ERRORS_TOTAL: &str = "hive.router.graphql.errors_total";
    pub const COST_ESTIMATED: &str = "cost.estimated";
    pub const COST_ACTUAL: &str = "cost.actual";
    pub const COST_DELTA: &str = "cost.delta";
    pub const SUPERGRAPH_POLL_TOTAL: &str = "hive.router.supergraph.poll.total";
    pub const SUPERGRAPH_POLL_DURATION: &str = "hive.router.supergraph.poll.duration";
    pub const SUPERGRAPH_PROCESS_DURATION: &str = "hive.router.supergraph.process.duration";
    pub const HTTP_SERVER_REQUEST_DURATION: &str = "http.server.request.duration";
    pub const HTTP_SERVER_ACTIVE_REQUESTS: &str = "http.server.active_requests";
    pub const HTTP_SERVER_REQUEST_BODY_SIZE: &str = "http.server.request.body.size";
    pub const HTTP_SERVER_RESPONSE_BODY_SIZE: &str = "http.server.response.body.size";
    pub const HTTP_CLIENT_REQUEST_DURATION: &str = "http.client.request.duration";
    pub const HTTP_CLIENT_ACTIVE_REQUESTS: &str = "http.client.active_requests";
    pub const HTTP_CLIENT_REQUEST_BODY_SIZE: &str = "http.client.request.body.size";
    pub const HTTP_CLIENT_RESPONSE_BODY_SIZE: &str = "http.client.response.body.size";
    pub const PARSE_CACHE_REQUESTS_TOTAL: &str = "hive.router.parse_cache.requests_total";
    pub const PARSE_CACHE_DURATION: &str = "hive.router.parse_cache.duration";
    pub const PARSE_CACHE_SIZE: &str = "hive.router.parse_cache.size";
    pub const VALIDATE_CACHE_REQUESTS_TOTAL: &str = "hive.router.validate_cache.requests_total";
    pub const VALIDATE_CACHE_DURATION: &str = "hive.router.validate_cache.duration";
    pub const VALIDATE_CACHE_SIZE: &str = "hive.router.validate_cache.size";
    pub const NORMALIZE_CACHE_REQUESTS_TOTAL: &str = "hive.router.normalize_cache.requests_total";
    pub const NORMALIZE_CACHE_DURATION: &str = "hive.router.normalize_cache.duration";
    pub const NORMALIZE_CACHE_SIZE: &str = "hive.router.normalize_cache.size";
    pub const PLAN_CACHE_REQUESTS_TOTAL: &str = "hive.router.plan_cache.requests_total";
    pub const PLAN_CACHE_DURATION: &str = "hive.router.plan_cache.duration";
    pub const PLAN_CACHE_SIZE: &str = "hive.router.plan_cache.size";
    pub const CIRCUIT_BREAKER_SHORT_CIRCUITS_TOTAL: &str =
        "hive.router.circuit_breaker.short_circuits_total";
    pub const CIRCUIT_BREAKER_STATE: &str = "hive.router.circuit_breaker.state";
    pub const CIRCUIT_BREAKER_STATE_TRANSITIONS_TOTAL: &str =
        "hive.router.circuit_breaker.state_transitions_total";
    pub const CIRCUIT_BREAKER_FAILURES_TOTAL: &str = "hive.router.circuit_breaker.failures_total";
    pub const PERSISTED_DOCUMENTS_STORAGE_FAILURES_TOTAL: &str =
        "hive.router.persisted_documents.storage.failures_total";
    pub const PERSISTED_DOCUMENTS_EXTRACT_MISSING_ID_TOTAL: &str =
        "hive.router.persisted_documents.extract.missing_id_total";
    pub const COPROCESSOR_REQUESTS_TOTAL: &str = "hive.router.coprocessor.requests_total";
    pub const COPROCESSOR_DURATION: &str = "hive.router.coprocessor.duration";
    pub const COPROCESSOR_ERRORS_TOTAL: &str = "hive.router.coprocessor.errors_total";
    pub const SUBSCRIPTIONS_SUBGRAPHS_ACTIVE: &str = "hive.router.subscriptions.subgraphs.active";
    pub const SUBSCRIPTIONS_SUBGRAPHS_CONNECTIONS: &str =
        "hive.router.subscriptions.subgraphs.connections";
    pub const SUBSCRIPTIONS_CLIENTS_ACTIVE: &str = "hive.router.subscriptions.clients.active";
    pub const SUBSCRIPTIONS_CLIENTS_CONNECTIONS: &str =
        "hive.router.subscriptions.clients.connections";
    pub const SUBSCRIPTIONS_CLIENTS_STARTED_TOTAL: &str =
        "hive.router.subscriptions.clients.started_total";
    pub const SUBSCRIPTIONS_CLIENTS_ENDED_TOTAL: &str =
        "hive.router.subscriptions.clients.ended_total";
    pub const SUBSCRIPTIONS_SUBGRAPHS_STARTED_TOTAL: &str =
        "hive.router.subscriptions.subgraphs.started_total";
    pub const SUBSCRIPTIONS_SUBGRAPHS_ENDED_TOTAL: &str =
        "hive.router.subscriptions.subgraphs.ended_total";
    pub const SUBSCRIPTIONS_SUBGRAPHS_DROPPED_MESSAGES_TOTAL: &str =
        "hive.router.subscriptions.subgraphs.dropped_messages_total";
    pub const SUBSCRIPTIONS_CLIENTS_LAGGED_MESSAGES_TOTAL: &str =
        "hive.router.subscriptions.clients.lagged_messages_total";
    pub const SUBSCRIPTIONS_CLIENTS_SENT_MESSAGES_TOTAL: &str =
        "hive.router.subscriptions.clients.sent_messages_total";
}

pub(crate) const METRIC_SPECS: &[(&str, &[&str])] = &[
    (
        names::SUBSCRIPTIONS_SUBGRAPHS_ACTIVE,
        &[labels::SUBGRAPH_NAME],
    ),
    (
        names::SUBSCRIPTIONS_SUBGRAPHS_CONNECTIONS,
        &[labels::SUBGRAPH_NAME, labels::SUBSCRIPTION_TRANSPORT],
    ),
    (
        names::SUBSCRIPTIONS_CLIENTS_ACTIVE,
        &[labels::SUBSCRIPTION_TRANSPORT],
    ),
    (
        names::SUBSCRIPTIONS_CLIENTS_CONNECTIONS,
        &[labels::SUBSCRIPTION_TRANSPORT],
    ),
    (
        names::SUBSCRIPTIONS_CLIENTS_STARTED_TOTAL,
        &[labels::SUBSCRIPTION_TRANSPORT],
    ),
    (
        names::SUBSCRIPTIONS_CLIENTS_ENDED_TOTAL,
        &[
            labels::SUBSCRIPTION_TRANSPORT,
            labels::SUBSCRIPTION_END_REASON,
        ],
    ),
    (
        names::SUBSCRIPTIONS_SUBGRAPHS_STARTED_TOTAL,
        &[labels::SUBGRAPH_NAME],
    ),
    (
        names::SUBSCRIPTIONS_SUBGRAPHS_ENDED_TOTAL,
        &[labels::SUBGRAPH_NAME],
    ),
    (
        names::SUBSCRIPTIONS_SUBGRAPHS_DROPPED_MESSAGES_TOTAL,
        &[labels::SUBSCRIPTION_TRANSPORT],
    ),
    (
        names::SUBSCRIPTIONS_CLIENTS_LAGGED_MESSAGES_TOTAL,
        &[labels::SUBSCRIPTION_TRANSPORT],
    ),
    (
        names::SUBSCRIPTIONS_CLIENTS_SENT_MESSAGES_TOTAL,
        &[labels::SUBSCRIPTION_TRANSPORT],
    ),
    (names::GRAPHQL_ERRORS_TOTAL, &[labels::CODE]),
    (
        names::COST_ESTIMATED,
        &[labels::COST_RESULT, labels::GRAPHQL_OPERATION_NAME],
    ),
    (
        names::COST_ACTUAL,
        &[labels::COST_RESULT, labels::GRAPHQL_OPERATION_NAME],
    ),
    (
        names::COST_DELTA,
        &[labels::COST_RESULT, labels::GRAPHQL_OPERATION_NAME],
    ),
    (
        names::HTTP_SERVER_REQUEST_DURATION,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::HTTP_RESPONSE_STATUS_CODE,
            labels::HTTP_ROUTE,
            labels::NETWORK_PROTOCOL_NAME,
            labels::NETWORK_PROTOCOL_VERSION,
            labels::URL_SCHEME,
            labels::ERROR_TYPE,
            labels::GRAPHQL_OPERATION_NAME,
            labels::GRAPHQL_OPERATION_TYPE,
            labels::GRAPHQL_RESPONSE_STATUS,
        ],
    ),
    (
        names::HTTP_SERVER_REQUEST_BODY_SIZE,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::HTTP_RESPONSE_STATUS_CODE,
            labels::HTTP_ROUTE,
            labels::NETWORK_PROTOCOL_NAME,
            labels::NETWORK_PROTOCOL_VERSION,
            labels::URL_SCHEME,
            labels::ERROR_TYPE,
            labels::GRAPHQL_OPERATION_NAME,
            labels::GRAPHQL_OPERATION_TYPE,
            labels::GRAPHQL_RESPONSE_STATUS,
        ],
    ),
    (
        names::HTTP_SERVER_RESPONSE_BODY_SIZE,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::HTTP_RESPONSE_STATUS_CODE,
            labels::HTTP_ROUTE,
            labels::NETWORK_PROTOCOL_NAME,
            labels::NETWORK_PROTOCOL_VERSION,
            labels::URL_SCHEME,
            labels::ERROR_TYPE,
            labels::GRAPHQL_OPERATION_NAME,
            labels::GRAPHQL_OPERATION_TYPE,
            labels::GRAPHQL_RESPONSE_STATUS,
        ],
    ),
    (
        names::HTTP_SERVER_ACTIVE_REQUESTS,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::NETWORK_PROTOCOL_NAME,
            labels::URL_SCHEME,
        ],
    ),
    (
        names::HTTP_CLIENT_REQUEST_DURATION,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::SERVER_ADDRESS,
            labels::SERVER_PORT,
            labels::NETWORK_PROTOCOL_NAME,
            labels::NETWORK_PROTOCOL_VERSION,
            labels::URL_SCHEME,
            labels::SUBGRAPH_NAME,
            labels::HTTP_RESPONSE_STATUS_CODE,
            labels::ERROR_TYPE,
            labels::GRAPHQL_RESPONSE_STATUS,
        ],
    ),
    (
        names::HTTP_CLIENT_REQUEST_BODY_SIZE,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::SERVER_ADDRESS,
            labels::SERVER_PORT,
            labels::NETWORK_PROTOCOL_NAME,
            labels::NETWORK_PROTOCOL_VERSION,
            labels::URL_SCHEME,
            labels::SUBGRAPH_NAME,
            labels::HTTP_RESPONSE_STATUS_CODE,
            labels::ERROR_TYPE,
            labels::GRAPHQL_RESPONSE_STATUS,
        ],
    ),
    (
        names::HTTP_CLIENT_RESPONSE_BODY_SIZE,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::SERVER_ADDRESS,
            labels::SERVER_PORT,
            labels::NETWORK_PROTOCOL_NAME,
            labels::NETWORK_PROTOCOL_VERSION,
            labels::URL_SCHEME,
            labels::SUBGRAPH_NAME,
            labels::HTTP_RESPONSE_STATUS_CODE,
            labels::ERROR_TYPE,
            labels::GRAPHQL_RESPONSE_STATUS,
        ],
    ),
    (
        names::HTTP_CLIENT_ACTIVE_REQUESTS,
        &[
            labels::HTTP_REQUEST_METHOD,
            labels::SERVER_ADDRESS,
            labels::SERVER_PORT,
            labels::URL_SCHEME,
            labels::SUBGRAPH_NAME,
        ],
    ),
    (names::SUPERGRAPH_POLL_TOTAL, &[labels::RESULT]),
    (names::SUPERGRAPH_POLL_DURATION, &[labels::RESULT]),
    (names::SUPERGRAPH_PROCESS_DURATION, &[labels::STATUS]),
    (names::PARSE_CACHE_REQUESTS_TOTAL, &[labels::RESULT]),
    (names::PARSE_CACHE_DURATION, &[labels::RESULT]),
    (names::PARSE_CACHE_SIZE, &[]),
    (names::VALIDATE_CACHE_REQUESTS_TOTAL, &[labels::RESULT]),
    (names::VALIDATE_CACHE_DURATION, &[labels::RESULT]),
    (names::VALIDATE_CACHE_SIZE, &[]),
    (names::NORMALIZE_CACHE_REQUESTS_TOTAL, &[labels::RESULT]),
    (names::NORMALIZE_CACHE_DURATION, &[labels::RESULT]),
    (names::NORMALIZE_CACHE_SIZE, &[]),
    (names::PLAN_CACHE_REQUESTS_TOTAL, &[labels::RESULT]),
    (names::PLAN_CACHE_DURATION, &[labels::RESULT]),
    (names::PLAN_CACHE_SIZE, &[]),
    (
        names::CIRCUIT_BREAKER_SHORT_CIRCUITS_TOTAL,
        &[labels::SUBGRAPH_NAME],
    ),
    (names::CIRCUIT_BREAKER_STATE, &[labels::SUBGRAPH_NAME]),
    (
        names::CIRCUIT_BREAKER_STATE_TRANSITIONS_TOTAL,
        &[
            labels::SUBGRAPH_NAME,
            labels::CIRCUIT_BREAKER_FROM_STATE,
            labels::CIRCUIT_BREAKER_TO_STATE,
        ],
    ),
    (
        names::CIRCUIT_BREAKER_FAILURES_TOTAL,
        &[labels::SUBGRAPH_NAME],
    ),
    (names::PERSISTED_DOCUMENTS_STORAGE_FAILURES_TOTAL, &[]),
    (names::PERSISTED_DOCUMENTS_EXTRACT_MISSING_ID_TOTAL, &[]),
    (
        names::COPROCESSOR_REQUESTS_TOTAL,
        &[labels::COPROCESSOR_STAGE],
    ),
    (names::COPROCESSOR_DURATION, &[labels::COPROCESSOR_STAGE]),
    (
        names::COPROCESSOR_ERRORS_TOTAL,
        &[labels::COPROCESSOR_STAGE],
    ),
];

pub fn labels_for(metric_name: &str) -> Option<&'static [&'static str]> {
    METRIC_SPECS
        .iter()
        .find(|(name, _)| *name == metric_name)
        .map(|(_, labels)| *labels)
}

pub(crate) fn all_metric_names() -> Vec<&'static str> {
    METRIC_SPECS.iter().map(|(name, _)| *name).collect()
}

#[cfg(debug_assertions)]
pub(crate) fn debug_assert_attrs(metric_name: &'static str, attrs: &[KeyValue]) {
    let labels = labels_for(metric_name)
        .unwrap_or_else(|| panic!("missing metric catalog entry for {metric_name}"));

    for attr in attrs {
        debug_assert!(
            labels.contains(&attr.key.as_str()),
            "attribute '{}' is not declared for metric '{}'",
            attr.key.as_str(),
            metric_name
        );
    }
}
