// Copyright 2016 TiKV Project Authors. Licensed under Apache-2.0.
// Modifications Copyright 2022 PingCAP, Inc.

use std::{error, result};

use error_code::{self, ErrorCode, ErrorCodeExt};
use futures::channel::mpsc::SendError;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
    #[error("cluster {0} is already bootstrapped")]
    ClusterBootstrapped(u64),
    #[error("cluster {0} is not bootstrapped")]
    ClusterNotBootstrapped(u64),
    #[error("feature is not supported in other cluster components")]
    Incompatible,
    #[error("keyspace not found: {0}")]
    KeyspaceNotFound(u32),
    #[error("keyspace not found: requested {requested}, got {returned}")]
    KeyspaceIdMismatch { requested: u32, returned: u32 },
    #[error("{0}")]
    Grpc(#[from] grpcio::Error),
    #[error("{0}")]
    StreamDisconnect(#[from] SendError),
    #[error("unknown error {0:?}")]
    Other(#[from] Box<dyn error::Error + Sync + Send>),
    #[error("region is not found for key {}", log_wrappers::Value::key(.0))]
    RegionNotFound(Vec<u8>),
    #[error("store is tombstone {0:?}")]
    StoreTombstone(String),
    #[error("global config item {0} not found")]
    GlobalConfigNotFound(String),
    #[error("required watch revision is smaller than current compact/min revision. {0:?}")]
    DataCompacted(String),
    #[error(
        "the requested service gc safe point({requested}) isn't safe(safe point now is {current_minimal})"
    )]
    UnsafeServiceGcSafePoint {
        requested: txn_types::TimeStamp,
        current_minimal: txn_types::TimeStamp,
    },
    #[error("channel is dropped")]
    ChannelDrop,
    #[error("tso server not found")]
    TsoServerNotFound,
    #[error("split regions not finished ({finished_percent}%)")]
    SplitRegionsNotFinished {
        regions_id: Vec<u64>,
        finished_percent: u64,
    },
}

pub type Result<T> = result::Result<T, Error>;

impl Error {
    pub fn retryable(&self) -> bool {
        match self {
            Error::Grpc(_)
            | Error::ClusterNotBootstrapped(_)
            | Error::StreamDisconnect(_)
            | Error::ChannelDrop
            | Error::TsoServerNotFound
            | Error::DataCompacted(_) => true,
            Error::Other(_)
            | Error::KeyspaceNotFound(_)
            | Error::KeyspaceIdMismatch { .. }
            | Error::RegionNotFound(_)
            | Error::StoreTombstone(_)
            | Error::GlobalConfigNotFound(_)
            | Error::ClusterBootstrapped(_)
            | Error::Incompatible
            | Error::UnsafeServiceGcSafePoint { .. } => false,
            e @ Error::SplitRegionsNotFinished { .. } => {
                debug_assert!(false, "unreachable: {:?}", e);
                false
            }
        }
    }

    pub fn is_keyspace_not_found(&self) -> bool {
        matches!(
            self,
            Error::KeyspaceNotFound(_) | Error::KeyspaceIdMismatch { .. }
        )
    }
}

impl ErrorCodeExt for Error {
    fn error_code(&self) -> ErrorCode {
        match self {
            Error::ClusterBootstrapped(_) => error_code::pd::CLUSTER_BOOTSTRAPPED,
            Error::ClusterNotBootstrapped(_) => error_code::pd::CLUSTER_NOT_BOOTSTRAPPED,
            Error::Incompatible => error_code::pd::INCOMPATIBLE,
            Error::KeyspaceNotFound(_) | Error::KeyspaceIdMismatch { .. } => {
                error_code::pd::UNKNOWN
            }
            Error::Grpc(_) => error_code::pd::GRPC,
            Error::StreamDisconnect(_) => error_code::pd::STREAM_DISCONNECT,
            Error::RegionNotFound(_) => error_code::pd::REGION_NOT_FOUND,
            Error::StoreTombstone(_) => error_code::pd::STORE_TOMBSTONE,
            Error::GlobalConfigNotFound(_) => error_code::pd::GLOBAL_CONFIG_NOT_FOUND,
            Error::DataCompacted(_) => error_code::pd::DATA_COMPACTED,
            Error::UnsafeServiceGcSafePoint { .. } => error_code::pd::STALE_SERVICE_GC_SAFE_POINT,
            Error::ChannelDrop => error_code::pd::CHANNEL_DROP,
            Error::TsoServerNotFound => error_code::pd::TSO_SERVER_NOT_FOUND,
            Error::SplitRegionsNotFinished { .. } => error_code::pd::SPLIT_REGIONS_NOT_FINISHED,
            Error::Other(_) => error_code::pd::UNKNOWN,
        }
    }
}
