//! Error types for the scan client.

use thiserror::Error;
use tower::BoxError;

/// Type alias for HTTP client operations.
pub type Result<T> = std::result::Result<T, Error>;

/// Errors that can occur during HTTP client instantiation or execution.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    /// Failed to configure or build the reqwest client internally.
    #[error(
        "could not build HTTP client: {0}. Fix: verify `proxy`, `custom_headers`, TLS settings, and timeout values in `HttpConfig`."
    )]
    ClientBuild(#[source] reqwest::Error),
    /// Failed to initialise the DNS resolver.
    ///
    /// Only returned by [`super::client::ScanClient::from_config`], which builds
    /// an ephemeral system resolver. In production code prefer
    /// [`super::client::ScanClient::new`] with a pre-built `TokioResolver` so
    /// resolver construction errors surface at application startup rather than
    /// per-request.
    #[error(
        "could not initialise DNS resolver: {message}. Fix: ensure the system \
         resolver is reachable, or use ScanClient::new with a shared TokioResolver."
    )]
    ResolverInit {
        /// Human-readable description of the resolver error.
        message: String,
    },
    /// Proxy string provided was invalid or unparseable.
    #[error(
        "invalid proxy URL `{0}`. Fix: use a full proxy URL such as `http://127.0.0.1:8080` or `socks5h://127.0.0.1:9050`."
    )]
    InvalidProxy(String),
    /// TLS verification settings are contradictory or insecure without explicit opt-in.
    #[error(
        "invalid TLS configuration. Fix: keep `tls_verify = true`, or explicitly set `tls_accept_invalid_certs` and/or `tls_accept_invalid_hostnames` when intentionally connecting to untrusted endpoints."
    )]
    InvalidTlsConfig,
    /// Timeout configuration is invalid.
    #[error("invalid `{field}` value `{value}`. Fix: use a value between 1 and {max} seconds.")]
    InvalidTimeout {
        /// The invalid field name.
        field: &'static str,
        /// The invalid value.
        value: u64,
        /// The maximum accepted value.
        max: u64,
    },
    /// Retry delay configuration is invalid.
    #[error("invalid retry delay `{value}`ms. Fix: use a value between 1ms and {max}ms.")]
    InvalidRetryDelay {
        /// The invalid retry delay.
        value: u64,
        /// The maximum accepted value.
        max: u64,
    },
    /// Maximum response body size is invalid.
    #[error("invalid `max_body_size` value `{value}`. Fix: use a value greater than 0 bytes.")]
    InvalidMaxBodySize {
        /// The invalid maximum response size.
        value: usize,
    },
    /// User agent configuration is invalid.
    #[error("invalid user agent. Fix: provide a non-empty HTTP-safe User-Agent string.")]
    InvalidUserAgent,
    /// Proxy authentication fields are inconsistent.
    #[error(
        "invalid proxy authentication configuration. Fix: set `proxy` when using `proxy_user`/`proxy_password`, and provide `proxy_user` whenever `proxy_password` is set."
    )]
    InvalidProxyAuth,
    /// Custom header name contained illegal characters.
    #[error(
        "invalid header name `{name}`. Fix: use an ASCII HTTP header name such as `X-Trace-Id`."
    )]
    InvalidHeaderName {
        /// The invalid header name.
        name: String,
    },
    /// Custom header value contained illegal characters.
    #[error(
        "invalid header value for `{name}`. Fix: remove newlines and non-header-safe bytes from the configured value."
    )]
    InvalidHeaderValue {
        /// The invalid header value's key name.
        name: String,
    },
    /// Rate limiter configuration encountered an error.
    #[error(
        "rate limit configuration failed: {0}. Fix: create the client inside a Tokio runtime and use a positive `rate_limit_per_sec` value."
    )]
    RateLimiter(BoxError),
    /// A required request could not be cloned for a retry attempt.
    #[error(
        "request could not be cloned for retry. Fix: avoid streaming bodies for retried requests, or set `max_retries = 0` for one-shot uploads."
    )]
    UnclonableRequest,
    /// The maximum number of retry attempts was exhausted.
    #[error(
        "request still failed after all retries. Fix: inspect the source error, increase `max_retries`, or reduce concurrency against the upstream service."
    )]
    RetryExhausted {
        /// The final underlying network or timeout error.
        #[source]
        source: reqwest::Error,
    },
    /// The response body exceeded the configured in-memory limit.
    #[error(
        "response body exceeded the configured limit of {limit} bytes after reading {observed} bytes. Fix: increase `max_body_size` for this target or stream the body outside `scanclient`."
    )]
    ResponseBodyTooLarge {
        /// Configured maximum allowed body size.
        limit: usize,
        /// Observed byte count at rejection time.
        observed: u64,
    },
    /// Redirect target points at a private or otherwise disallowed address.
    #[error(
        "redirect to `{url}` was blocked. Fix: allow only public redirect targets or disable redirects for untrusted flows."
    )]
    RedirectBlocked {
        /// The blocked redirect URL.
        url: String,
    },
    /// Maximum redirects exceeded for a request.
    #[error(
        "maximum redirects ({max}) exceeded for `{url}`. Fix: increase `max_redirects` or handle redirects manually."
    )]
    RedirectLimitExceeded {
        /// The URL that exceeded the redirect limit.
        url: String,
        /// The configured maximum number of redirects.
        max: usize,
    },
    /// Redirect target hostname could not be resolved safely.
    #[error(
        "could not resolve redirect target `{host}`: {source}. Fix: verify the hostname resolves publicly or disable redirects for this target."
    )]
    RedirectResolution {
        /// The hostname that failed DNS resolution.
        host: String,
        /// The underlying lookup error.
        #[source]
        source: std::io::Error,
    },
    /// Redirect location header was malformed.
    #[error(
        "invalid redirect location `{location}`. Fix: ensure the upstream sends a valid absolute or relative Location header."
    )]
    InvalidRedirectLocation {
        /// The malformed location value.
        location: String,
    },
    /// A generalized request error from reqwest.
    #[error(transparent)]
    Request(#[from] reqwest::Error),
}
