use serde::Serialize;
use thiserror::Error;

#[derive(Debug, Error)]
#[allow(dead_code)]
pub enum CliError {
    #[error("Not authenticated. Run `bpx setup` to configure your API key.")]
    NotAuthenticated,

    #[error("Invalid symbol '{0}'. Run `bpx markets list` to see available markets.")]
    InvalidSymbol(String),

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

    #[error("Network error: {0}. Check your connection and try again.")]
    Network(String),

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

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

impl From<bpx_api_client::error::Error> for CliError {
    fn from(err: bpx_api_client::error::Error) -> Self {
        match err {
            bpx_api_client::error::Error::NotAuthenticated => CliError::NotAuthenticated,
            _ => CliError::Api(err.to_string()),
        }
    }
}

// ── Structured error codes for agents ──────────────────────────────

#[derive(Debug, Clone, Copy, Serialize)]
pub enum ErrorCode {
    AuthRequired,
    RateLimited,
    InsufficientBalance,
    InvalidSymbol,
    InvalidInput,
    NetworkTimeout,
    NetworkError,
    MarginInsufficient,
    ReduceOnlyViolation,
    SelfTradePrevented,
    OrderNotFound,
    ConfigError,
    Unknown,
}

impl ErrorCode {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::AuthRequired => "AUTH_REQUIRED",
            Self::RateLimited => "RATE_LIMITED",
            Self::InsufficientBalance => "INSUFFICIENT_BALANCE",
            Self::InvalidSymbol => "INVALID_SYMBOL",
            Self::InvalidInput => "INVALID_INPUT",
            Self::NetworkTimeout => "NETWORK_TIMEOUT",
            Self::NetworkError => "NETWORK_ERROR",
            Self::MarginInsufficient => "MARGIN_INSUFFICIENT",
            Self::ReduceOnlyViolation => "REDUCE_ONLY_VIOLATION",
            Self::SelfTradePrevented => "SELF_TRADE_PREVENTED",
            Self::OrderNotFound => "ORDER_NOT_FOUND",
            Self::ConfigError => "CONFIG_ERROR",
            Self::Unknown => "UNKNOWN",
        }
    }

    pub fn category(&self) -> &'static str {
        match self {
            Self::AuthRequired => "auth",
            Self::RateLimited => "network",
            Self::InsufficientBalance | Self::MarginInsufficient => "trading",
            Self::InvalidSymbol | Self::InvalidInput | Self::ConfigError => "input",
            Self::NetworkTimeout | Self::NetworkError => "network",
            Self::ReduceOnlyViolation | Self::SelfTradePrevented | Self::OrderNotFound => "trading",
            Self::Unknown => "system",
        }
    }

    pub fn exit_code(&self) -> i32 {
        match self {
            Self::AuthRequired => 2,
            Self::InvalidSymbol | Self::InvalidInput | Self::ConfigError => 3,
            Self::NetworkTimeout | Self::NetworkError | Self::RateLimited => 4,
            Self::InsufficientBalance
            | Self::MarginInsufficient
            | Self::ReduceOnlyViolation
            | Self::SelfTradePrevented
            | Self::OrderNotFound => 5,
            Self::Unknown => 1,
        }
    }

    pub fn suggestion(&self) -> &'static str {
        match self {
            Self::AuthRequired => "Run `bpx setup` to configure your API key",
            Self::RateLimited => "Wait a moment and retry, or use --retry flag",
            Self::InsufficientBalance => "Check balances with `bpx account balances`",
            Self::InvalidSymbol => "Run `bpx markets list` to see available symbols",
            Self::InvalidInput => "Check command usage with --help",
            Self::NetworkTimeout => "Check your connection, or use --retry flag",
            Self::NetworkError => "Check your connection and try again",
            Self::MarginInsufficient => "Check margin with `bpx portfolio` or reduce position size",
            Self::ReduceOnlyViolation => {
                "This order would increase your position. Remove --reduce-only or adjust quantity"
            }
            Self::SelfTradePrevented => "Your order would match against your own resting order",
            Self::OrderNotFound => "The order may have been filled or already cancelled",
            Self::ConfigError => "Check your config with `bpx profile list`",
            Self::Unknown => "Try again or check `bpx status` for API health",
        }
    }
}

/// Classify an error message into a structured error code.
pub fn classify_error(msg: &str) -> ErrorCode {
    let lower = msg.to_lowercase();

    // Auth
    if lower.contains("not authenticated")
        || lower.contains("api key")
        || lower.contains("unauthorized")
        || lower.contains("401")
    {
        return ErrorCode::AuthRequired;
    }

    // Rate limiting
    if lower.contains("429") || lower.contains("rate limit") || lower.contains("too many") {
        return ErrorCode::RateLimited;
    }

    // Network
    if lower.contains("timeout") || lower.contains("timed out") {
        return ErrorCode::NetworkTimeout;
    }
    if lower.contains("connection")
        || lower.contains("network")
        || lower.contains("dns")
        || lower.contains("502")
        || lower.contains("503")
        || lower.contains("504")
    {
        return ErrorCode::NetworkError;
    }

    // Trading
    if lower.contains("insufficient margin") || lower.contains("margin") {
        return ErrorCode::MarginInsufficient;
    }
    if lower.contains("insufficient") || lower.contains("not enough") {
        return ErrorCode::InsufficientBalance;
    }
    if lower.contains("reduce only") || lower.contains("reduce-only") {
        return ErrorCode::ReduceOnlyViolation;
    }
    if lower.contains("self trade") || lower.contains("self-trade") {
        return ErrorCode::SelfTradePrevented;
    }
    if lower.contains("order not found") || lower.contains("no such order") {
        return ErrorCode::OrderNotFound;
    }

    // Input
    if lower.contains("invalid symbol") {
        return ErrorCode::InvalidSymbol;
    }
    if lower.contains("invalid") || lower.contains("parse") || lower.contains("malformed") {
        return ErrorCode::InvalidInput;
    }

    // Config
    if lower.contains("config") {
        return ErrorCode::ConfigError;
    }

    ErrorCode::Unknown
}
