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

/// Plesk-specific error types
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
pub enum PleskError {
    #[error("Connection to {host} failed: {reason}")]
    ConnectionFailed { host: String, reason: String },

    #[error("Authentication failed: {reason}")]
    AuthenticationFailed { reason: String },

    #[error("Two-factor authentication required")]
    TwoFactorRequired { session_id: String, method: String },

    #[error("Plesk API error (code {code}): {message}")]
    ApiError { code: i32, message: String },

    #[error("Connection timed out after {duration_ms}ms")]
    Timeout { duration_ms: u64 },

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

    #[error("SSL certificate error: {reason}")]
    SslError { reason: String },
}

/// SAP-specific error types
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
pub enum SapError {
    #[error("Connection to {host} failed: {reason}")]
    ConnectionFailed { host: String, reason: String },

    #[error("Authentication failed: {reason}")]
    AuthenticationFailed { reason: String },

    #[error("Session expired")]
    SessionExpired,

    #[error("SAP API error (code {code}): {message}")]
    ApiError { code: i32, message: String },

    #[error("Invalid response from SAP: {raw}")]
    InvalidResponse { raw: String },

    #[error("Connection timed out after {duration_ms}ms")]
    Timeout { duration_ms: u64 },

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

    #[error("SSL certificate error: {reason}")]
    SslError { reason: String },
}

/// Connection test result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionTestResult {
    pub success: bool,
    pub message: String,
    pub latency_ms: Option<u64>,
    pub error: Option<ConnectionError>,
    pub requires_2fa: bool,
    pub session_id: Option<String>,
    pub two_factor_method: Option<String>,
}

/// Unified connection error for API responses
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionError {
    pub error_type: String,
    pub error_code: String,
    pub message: String,
    pub details: Option<String>,
}

impl From<PleskError> for ConnectionError {
    fn from(err: PleskError) -> Self {
        let (error_type, error_code, message) = match &err {
            PleskError::ConnectionFailed { .. } => {
                ("connection", "PLESK_CONN_001", err.to_string())
            }
            PleskError::AuthenticationFailed { .. } => {
                ("authentication", "PLESK_AUTH_001", err.to_string())
            }
            PleskError::TwoFactorRequired { .. } => {
                ("two_factor", "PLESK_2FA_001", err.to_string())
            }
            PleskError::ApiError { .. } => ("api", "PLESK_API_001", err.to_string()),
            PleskError::Timeout { .. } => ("timeout", "PLESK_TIMEOUT_001", err.to_string()),
            PleskError::InvalidConfig { .. } => ("validation", "PLESK_VAL_001", err.to_string()),
            PleskError::SslError { .. } => ("ssl", "PLESK_SSL_001", err.to_string()),
        };
        ConnectionError {
            error_type: error_type.to_string(),
            error_code: error_code.to_string(),
            message,
            details: None,
        }
    }
}

impl From<SapError> for ConnectionError {
    fn from(err: SapError) -> Self {
        let (error_type, error_code, message) = match &err {
            SapError::ConnectionFailed { .. } => ("connection", "SAP_CONN_001", err.to_string()),
            SapError::AuthenticationFailed { .. } => {
                ("authentication", "SAP_AUTH_001", err.to_string())
            }
            SapError::SessionExpired => ("session", "SAP_SESSION_001", err.to_string()),
            SapError::ApiError { .. } => ("api", "SAP_API_001", err.to_string()),
            SapError::InvalidResponse { .. } => ("response", "SAP_RESP_001", err.to_string()),
            SapError::Timeout { .. } => ("timeout", "SAP_TIMEOUT_001", err.to_string()),
            SapError::InvalidConfig { .. } => ("validation", "SAP_VAL_001", err.to_string()),
            SapError::SslError { .. } => ("ssl", "SAP_SSL_001", err.to_string()),
        };
        ConnectionError {
            error_type: error_type.to_string(),
            error_code: error_code.to_string(),
            message,
            details: None,
        }
    }
}

/// Comprehensive error types for the API
#[derive(Debug, thiserror::Error)]
pub enum ApiError {
    #[error("Database error: {0}")]
    Database(String),

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

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

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

    #[error("SAP API error: {0}")]
    SapApi(String),

    #[error("Plesk API error: {0}")]
    PleskApi(String),

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

    #[error("Internal server error: {0}")]
    Internal(String),
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let status = match self {
            ApiError::Authentication(_) => StatusCode::UNAUTHORIZED,
            ApiError::Authorization(_) => StatusCode::FORBIDDEN,
            ApiError::Validation(_) => StatusCode::BAD_REQUEST,
            ApiError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
            ApiError::SapApi(_) => StatusCode::BAD_GATEWAY,
            ApiError::PleskApi(_) => StatusCode::BAD_GATEWAY,
            ApiError::Sync(_) => StatusCode::INTERNAL_SERVER_ERROR,
            ApiError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
        };

        let error_code = self.error_code();
        let error_message = self.to_string();

        (
            status,
            Json(json!({
                "success": false,
                "error": error_message,
                "error_code": error_code,
                "timestamp": chrono::Utc::now().to_rfc3339()
            })),
        )
            .into_response()
    }
}

impl ApiError {
    /// Get the error code for this error
    pub fn error_code(&self) -> String {
        match self {
            ApiError::Authentication(_) => "AUTH_001".to_string(),
            ApiError::Authorization(_) => "AUTH_002".to_string(),
            ApiError::Validation(_) => "VAL_001".to_string(),
            ApiError::Database(_) => "DB_001".to_string(),
            ApiError::SapApi(_) => "SAP_001".to_string(),
            ApiError::PleskApi(_) => "PLESK_001".to_string(),
            ApiError::Sync(_) => "SYNC_001".to_string(),
            ApiError::Internal(_) => "INT_001".to_string(),
        }
    }

    /// Check if this is a client error (4xx)
    pub fn is_client_error(&self) -> bool {
        matches!(
            self,
            ApiError::Authentication(_) | ApiError::Authorization(_) | ApiError::Validation(_)
        )
    }

    /// Check if this is a server error (5xx)
    pub fn is_server_error(&self) -> bool {
        matches!(
            self,
            ApiError::Database(_)
                | ApiError::SapApi(_)
                | ApiError::PleskApi(_)
                | ApiError::Sync(_)
                | ApiError::Internal(_)
        )
    }
}

/// Error context for better error tracking
#[derive(Debug)]
pub struct ErrorContext {
    pub request_id: String,
    pub path: String,
    pub method: String,
    pub user_id: Option<i32>,
    pub timestamp: chrono::DateTime<chrono::Utc>,
}

impl ErrorContext {
    pub fn new(request_id: String, path: String, method: String) -> Self {
        Self {
            request_id,
            path,
            method,
            user_id: None,
            timestamp: chrono::Utc::now(),
        }
    }

    pub fn with_user_id(mut self, user_id: i32) -> Self {
        self.user_id = Some(user_id);
        self
    }
}

impl From<ErrorContext> for ApiError {
    fn from(ctx: ErrorContext) -> Self {
        ApiError::Internal(format!(
            "Error in {}: {} (request_id: {})",
            ctx.path, ctx.method, ctx.request_id
        ))
    }
}

/// Result type alias for easier error handling
pub type ApiResult<T> = Result<T, ApiError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_conversion() {
        let error = ApiError::Authentication("Invalid credentials".to_string());
        let response = error.into_response();
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
    }

    #[test]
    fn test_error_code() {
        assert_eq!(
            ApiError::Authentication("test".to_string()).error_code(),
            "AUTH_001"
        );
        assert_eq!(
            ApiError::Validation("test".to_string()).error_code(),
            "VAL_001"
        );
        assert_eq!(
            ApiError::Database("test".to_string()).error_code(),
            "DB_001"
        );
    }

    #[test]
    fn test_error_classification() {
        assert!(ApiError::Authentication("test".to_string()).is_client_error());
        assert!(ApiError::Validation("test".to_string()).is_client_error());
        assert!(!ApiError::Authentication("test".to_string()).is_server_error());
        assert!(ApiError::Database("test".to_string()).is_server_error());
    }

    #[test]
    fn test_error_context() {
        let ctx = ErrorContext::new(
            "req-123".to_string(),
            "/api/test".to_string(),
            "GET".to_string(),
        );
        assert_eq!(ctx.request_id, "req-123");
        assert_eq!(ctx.path, "/api/test");
        assert_eq!(ctx.method, "GET");
        assert!(ctx.user_id.is_none());

        let ctx_with_user = ctx.with_user_id(42);
        assert_eq!(ctx_with_user.user_id, Some(42));
    }
}
