use axum::{
    Json,
    http::StatusCode,
    response::{IntoResponse, Response},
};
use crank_mapping::MappingError;
use crank_registry::RegistryError;
use crank_runtime::RuntimeError;
use crank_schema::SchemaError;
use serde_json::{Value, json};
use thiserror::Error;
use tracing::{error, warn};

use crate::storage::StorageError;

#[derive(Debug, Error)]
pub enum ApiError {
    #[error("{message}")]
    Unauthorized { message: String },
    #[error("{message}")]
    Forbidden { message: String },
    #[error("{message}")]
    Validation { message: String },
    #[error("{message}")]
    NotFound { message: String },
    #[error("{message}")]
    Conflict { message: String },
    #[error("{message}")]
    Internal { message: String },
}

impl ApiError {
    pub fn unauthorized(message: impl Into<String>) -> Self {
        Self::Unauthorized {
            message: message.into(),
        }
    }

    pub fn forbidden(message: impl Into<String>) -> Self {
        Self::Forbidden {
            message: message.into(),
        }
    }

    pub fn validation(message: impl Into<String>) -> Self {
        Self::Validation {
            message: message.into(),
        }
    }

    pub fn not_found(message: impl Into<String>) -> Self {
        Self::NotFound {
            message: message.into(),
        }
    }

    pub fn conflict(message: impl Into<String>) -> Self {
        Self::Conflict {
            message: message.into(),
        }
    }

    pub fn internal(message: impl Into<String>) -> Self {
        Self::Internal {
            message: message.into(),
        }
    }

    fn status_code(&self) -> StatusCode {
        match self {
            Self::Unauthorized { .. } => StatusCode::UNAUTHORIZED,
            Self::Forbidden { .. } => StatusCode::FORBIDDEN,
            Self::Validation { .. } => StatusCode::BAD_REQUEST,
            Self::NotFound { .. } => StatusCode::NOT_FOUND,
            Self::Conflict { .. } => StatusCode::CONFLICT,
            Self::Internal { .. } => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }

    fn code(&self) -> &'static str {
        match self {
            Self::Unauthorized { .. } => "unauthorized",
            Self::Forbidden { .. } => "forbidden",
            Self::Validation { .. } => "validation_error",
            Self::NotFound { .. } => "not_found",
            Self::Conflict { .. } => "conflict",
            Self::Internal { .. } => "internal_error",
        }
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        match &self {
            Self::Internal { message } => {
                error!(error_code = self.code(), error_message = %message)
            }
            Self::Unauthorized { message }
            | Self::Forbidden { message }
            | Self::Validation { message }
            | Self::NotFound { message }
            | Self::Conflict { message } => {
                warn!(error_code = self.code(), error_message = %message)
            }
        }

        let body = Json(json!({
            "error": {
                "code": self.code(),
                "message": self.to_string(),
            }
        }));

        (self.status_code(), body).into_response()
    }
}

impl From<RegistryError> for ApiError {
    fn from(value: RegistryError) -> Self {
        match value {
            RegistryError::WorkspaceNotFound { workspace_id } => {
                Self::not_found(format!("workspace {workspace_id} was not found"))
            }
            RegistryError::UserNotFound { user_id } => {
                Self::not_found(format!("user {user_id} was not found"))
            }
            RegistryError::MembershipNotFound {
                workspace_id,
                user_id,
            } => Self::not_found(format!(
                "membership for user {user_id} in workspace {workspace_id} was not found"
            )),
            RegistryError::AgentNotFound { agent_id } => {
                Self::not_found(format!("agent {agent_id} was not found"))
            }
            RegistryError::InvitationNotFound { invitation_id } => {
                Self::not_found(format!("invitation {invitation_id} was not found"))
            }
            RegistryError::PlatformApiKeyNotFound { key_id } => {
                Self::not_found(format!("platform api key {key_id} was not found"))
            }
            RegistryError::SecretNotFound { secret_id } => {
                Self::not_found(format!("secret {secret_id} was not found"))
            }
            RegistryError::StreamSessionNotFound { session_id } => {
                Self::not_found(format!("stream session {session_id} was not found"))
            }
            RegistryError::AsyncJobNotFound { job_id } => {
                Self::not_found(format!("async job {job_id} was not found"))
            }
            RegistryError::InvocationLogNotFound { log_id } => {
                Self::not_found(format!("invocation log {log_id} was not found"))
            }
            RegistryError::PublishedAgentNotFound {
                workspace_slug,
                agent_slug,
            } => Self::not_found(format!(
                "published agent {workspace_slug}/{agent_slug} was not found"
            )),
            RegistryError::OperationNotFound { operation_id } => {
                Self::not_found(format!("operation {operation_id} was not found"))
            }
            RegistryError::OperationVersionNotFound {
                operation_id,
                version,
            } => Self::not_found(format!(
                "operation version {version} for {operation_id} was not found"
            )),
            RegistryError::OperationHasPublishedAgentBindings { operation_id } => {
                Self::conflict(format!(
                    "operation {operation_id} cannot be deleted while it is bound to a published agent"
                ))
            }
            RegistryError::AuthProfileNotFound { auth_profile_id } => {
                Self::not_found(format!("auth profile {auth_profile_id} was not found"))
            }
            RegistryError::OperationAlreadyExists { operation_id } => {
                Self::conflict(format!("operation {operation_id} already exists"))
            }
            RegistryError::WorkspaceSlugAlreadyExists { slug } => {
                Self::conflict(format!("workspace with slug {slug} already exists"))
            }
            RegistryError::SecretNameAlreadyExists { workspace_id, name } => Self::conflict(
                format!("secret with name {name} already exists in workspace {workspace_id}"),
            ),
            RegistryError::SecretReferencedByAuthProfile {
                secret_id,
                auth_profile_id,
            } => Self::conflict(format!(
                "secret {secret_id} is referenced by auth profile {auth_profile_id}"
            )),
            RegistryError::InvalidStreamSessionTransition { .. }
            | RegistryError::InvalidAsyncJobTransition { .. } => Self::conflict(value.to_string()),
            RegistryError::UserEmailAlreadyExists { email } => {
                Self::conflict(format!("user with email {email} already exists"))
            }
            RegistryError::InvalidInitialVersion { .. }
            | RegistryError::InvalidVersionSequence { .. }
            | RegistryError::ImmutableOperationFieldChanged { .. }
            | RegistryError::InvalidEnumRepresentation { .. }
            | RegistryError::InvalidNumericValue { .. }
            | RegistryError::YamlImportJobNotFound { .. } => Self::validation(value.to_string()),
            RegistryError::Storage(_) | RegistryError::Serialization(_) => {
                Self::internal(value.to_string())
            }
        }
    }
}

impl From<MappingError> for ApiError {
    fn from(value: MappingError) -> Self {
        Self::validation(value.to_string())
    }
}

impl From<SchemaError> for ApiError {
    fn from(value: SchemaError) -> Self {
        Self::validation(value.to_string())
    }
}

impl From<StorageError> for ApiError {
    fn from(value: StorageError) -> Self {
        match value {
            StorageError::InvalidStorageRef { details } => Self::validation(details),
            StorageError::Io(_) | StorageError::Serialization(_) => {
                Self::internal(value.to_string())
            }
        }
    }
}

pub fn runtime_test_failure(error: &RuntimeError) -> Value {
    json!({
        "code": runtime_test_failure_code(error),
        "message": error.to_string()
    })
}

fn runtime_test_failure_code(error: &RuntimeError) -> &'static str {
    match error {
        RuntimeError::Schema(_) => "runtime_schema_error",
        RuntimeError::Mapping(_) => "runtime_mapping_error",
        RuntimeError::GraphqlAdapter(_) => "runtime_graphql_error",
        RuntimeError::GrpcAdapter(_) => "runtime_grpc_error",
        RuntimeError::RestAdapter(_) => "runtime_rest_error",
        RuntimeError::SoapAdapter(_) => "runtime_soap_error",
        RuntimeError::WebsocketAdapter(_) => "runtime_websocket_error",
        RuntimeError::UnsupportedProtocol { .. } => "runtime_protocol_error",
        RuntimeError::InvalidPreparedRequest { .. } => "runtime_request_error",
        RuntimeError::MissingStreamingConfig { .. } => "runtime_streaming_config_error",
        RuntimeError::UnsupportedExecutionMode { .. } => "runtime_streaming_mode_error",
        RuntimeError::InvalidStreamingPayload { .. } => "runtime_streaming_payload_error",
        RuntimeError::MissingAuthProfile { .. } => "runtime_auth_profile_error",
        RuntimeError::MissingSecret { .. } | RuntimeError::MissingSecretVersion { .. } => {
            "runtime_secret_error"
        }
        RuntimeError::InvalidAuthSecretValue { .. } => "runtime_secret_value_error",
        RuntimeError::SecretCrypto { .. } => "runtime_secret_crypto_error",
    }
}
