//! Comprehensive error handling for Falcorn
//!
//! This module provides typed error handling with detailed context
//! for all failure scenarios in the application.

use std::io;
use thiserror::Error;

/// Main error type for Falcorn operations
#[derive(Error, Debug)]
pub enum FalcornError {
    // I/O Errors
    #[error("IO error: {0}")]
    IoError(#[from] io::Error),

    // HTTP Parsing Errors
    #[error("HTTP parse error: {reason}")]
    HttpParseError { reason: String },

    #[error("Invalid HTTP method: {method}")]
    InvalidHttpMethod { method: String },

    #[error("Invalid HTTP version: {version}")]
    InvalidHttpVersion { version: String },

    #[error("Missing required header: {header}")]
    MissingHeader { header: String },

    #[error("Invalid header value: {reason}")]
    InvalidHeaderValue { reason: String },

    #[error("Payload too large: limit={limit} bytes")]
    PayloadTooLarge { limit: usize },

    // Python Integration Errors
    #[error("Python error: {message}")]
    PythonError { message: String },

    #[error("Python app not found: module='{module}', callable='{callable}'")]
    PythonAppNotFound { module: String, callable: String },

    #[error("Python import error: {reason}")]
    PythonImportError { reason: String },

    #[error("WSGI interface not supported")]
    WsgiInterfaceError,

    #[error("Python type conversion error: {reason}")]
    PythonTypeError { reason: String },

    // Configuration Errors
    #[error("Configuration error: {reason}")]
    ConfigError { reason: String },

    #[error("Invalid worker class: {class}. Must be 'sync' or 'async'")]
    InvalidWorkerClass { class: String },

    #[error("Invalid configuration: {reason}")]
    InvalidConfig { reason: String },

    // Runtime Errors
    #[error("Runtime error: {reason}")]
    RuntimeError { reason: String },

    #[error("Worker panic: worker_id={worker_id}, message={message}")]
    WorkerPanicError { worker_id: u32, message: String },

    #[error("Failed to bind to {address}: {reason}")]
    BindError { address: String, reason: String },

    #[error("Failed to start worker: {reason}")]
    WorkerStartError { reason: String },

    // Timeout Errors
    #[error("Request timeout after {seconds}s")]
    RequestTimeout { seconds: u64 },

    #[error("Worker timeout: worker_id={worker_id}")]
    WorkerTimeout { worker_id: u32 },

    // Connection Errors
    #[error("Connection error: {reason}")]
    ConnectionError { reason: String },

    #[error("Max connections reached: {limit}")]
    MaxConnectionsReached { limit: usize },

    /// ASGI interface error
    #[error(
        "ASGI interface error: application must be an async callable accepting (scope, receive, send)"
    )]
    AsgiInterfaceError,

    /// ASGI protocol error
    #[error("ASGI protocol error: {reason}")]
    AsgiProtocolError { reason: String },

    /// ASGI application error
    #[error("ASGI application error: {reason}")]
    AsgiApplicationError { reason: String },

    /// ASGI timeout
    #[error("ASGI request timeout after {seconds} seconds")]
    AsgiTimeout { seconds: u64 },

    // Serialization Errors
    #[error("Serialization error: {0}")]
    SerializationError(#[from] serde_json::Error),

    #[error("TOML error: {0}")]
    TomlError(#[from] toml::de::Error),

    // Generic errors
    #[error("Internal server error: {reason}")]
    InternalError { reason: String },

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

/// Result type alias for Falcorn
pub type Result<T> = std::result::Result<T, FalcornError>;

// Conversion implementations

impl From<http::Error> for FalcornError {
    fn from(err: http::Error) -> Self {
        Self::HttpParseError {
            reason: err.to_string(),
        }
    }
}

impl From<http::header::InvalidHeaderName> for FalcornError {
    fn from(err: http::header::InvalidHeaderName) -> Self {
        Self::InvalidHeaderValue {
            reason: format!("Invalid header name: {}", err),
        }
    }
}

impl From<http::header::InvalidHeaderValue> for FalcornError {
    fn from(err: http::header::InvalidHeaderValue) -> Self {
        Self::InvalidHeaderValue {
            reason: format!("Invalid header value: {}", err),
        }
    }
}

// PyO3 error conversion
impl From<pyo3::PyErr> for FalcornError {
    fn from(err: pyo3::PyErr) -> Self {
        use pyo3::Python;

        Python::attach(|py| {
            let err_str = err.to_string();

            // Check error type
            if err.is_instance_of::<pyo3::exceptions::PyImportError>(py) {
                Self::PythonImportError { reason: err_str }
            } else if err.is_instance_of::<pyo3::exceptions::PyTypeError>(py)
                || err.is_instance_of::<pyo3::exceptions::PyValueError>(py)
            {
                Self::PythonTypeError { reason: err_str }
            } else {
                Self::PythonError { message: err_str }
            }
        })
    }
}

// impl From<pyo3::err::CastError> for FalcornError {
//     fn from(err: pyo3::CastError) -> Self {
//         Self::PythonTypeError {
//             reason: format!("Python type cast error: {}", err),
//         }
//     }
// }

impl From<tokio::task::JoinError> for FalcornError {
    fn from(err: tokio::task::JoinError) -> Self {
        Self::RuntimeError {
            reason: format!("Task join error: {}", err),
        }
    }
}

impl From<std::num::ParseIntError> for FalcornError {
    fn from(err: std::num::ParseIntError) -> Self {
        Self::ConfigError {
            reason: format!("Integer parse error: {}", err),
        }
    }
}

impl From<std::string::FromUtf8Error> for FalcornError {
    fn from(err: std::string::FromUtf8Error) -> Self {
        Self::HttpParseError {
            reason: format!("UTF-8 conversion error: {}", err),
        }
    }
}

impl From<std::str::Utf8Error> for FalcornError {
    fn from(err: std::str::Utf8Error) -> Self {
        Self::HttpParseError {
            reason: format!("UTF-8 error: {}", err),
        }
    }
}

impl From<Box<dyn std::error::Error + Send + Sync>> for FalcornError {
    fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
        Self::InternalError {
            reason: err.to_string(),
        }
    }
}

impl From<Box<dyn std::error::Error>> for FalcornError {
    fn from(err: Box<dyn std::error::Error>) -> Self {
        Self::InternalError {
            reason: err.to_string(),
        }
    }
}

impl FalcornError {
    /// Create a custom error message
    pub fn custom<S: Into<String>>(msg: S) -> Self {
        Self::Custom(msg.into())
    }

    /// Create an HTTP parse error
    pub fn http_parse<S: Into<String>>(reason: S) -> Self {
        Self::HttpParseError {
            reason: reason.into(),
        }
    }

    /// Create a configuration error
    pub fn config<S: Into<String>>(reason: S) -> Self {
        Self::ConfigError {
            reason: reason.into(),
        }
    }

    /// Create a runtime error
    pub fn runtime<S: Into<String>>(reason: S) -> Self {
        Self::RuntimeError {
            reason: reason.into(),
        }
    }

    /// Create a Python error
    pub fn python<S: Into<String>>(message: S) -> Self {
        Self::PythonError {
            message: message.into(),
        }
    }

    pub fn asgi_protocol(reason: impl Into<String>) -> Self {
        Self::AsgiProtocolError {
            reason: reason.into(),
        }
    }

    pub fn asgi_app(reason: impl Into<String>) -> Self {
        Self::AsgiApplicationError {
            reason: reason.into(),
        }
    }

    /// Get HTTP status code for this error
    pub fn status_code(&self) -> u16 {
        match self {
            Self::HttpParseError { .. } => 400,
            Self::InvalidHttpMethod { .. } => 405,
            Self::InvalidHttpVersion { .. } => 505,
            Self::MissingHeader { .. } => 400,
            Self::InvalidHeaderValue { .. } => 400,
            Self::PayloadTooLarge { .. } => 413,
            Self::RequestTimeout { .. } => 408,
            Self::MaxConnectionsReached { .. } => 503,
            _ => 500,
        }
    }

    /// Check if this is a recoverable error
    pub fn is_recoverable(&self) -> bool {
        matches!(
            self,
            Self::HttpParseError { .. }
                | Self::RequestTimeout { .. }
                | Self::InvalidHttpMethod { .. }
                | Self::InvalidHttpVersion { .. }
                | Self::MissingHeader { .. }
                | Self::PayloadTooLarge { .. }
                | Self::ConnectionError { .. }
                | Self::MaxConnectionsReached { .. }
        ) || matches!(self, Self::IoError(err) if is_recoverable_io_error(err))
    }
}

fn is_recoverable_io_error(err: &io::Error) -> bool {
    use io::ErrorKind::*;

    matches!(
        err.kind(),
        WouldBlock
            | TimedOut
            | Interrupted
            | ConnectionReset
            | ConnectionAborted
            | BrokenPipe
            | UnexpectedEof
            | NotConnected
    )
}
