use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ErrorCode {
    #[serde(rename = "INVALID_ARGUMENT")]
    InvalidArgument,
    #[serde(rename = "NOT_FOUND")]
    NotFound,
    #[serde(rename = "ALREADY_EXISTS")]
    AlreadyExists,
    #[serde(rename = "VERSION_CONFLICT")]
    VersionConflict,
    #[serde(rename = "RATE_LIMITED")]
    RateLimited,
    #[serde(rename = "UNAUTHENTICATED")]
    Unauthenticated,
    #[serde(rename = "PERMISSION_DENIED")]
    PermissionDenied,
    #[serde(rename = "FAILED_PRECONDITION")]
    FailedPrecondition,
    #[serde(rename = "BACKEND_UNAVAILABLE")]
    BackendUnavailable,
    #[serde(rename = "INTERNAL_ERROR")]
    InternalError,
    #[serde(rename = "CONNECTION_ERROR")]
    ConnectionError,
    #[serde(rename = "TIMEOUT")]
    Timeout,
}

impl fmt::Display for ErrorCode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidArgument => write!(f, "INVALID_ARGUMENT"),
            Self::NotFound => write!(f, "NOT_FOUND"),
            Self::AlreadyExists => write!(f, "ALREADY_EXISTS"),
            Self::VersionConflict => write!(f, "VERSION_CONFLICT"),
            Self::RateLimited => write!(f, "RATE_LIMITED"),
            Self::Unauthenticated => write!(f, "UNAUTHENTICATED"),
            Self::PermissionDenied => write!(f, "PERMISSION_DENIED"),
            Self::FailedPrecondition => write!(f, "FAILED_PRECONDITION"),
            Self::BackendUnavailable => write!(f, "BACKEND_UNAVAILABLE"),
            Self::InternalError => write!(f, "INTERNAL_ERROR"),
            Self::ConnectionError => write!(f, "CONNECTION_ERROR"),
            Self::Timeout => write!(f, "TIMEOUT"),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolError {
    pub code: String,
    pub message: String,
    #[serde(default)]
    pub retryable: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Clone)]
pub struct TetrixError {
    pub code: ErrorCode,
    pub message: String,
    pub retryable: bool,
    pub details: Option<HashMap<String, serde_json::Value>>,
}

impl TetrixError {
    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
        let retryable = matches!(
            code,
            ErrorCode::RateLimited | ErrorCode::BackendUnavailable | ErrorCode::ConnectionError
        );
        Self {
            code,
            message: message.into(),
            retryable,
            details: None,
        }
    }

    pub fn with_retryable(mut self, retryable: bool) -> Self {
        self.retryable = retryable;
        self
    }

    pub fn with_details(mut self, details: HashMap<String, serde_json::Value>) -> Self {
        self.details = Some(details);
        self
    }

    pub fn from_protocol(pe: &ProtocolError) -> Self {
        let code = match pe.code.as_str() {
            "INVALID_ARGUMENT" => ErrorCode::InvalidArgument,
            "NOT_FOUND" => ErrorCode::NotFound,
            "ALREADY_EXISTS" => ErrorCode::AlreadyExists,
            "VERSION_CONFLICT" => ErrorCode::VersionConflict,
            "RATE_LIMITED" => ErrorCode::RateLimited,
            "UNAUTHENTICATED" => ErrorCode::Unauthenticated,
            "PERMISSION_DENIED" => ErrorCode::PermissionDenied,
            "FAILED_PRECONDITION" => ErrorCode::FailedPrecondition,
            "BACKEND_UNAVAILABLE" => ErrorCode::BackendUnavailable,
            "CONNECTION_ERROR" => ErrorCode::ConnectionError,
            "TIMEOUT" => ErrorCode::Timeout,
            _ => ErrorCode::InternalError,
        };
        Self {
            code,
            message: pe.message.clone(),
            retryable: pe.retryable,
            details: pe.details.clone(),
        }
    }

    pub fn is_retryable(&self) -> bool {
        self.retryable
    }
}

impl fmt::Display for TetrixError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "tetrixaidb: {}: {}", self.code, self.message)
    }
}

impl std::error::Error for TetrixError {}

impl From<std::io::Error> for TetrixError {
    fn from(err: std::io::Error) -> Self {
        Self::new(ErrorCode::ConnectionError, err.to_string())
    }
}

impl From<serde_json::Error> for TetrixError {
    fn from(err: serde_json::Error) -> Self {
        Self::new(ErrorCode::InternalError, format!("json: {err}"))
    }
}
