//! API error types and handling.

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Result type for API operations.
pub type ApiResult<T> = Result<T, ApiError>;

/// API error types.
#[derive(Debug, Error)]
pub enum ApiError {
    // Authentication errors (401)
    #[error("Authentication required")]
    Unauthorized,

    #[error("Invalid API key")]
    InvalidApiKey,

    #[error("Expired token")]
    ExpiredToken,

    #[error("Invalid token: {0}")]
    InvalidToken(String),

    // Authorization errors (403)
    #[error("Access denied: {0}")]
    Forbidden(String),

    #[error("Insufficient permissions")]
    InsufficientPermissions,

    // Not found errors (404)
    #[error("Resource not found: {0}")]
    NotFound(String),

    #[error("Endpoint not found")]
    EndpointNotFound,

    // Validation errors (400)
    #[error("Invalid request: {0}")]
    BadRequest(String),

    #[error("Validation failed: {0}")]
    ValidationError(String),

    #[error("Missing required field: {0}")]
    MissingField(String),

    #[error("Invalid parameter: {field} - {message}")]
    InvalidParameter { field: String, message: String },

    // Rate limiting (429)
    #[error("Rate limit exceeded")]
    RateLimitExceeded,

    #[error("Too many requests, retry after {retry_after} seconds")]
    TooManyRequests { retry_after: u64 },

    // Conflict errors (409)
    #[error("Resource already exists: {0}")]
    Conflict(String),

    // Service errors (500+)
    #[error("Internal server error")]
    InternalError,

    #[error("Service unavailable: {0}")]
    ServiceUnavailable(String),

    #[error("Upstream service error: {0}")]
    UpstreamError(String),

    #[error("Request timeout")]
    Timeout,

    // Domain-specific errors
    #[error("Insufficient balance: required {required}, available {available}")]
    InsufficientBalance { required: String, available: String },

    #[error("Transaction failed: {0}")]
    TransactionFailed(String),

    #[error("Contract error: {0}")]
    ContractError(String),

    #[error("Invalid address: {0}")]
    InvalidAddress(String),

    #[error("Invalid signature")]
    InvalidSignature,

    // Catch-all
    #[error("{0}")]
    Custom(String),
}

/// Error response body.
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
    /// Whether the request succeeded
    pub success: bool,

    /// Error details
    pub error: ErrorDetails,

    /// Request metadata
    #[serde(skip_serializing_if = "Option::is_none")]
    pub meta: Option<ErrorMeta>,
}

/// Detailed error information.
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorDetails {
    /// Error code (machine-readable)
    pub code: String,

    /// Human-readable error message
    pub message: String,

    /// Additional error details
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,
}

/// Error metadata.
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorMeta {
    /// Request ID for tracing
    pub request_id: String,

    /// Timestamp
    pub timestamp: String,
}

impl ApiError {
    /// Get the HTTP status code for this error.
    pub fn status_code(&self) -> StatusCode {
        match self {
            // 400 Bad Request
            Self::BadRequest(_)
            | Self::ValidationError(_)
            | Self::MissingField(_)
            | Self::InvalidParameter { .. }
            | Self::InvalidAddress(_)
            | Self::InvalidSignature => StatusCode::BAD_REQUEST,

            // 401 Unauthorized
            Self::Unauthorized
            | Self::InvalidApiKey
            | Self::ExpiredToken
            | Self::InvalidToken(_) => StatusCode::UNAUTHORIZED,

            // 403 Forbidden
            Self::Forbidden(_) | Self::InsufficientPermissions => StatusCode::FORBIDDEN,

            // 404 Not Found
            Self::NotFound(_) | Self::EndpointNotFound => StatusCode::NOT_FOUND,

            // 409 Conflict
            Self::Conflict(_) => StatusCode::CONFLICT,

            // 422 Unprocessable Entity
            Self::InsufficientBalance { .. }
            | Self::TransactionFailed(_)
            | Self::ContractError(_) => StatusCode::UNPROCESSABLE_ENTITY,

            // 429 Too Many Requests
            Self::RateLimitExceeded | Self::TooManyRequests { .. } => {
                StatusCode::TOO_MANY_REQUESTS
            }

            // 500 Internal Server Error
            Self::InternalError | Self::Custom(_) => StatusCode::INTERNAL_SERVER_ERROR,

            // 502 Bad Gateway
            Self::UpstreamError(_) => StatusCode::BAD_GATEWAY,

            // 503 Service Unavailable
            Self::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,

            // 504 Gateway Timeout
            Self::Timeout => StatusCode::GATEWAY_TIMEOUT,
        }
    }

    /// Get the error code string.
    pub fn error_code(&self) -> &'static str {
        match self {
            Self::Unauthorized => "UNAUTHORIZED",
            Self::InvalidApiKey => "INVALID_API_KEY",
            Self::ExpiredToken => "EXPIRED_TOKEN",
            Self::InvalidToken(_) => "INVALID_TOKEN",
            Self::Forbidden(_) => "FORBIDDEN",
            Self::InsufficientPermissions => "INSUFFICIENT_PERMISSIONS",
            Self::NotFound(_) => "NOT_FOUND",
            Self::EndpointNotFound => "ENDPOINT_NOT_FOUND",
            Self::BadRequest(_) => "BAD_REQUEST",
            Self::ValidationError(_) => "VALIDATION_ERROR",
            Self::MissingField(_) => "MISSING_FIELD",
            Self::InvalidParameter { .. } => "INVALID_PARAMETER",
            Self::RateLimitExceeded => "RATE_LIMIT_EXCEEDED",
            Self::TooManyRequests { .. } => "TOO_MANY_REQUESTS",
            Self::Conflict(_) => "CONFLICT",
            Self::InternalError => "INTERNAL_ERROR",
            Self::ServiceUnavailable(_) => "SERVICE_UNAVAILABLE",
            Self::UpstreamError(_) => "UPSTREAM_ERROR",
            Self::Timeout => "TIMEOUT",
            Self::InsufficientBalance { .. } => "INSUFFICIENT_BALANCE",
            Self::TransactionFailed(_) => "TRANSACTION_FAILED",
            Self::ContractError(_) => "CONTRACT_ERROR",
            Self::InvalidAddress(_) => "INVALID_ADDRESS",
            Self::InvalidSignature => "INVALID_SIGNATURE",
            Self::Custom(_) => "ERROR",
        }
    }

    /// Build error details with optional extra information.
    pub fn to_details(&self) -> ErrorDetails {
        let details = match self {
            Self::InsufficientBalance { required, available } => Some(serde_json::json!({
                "required": required,
                "available": available
            })),
            Self::InvalidParameter { field, message } => Some(serde_json::json!({
                "field": field,
                "message": message
            })),
            Self::TooManyRequests { retry_after } => Some(serde_json::json!({
                "retry_after": retry_after
            })),
            _ => None,
        };

        ErrorDetails {
            code: self.error_code().to_string(),
            message: self.to_string(),
            details,
        }
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let status = self.status_code();
        let body = ErrorResponse {
            success: false,
            error: self.to_details(),
            meta: None, // Will be filled in by middleware
        };

        let mut response = (status, Json(body)).into_response();

        // Add rate limit headers for 429 errors
        if let Self::TooManyRequests { retry_after } = &self {
            response.headers_mut().insert(
                "Retry-After",
                retry_after.to_string().parse().unwrap(),
            );
        }

        response
    }
}

// Implement From for common error types
impl From<anyhow::Error> for ApiError {
    fn from(err: anyhow::Error) -> Self {
        tracing::error!("Internal error: {:?}", err);
        Self::InternalError
    }
}

impl From<serde_json::Error> for ApiError {
    fn from(err: serde_json::Error) -> Self {
        Self::BadRequest(format!("JSON parsing error: {}", err))
    }
}

impl From<std::io::Error> for ApiError {
    fn from(err: std::io::Error) -> Self {
        tracing::error!("IO error: {:?}", err);
        Self::InternalError
    }
}
