use std::error::Error;
use std::fmt;

use crate::frame::Compression;
use crate::frame::Encoding;

/// Core result type used across the qroissant core crate.
pub type CoreResult<T> = Result<T, CoreError>;

/// Errors produced by low-level q IPC frame handling.
#[derive(Debug)]
pub enum CoreError {
    InvalidEncoding(u8),
    InvalidMessageType(u8),
    InvalidCompression(u8),
    InvalidAttribute(i8),
    InvalidTypeCode(i8),
    InvalidMessageLength(usize),
    InvalidCollectionLength(i32),
    InvalidStructure(String),
    TruncatedHeader { actual: usize },
    FrameLengthMismatch { declared: usize, actual: usize },
    TrailingBodyBytes(usize),
    UnsupportedEndianness(Encoding),
    UnsupportedCompression(Compression),
    UnsupportedTypeCode(i8),
    LengthOverflow(usize),
    Io(std::io::Error),
    QRuntime(String),
}

impl fmt::Display for CoreError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidEncoding(value) => write!(
                f,
                "invalid q IPC encoding value {value}; expected 0 (big-endian) or 1 (little-endian)"
            ),
            Self::InvalidMessageType(value) => write!(
                f,
                "invalid q IPC message type value {value}; expected 0 (asynchronous), 1 (synchronous), or 2 (response)"
            ),
            Self::InvalidCompression(value) => write!(
                f,
                "invalid q IPC compression value {value}; expected 0 (uncompressed), 1 (compressed), or 2 (compressed large)"
            ),
            Self::InvalidAttribute(value) => write!(
                f,
                "invalid q attribute value {value}; expected 0 (none), 1 (sorted), 2 (unique), 3 (parted), or 4 (grouped)"
            ),
            Self::InvalidTypeCode(value) => write!(f, "invalid q IPC type code {value}"),
            Self::InvalidMessageLength(length) => {
                write!(
                    f,
                    "invalid q IPC message length {length}; minimum is 8 bytes"
                )
            }
            Self::InvalidCollectionLength(length) => {
                write!(
                    f,
                    "invalid q collection length {length}; length must be non-negative"
                )
            }
            Self::InvalidStructure(message) => write!(f, "{message}"),
            Self::TruncatedHeader { actual } => write!(
                f,
                "truncated q IPC header: expected 8 bytes, received {actual}"
            ),
            Self::FrameLengthMismatch { declared, actual } => write!(
                f,
                "q IPC header declares {declared} bytes, but frame contains {actual}"
            ),
            Self::TrailingBodyBytes(remaining) => write!(
                f,
                "q IPC body contains {remaining} trailing bytes after the decoded value"
            ),
            Self::UnsupportedEndianness(encoding) => write!(
                f,
                "serialization currently supports only little-endian q IPC frames, got {encoding:?}"
            ),
            Self::UnsupportedCompression(compression) => write!(
                f,
                "serialization currently supports only uncompressed q IPC frames, got {compression:?}"
            ),
            Self::UnsupportedTypeCode(value) => write!(
                f,
                "q IPC type code {value} is valid but not implemented yet in the current decoder"
            ),
            Self::LengthOverflow(length) => write!(
                f,
                "q IPC frame length {length} exceeds 32-bit header capacity"
            ),
            Self::Io(error) => error.fmt(f),
            Self::QRuntime(message) => write!(f, "q runtime error: {message}"),
        }
    }
}

impl Error for CoreError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Io(error) => Some(error),
            _ => None,
        }
    }
}

impl From<std::io::Error> for CoreError {
    fn from(value: std::io::Error) -> Self {
        Self::Io(value)
    }
}
