//! Error handling, and communicating failures across the JNI boundary.

use jni::JNIEnv;
use onion_tunnel::errors::{ArtiError, ArtiErrorKind, HasKind};
use onion_tunnel::tor_config::BridgeParseError;
use std::fmt::{Display, Formatter};

/// An error for 'the proxy was not running but you tried to interact with it like it was'.
///
/// This exists so we can propagate this case back to the Java side, since it might be important:
/// we want Java to be aware that (for example) they failed to refresh circuits or whatever.
#[derive(Debug)]
pub struct ProxyStoppedError;

impl Display for ProxyStoppedError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Attempted to do something with a stopped proxy")
    }
}

impl std::error::Error for ProxyStoppedError {}

/// An error for 'the country code you tried to use is invalid'.
///
/// This exists so we can propagate this case back to the Java side.
#[derive(Debug)]
pub struct CountryCodeError {
    pub(crate) code: String,
}

impl Display for CountryCodeError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Country code is invalid: {}", self.code)
    }
}

impl std::error::Error for CountryCodeError {}

/// Throws a Java exception corresponding to the provided error.
///
/// This will attempt to find an error type in the error's chain that corresponds to one of the
/// more specific Java exception types. If this fails, it'll just throw a generic
/// `OnionmasqException`.
pub fn throw_java_exception_for(env: &mut JNIEnv, e: anyhow::Error) -> jni::errors::Result<()> {
    // Using the 'debug' formatter results in a complete list of error causes according to
    // the `anyhow` docs.
    let formatted = format!("{e:?}");

    for e in e.chain() {
        if e.is::<jni::errors::Error>() {
            // To be honest I suspect this kind of error wouldn't even get to this stage, but
            // whatever.
            return env.throw_new(
                "org/torproject/onionmasq/errors/JniMisuseException",
                formatted,
            );
        }
        if e.is::<ProxyStoppedError>() {
            return env.throw_new(
                "org/torproject/onionmasq/errors/ProxyStoppedException",
                formatted,
            );
        }
        if e.is::<CountryCodeError>() {
            return env.throw_new(
                "org/torproject/onionmasq/errors/CountryCodeException",
                formatted,
            );
        }
        if e.is::<BridgeParseError>() {
            return env.throw_new(
                "org/torproject/onionmasq/errors/BridgelineException",
                formatted,
            );
        }
        if let Some(arti_e) = e.downcast_ref::<ArtiError>() {
            return match arti_e.kind() {
                ArtiErrorKind::CacheAccessFailed
                | ArtiErrorKind::CacheCorrupted
                | ArtiErrorKind::PersistentStateAccessFailed
                | ArtiErrorKind::PersistentStateCorrupted => env.throw_new(
                    "org/torproject/onionmasq/errors/ArtiStateException",
                    formatted,
                ),
                ArtiErrorKind::TorAccessFailed | ArtiErrorKind::LocalNetworkError => env.throw_new(
                    "org/torproject/onionmasq/errors/TorAccessException",
                    formatted,
                ),
                _ => env.throw_new("org/torproject/onionmasq/errors/ArtiException", formatted),
            };
        }
    }
    env.throw_new(
        "org/torproject/onionmasq/errors/OnionmasqException",
        formatted,
    )
}
