#![cfg(any(feature = "eyre", feature = "anyhow"))]
#![cfg_attr(nightly, feature(error_generic_member_access))]
#![cfg_attr(nightly, allow(clippy::incompatible_msrv))]
#![cfg_attr(all(nightly, feature = "backtrace"), feature(backtrace_frames))]

mod common;

#[cfg(nightly)]
use core::error;

use common::*;
use error_stack::IntoReportCompat as _;
#[cfg(all(nightly, feature = "backtrace"))]
use error_stack::Report;

#[test]
#[cfg(any(nightly, feature = "std"))]
fn error() {
    let report = create_report().change_context(ContextA(10));
    let error_ref = report.as_error();
    assert_eq!(error_ref.to_string(), "context A");
    #[cfg(nightly)]
    assert_eq!(
        *error::request_ref::<u32>(error_ref).expect("requested value not found"),
        10
    );

    let error = report.into_error();
    assert_eq!(error.to_string(), "context A");
    #[cfg(nightly)]
    assert_eq!(
        *error::request_ref::<u32>(&error).expect("requested value not found"),
        10
    );
}

#[test]
#[cfg(all(feature = "std", feature = "anyhow"))]
fn anyhow() {
    let anyhow: Result<(), _> = Err(anyhow::anyhow!(RootError)
        .context(PrintableA(0))
        .context(PrintableB(0)));

    let report = create_report().attach(PrintableA(0)).attach(PrintableB(0));

    #[expect(unused_mut)]
    let mut report_messages = messages(&report);

    let anyhow_report = anyhow
        .into_report()
        .expect_err("should have returned error");
    let anyhow_messages = messages(&anyhow_report);

    assert_eq!(
        remove_builtin_messages(anyhow_messages.into_iter().rev()),
        remove_builtin_messages(report_messages),
    );
}

#[test]
#[cfg(all(not(feature = "std"), feature = "anyhow"))]
fn anyhow_nostd() {
    let anyhow: Result<(), _> = Err(anyhow::anyhow!(RootError)
        .context(PrintableA(0))
        .context(PrintableB(0)));

    let report = anyhow
        .into_report()
        .expect_err("should have returned error");
    let expected_output = ["Location", "printable B"];
    for (anyhow, expected) in messages(&report).into_iter().zip(expected_output) {
        assert_eq!(anyhow, expected);
    }
}

#[test]
#[cfg(all(nightly, feature = "backtrace", feature = "anyhow"))]
fn anyhow_backtrace() {
    let error = anyhow::anyhow!("test error");
    let error_backtrace = error.backtrace();
    let error_backtrace_len = error_backtrace.frames().len();
    #[cfg(not(miri))]
    let error_backtrace_string = error_backtrace.to_string();

    let report: Report<anyhow::Error> = Err::<(), _>(error)
        .into_report()
        .expect_err("should have returned error");
    let report_backtrace = report
        .request_ref::<std::backtrace::Backtrace>()
        .next()
        .expect("No backtrace captured");
    let report_backtrace_len = report_backtrace.frames().len();
    #[cfg(not(miri))]
    let report_backtrace_string = report_backtrace.to_string();

    assert_eq!(error_backtrace_len, report_backtrace_len);
    #[cfg(not(miri))]
    assert_eq!(error_backtrace_string, report_backtrace_string);
}

#[test]
#[cfg(feature = "anyhow")]
fn anyhow_output() {
    let anyhow = anyhow::anyhow!(RootError)
        .context(PrintableA(0))
        .context(PrintableB(0));

    let anyhow_debug_normal = format!("{anyhow:?}");
    let anyhow_debug_extended = format!("{anyhow:#?}");
    let anyhow_display_normal = format!("{anyhow:}");
    let anyhow_display_extended = format!("{anyhow:#}");

    let anyhow_report = Err::<(), _>(anyhow)
        .into_report()
        .expect_err("should have returned error");
    let context = anyhow_report.current_context();

    let context_debug_normal = format!("{context:?}");
    let context_debug_extended = format!("{context:#?}");
    let context_display_normal = format!("{context:}");
    let context_display_extended = format!("{context:#}");

    assert_eq!(anyhow_debug_normal, context_debug_normal);
    assert_eq!(anyhow_debug_extended, context_debug_extended);
    assert_eq!(anyhow_display_normal, context_display_normal);
    assert_eq!(anyhow_display_extended, context_display_extended);
}

#[cfg(feature = "eyre")]
fn install_eyre_hook() {
    use std::sync::Once;

    static ONCE: Once = Once::new();

    ONCE.call_once(|| {
        eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).expect("Could not set hook");
    });
}

#[test]
#[cfg(feature = "eyre")]
#[cfg_attr(
    miri,
    ignore = "bug: miri is failing for `eyre`, this is unrelated to our implementation"
)]
fn eyre() {
    install_eyre_hook();

    let eyre: Result<(), _> = Err(eyre::eyre!(RootError)
        .wrap_err(PrintableA(0))
        .wrap_err(PrintableB(0)));

    let report = create_report().attach(PrintableA(0)).attach(PrintableB(0));
    let report_messages = messages(&report);

    let eyre_report = eyre.into_report().expect_err("should have returned error");
    let eyre_messages = messages(&eyre_report);

    assert_eq!(
        remove_builtin_messages(eyre_messages.into_iter().rev()),
        remove_builtin_messages(report_messages),
    );
}

#[test]
#[cfg(all(nightly, feature = "eyre", feature = "backtrace"))]
#[ignore = "bug: `eyre` currently does not provide a backtrace`"]
fn eyre_backtrace() {
    install_eyre_hook();

    let error = eyre::eyre!("test error");
    let error_backtrace =
        error::request_ref::<std::backtrace::Backtrace>(error.as_ref() as &dyn error::Error)
            .expect("no backtrace captured");
    let error_backtrace_len = error_backtrace.frames().len();
    let error_backtrace_string = error_backtrace.to_string();

    let report: Report<eyre::Error> = Err::<(), _>(error)
        .into_report()
        .expect_err("should have returned error");
    let report_backtrace = report
        .request_ref::<std::backtrace::Backtrace>()
        .next()
        .expect("No backtrace captured");
    let report_backtrace_len = report_backtrace.frames().len();
    let report_backtrace_string = report_backtrace.to_string();

    assert_eq!(error_backtrace_len, report_backtrace_len);
    assert_eq!(error_backtrace_string, report_backtrace_string);
}

#[test]
#[cfg(feature = "eyre")]
#[cfg_attr(
    miri,
    ignore = "bug: miri is failing for `eyre`, this is unrelated to our implementation"
)]
fn eyre_output() {
    install_eyre_hook();

    let eyre = eyre::eyre!(RootError)
        .wrap_err(PrintableA(0))
        .wrap_err(PrintableB(0));

    let eyre_debug_normal = format!("{eyre:?}");
    let eyre_debug_extended = format!("{eyre:#?}");
    let eyre_display_normal = format!("{eyre:}");
    let eyre_display_extended = format!("{eyre:#}");

    let eyre_report = Err::<(), _>(eyre)
        .into_report()
        .expect_err("should have returned error");
    let context = eyre_report.current_context();

    let context_debug_normal = format!("{context:?}");
    let context_debug_extended = format!("{context:#?}");
    let context_display_normal = format!("{context:}");
    let context_display_extended = format!("{context:#}");

    assert_eq!(eyre_debug_normal, context_debug_normal);
    assert_eq!(eyre_debug_extended, context_debug_extended);
    assert_eq!(eyre_display_normal, context_display_normal);
    assert_eq!(eyre_display_extended, context_display_extended);
}
