use crate::error::{TranslateError, TranslateErrorKind};
use crate::fluent_bundle::*;
use crate::translation::Translate;
use crate::FluentBundle;
use rustc_data_structures::sync::{IntoDynSyncSend, Lrc};
use rustc_error_messages::fluent_bundle::resolver::errors::{ReferenceKind, ResolverError};
use rustc_error_messages::langid;
use rustc_error_messages::DiagnosticMessage;

struct Dummy {
    bundle: FluentBundle,
}

impl Translate for Dummy {
    fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
        None
    }

    fn fallback_fluent_bundle(&self) -> &FluentBundle {
        &self.bundle
    }
}

fn make_dummy(ftl: &'static str) -> Dummy {
    let resource = FluentResource::try_new(ftl.into()).expect("Failed to parse an FTL string.");

    let langid_en = langid!("en-US");

    #[cfg(parallel_compiler)]
    let mut bundle: FluentBundle =
        IntoDynSyncSend(crate::fluent_bundle::bundle::FluentBundle::new_concurrent(vec![
            langid_en,
        ]));

    #[cfg(not(parallel_compiler))]
    let mut bundle: FluentBundle =
        IntoDynSyncSend(crate::fluent_bundle::bundle::FluentBundle::new(vec![langid_en]));

    bundle.add_resource(resource).expect("Failed to add FTL resources to the bundle.");

    Dummy { bundle }
}

#[test]
fn wellformed_fluent() {
    let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
    .label = value moved into `{$name}` here
    .occurs_because_label = move occurs because `{$name}` has type `{$ty}` which does not implement the `Copy` trait
    .value_borrowed_label = value borrowed here after move
    .suggestion = borrow this binding in the pattern to avoid moving the value");

    let mut args = FluentArgs::new();
    args.set("name", "Foo");
    args.set("ty", "std::string::String");
    {
        let message = DiagnosticMessage::FluentIdentifier(
            "mir_build_borrow_of_moved_value".into(),
            Some("suggestion".into()),
        );

        assert_eq!(
            dummy.translate_message(&message, &args).unwrap(),
            "borrow this binding in the pattern to avoid moving the value"
        );
    }

    {
        let message = DiagnosticMessage::FluentIdentifier(
            "mir_build_borrow_of_moved_value".into(),
            Some("value_borrowed_label".into()),
        );

        assert_eq!(
            dummy.translate_message(&message, &args).unwrap(),
            "value borrowed here after move"
        );
    }

    {
        let message = DiagnosticMessage::FluentIdentifier(
            "mir_build_borrow_of_moved_value".into(),
            Some("occurs_because_label".into()),
        );

        assert_eq!(
            dummy.translate_message(&message, &args).unwrap(),
            "move occurs because `\u{2068}Foo\u{2069}` has type `\u{2068}std::string::String\u{2069}` which does not implement the `Copy` trait"
        );

        {
            let message = DiagnosticMessage::FluentIdentifier(
                "mir_build_borrow_of_moved_value".into(),
                Some("label".into()),
            );

            assert_eq!(
                dummy.translate_message(&message, &args).unwrap(),
                "value moved into `\u{2068}Foo\u{2069}` here"
            );
        }
    }
}

#[test]
fn misformed_fluent() {
    let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
    .label = value moved into `{name}` here
    .occurs_because_label = move occurs because `{$oops}` has type `{$ty}` which does not implement the `Copy` trait
    .suggestion = borrow this binding in the pattern to avoid moving the value");

    let mut args = FluentArgs::new();
    args.set("name", "Foo");
    args.set("ty", "std::string::String");
    {
        let message = DiagnosticMessage::FluentIdentifier(
            "mir_build_borrow_of_moved_value".into(),
            Some("value_borrowed_label".into()),
        );

        let err = dummy.translate_message(&message, &args).unwrap_err();
        assert!(
            matches!(
                &err,
                TranslateError::Two {
                    primary: box TranslateError::One {
                        kind: TranslateErrorKind::PrimaryBundleMissing,
                        ..
                    },
                    fallback: box TranslateError::One {
                        kind: TranslateErrorKind::AttributeMissing { attr: "value_borrowed_label" },
                        ..
                    }
                }
            ),
            "{err:#?}"
        );
        assert_eq!(
            format!("{err}"),
            "failed while formatting fluent string `mir_build_borrow_of_moved_value`: \nthe attribute `value_borrowed_label` was missing\nhelp: add `.value_borrowed_label = <message>`\n"
        );
    }

    {
        let message = DiagnosticMessage::FluentIdentifier(
            "mir_build_borrow_of_moved_value".into(),
            Some("label".into()),
        );

        let err = dummy.translate_message(&message, &args).unwrap_err();
        if let TranslateError::Two {
            primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
            fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
        } = &err
            && let [FluentError::ResolverError(ResolverError::Reference(
                ReferenceKind::Message { id, .. }
                    | ReferenceKind::Variable { id, .. },
            ))] = &**errs
            && id == "name"
        {} else {
            panic!("{err:#?}")
        };
        assert_eq!(
            format!("{err}"),
            "failed while formatting fluent string `mir_build_borrow_of_moved_value`: \nargument `name` exists but was not referenced correctly\nhelp: try using `{$name}` instead\n"
        );
    }

    {
        let message = DiagnosticMessage::FluentIdentifier(
            "mir_build_borrow_of_moved_value".into(),
            Some("occurs_because_label".into()),
        );

        let err = dummy.translate_message(&message, &args).unwrap_err();
        if let TranslateError::Two {
            primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
            fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
        } = &err
            && let [FluentError::ResolverError(ResolverError::Reference(
                ReferenceKind::Message { id, .. }
                    | ReferenceKind::Variable { id, .. },
            ))] = &**errs
            && id == "oops"
        {} else {
            panic!("{err:#?}")
        };
        assert_eq!(
            format!("{err}"),
            "failed while formatting fluent string `mir_build_borrow_of_moved_value`: \nthe fluent string has an argument `oops` that was not found.\nhelp: the arguments `name` and `ty` are available\n"
        );
    }
}
