package org.jboss.resteasy.test.cdi.injection.resource;

import java.util.HashMap;
import java.util.logging.Logger;

import jakarta.annotation.PostConstruct;
import jakarta.ejb.EJB;
import jakarta.ejb.Stateful;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;

import org.jboss.resteasy.test.cdi.util.Utilities;

@Stateful
@RequestScoped
public class ReverseInjectionEJBHolder implements ReverseInjectionEJBHolderRemote, ReverseInjectionEJBHolderLocal {
    public static final String SLE = "sle";
    public static final String SFDE = "sfde";
    public static final String SFRE = "sfre";
    public static final String SFAE = "sfae";
    public static final String SLI = "sli";
    public static final String SFDI = "sfdi";
    public static final String SFRI = "sfri";
    public static final String SFAI = "sfai";
    public static final String SLI_SECRET = "sliSecret";
    public static final String SFDI_SECRET = "sfdiSecret";
    public static final String SFRI_SECRET = "sfriSecret";
    public static final String SFAI_SECRET = "sfaiSecret";

    private static HashMap<String, Object> store = new HashMap<String, Object>();

    @Inject
    private Logger log;
    @Inject
    private Utilities utilities;
    @Inject
    int secret;

    @EJB
    private StatelessEJBwithJaxRsComponentsInterface sle;
    @EJB
    private StatefulDependentScopedEJBwithJaxRsComponentsInterface sfde;
    @EJB
    private StatefulRequestScopedEJBwithJaxRsComponentsInterface sfre;
    @EJB
    private StatefulApplicationScopedEJBwithJaxRsComponentsInterface sfae;

    @Inject
    private StatelessEJBwithJaxRsComponentsInterface sli;
    @Inject
    private StatefulDependentScopedEJBwithJaxRsComponentsInterface sfdi;
    @Inject
    private StatefulRequestScopedEJBwithJaxRsComponentsInterface sfri;
    @Inject
    private StatefulApplicationScopedEJBwithJaxRsComponentsInterface sfai;

    @Inject
    private CDIInjectionBookResource resource;

    @PostConstruct
    public void postConstruct() {
        log.info(this + " secret: " + secret);
    }

    @Override
    public boolean testScopes() {
        log.info("");
        log.info("entering ReverseInjectionEJBHolder.testScopes()");
        log.info("resource scope:                                                 "
                + utilities.getScope(CDIInjectionBookResource.class));
        log.info("ReverseInjectionEJBHolder scope:                                                "
                + utilities.getScope(ReverseInjectionEJBHolder.class));
        log.info("ReverseInjectionEJBHolderLocal scope:                                           "
                + utilities.getScope(ReverseInjectionEJBHolderLocal.class));
        log.info("ReverseInjectionEJBHolderRemote scope:                                          "
                + utilities.getScope(ReverseInjectionEJBHolderRemote.class));
        log.info("StatelessEJBwithJaxRsComponents scope:                          "
                + utilities.getScope(StatelessEJBwithJaxRsComponents.class));
        log.info("StatelessEJBwithJaxRsComponentsInterface scope:                 "
                + utilities.getScope(StatelessEJBwithJaxRsComponentsInterface.class));
        log.info("StatefulDependentScopedEJBwithJaxRsComponents scope:            "
                + utilities.getScope(StatefulDependentScopedEJBwithJaxRsComponents.class));
        log.info("StatefulDependentScopedEJBwithJaxRsComponentsInterface scope:   "
                + utilities.getScope(StatefulDependentScopedEJBwithJaxRsComponentsInterface.class));
        log.info("StatefulRequestScopedEJBwithJaxRsComponents scope:              "
                + utilities.getScope(StatefulRequestScopedEJBwithJaxRsComponents.class));
        log.info("StatefulRequestScopedEJBwithJaxRsComponentsInterface scope:     "
                + utilities.getScope(StatefulRequestScopedEJBwithJaxRsComponentsInterface.class));
        log.info("StatefulApplicationScopedEJBwithJaxRsComponents scope:          "
                + utilities.getScope(StatefulApplicationScopedEJBwithJaxRsComponents.class));
        log.info("StatefulApplicationScopedEJBwithJaxRsComponentsInterface scope: "
                + utilities.getScope(StatefulApplicationScopedEJBwithJaxRsComponentsInterface.class));

        return utilities.isDependentScoped(StatelessEJBwithJaxRsComponentsInterface.class) &&
                utilities.isDependentScoped(StatefulDependentScopedEJBwithJaxRsComponentsInterface.class) &&
                utilities.isRequestScoped(StatefulRequestScopedEJBwithJaxRsComponentsInterface.class) &&
                utilities.isApplicationScoped(StatefulApplicationScopedEJBwithJaxRsComponentsInterface.class);
    }

    @Override
    public void setup() {
        log.info("");
        log.info("entering ReverseInjectionEJBHolder.setup()");
        resource.getSet().add(new CDIInjectionBook("Disappearing Book"));
        store.put("sle", sle);
        store.put("sfde", sfde);
        store.put("sfre", sfre);
        store.put("sfae", sfae);
        store.put("sli", sli);
        store.put("sfdi", sfdi);
        store.put("sfri", sfri);
        store.put("sfai", sfai);
        store.put("sli.secret", sli.theSecret());
        store.put("sfdi.secret", sfdi.theSecret());
        store.put("sfri.secret", sfri.theSecret());
        store.put("sfai.secret", sfai.theSecret());

        sleSetup();
        sfdeSetup();
        sfreSetup();
        sfaeSetup();

        sliSetup();
        sfdiSetup();
        sfriSetup();
        sfaiSetup();
    }

    @Override
    public boolean test() {
        log.info("");
        log.info("entering ReverseInjectionEJBHolder.test()");

        boolean result = true;
        result &= resource.getSet().isEmpty();

        // @TODO inject singleton
        // @TODO inject by setter method
        result &= store.get("sle").equals(sle); // EJB spec 3.4.7.1
        result &= !store.get("sfde").equals(sfde); // EJB spec 3.4.7.2, 16.2.1
        result &= !store.get("sfre").equals(sfre); // EJB spec 3.4.7.2, 16.2.1
        result &= !store.get("sfae").equals(sfae); // EJB spec 3.4.7.2, 16.2.1
        //
        result &= !(store.get("sle") == sle); // EJB spec 16.2.1
        result &= !(store.get("sfde") == sfde); // EJB spec 16.2.1
        result &= !(store.get("sfre") == sfre); // EJB spec 16.2.1
        result &= !(store.get("sfae") == sfae); // EJB spec 16.2.1

        // Unlike the EJB spec, the CDI spec does not explicitly specify the semantics of the equality or inequality
        // of injected objects.  In fact, it explicitly forbids calling the java.lang.Object.equals() function on injected
        // objects [CDI spec, section 5.4.2].  It does specify that the first reference to an injectible object in a given
        // context should result in a new contextual reference or a new contextual instance [CDI spec, section 6.5.3],
        // but it doesn't seem to specify the precise semantics of "new".  In the weld reference implementation,
        // there seem to be three variations, at least with respect to injected EJBs:
        //
        // 1. It could be a new proxy for an existing object, or
        // 2. a new proxy for a new object, or
        // 3. a reused proxy for a new object.
        //
        // In this test, we find that
        //
        // 1. An SLSB (which necessarily has dependent pseudo-scope) is treated according to case 1.
        // 2. An SFSB with dependent scope is treated according to case 2.
        // 3. An SFSB with request scope is treated according to case 3.
        //
        // This behavior seems to be consistent with the semantics of EJBs:
        //
        // 1. All instances of a given SLSB class are considered equal, and SLSB target objects are reused (case 1).
        // 2. All instances of a given SFSB class are considered unequal, and SFSBs are always recreated (cases 2 and 3).
        //
        // For this test, we consider inequality, in cases where we expect a new object in a new scope, to mean
        //
        // 1. a new proxy for SLSBs, and
        // 2. a new target object for SFSBs.
        //
        // N.B.  If an SLSB is reused, and it is a contextual object (i.e., created by CDI), then, though some fields might
        //       remain the same, all fields annotated with @Inject should be processed accordingly.
        //
        log.info("sli:  == stored sli:  " + (sli == store.get("sli")));
        log.info("sfdi: == stored sfdi: " + (sfdi == store.get("sfdi")));
        log.info("sfri: == stored sfri: " + (sfri == store.get("sfri")));
        log.info("sfai: == stored sfai: " + (sfai == store.get("sfai")));

        log.info("sli.secret:  == stored sli.secret:  " + (sli.theSecret() == Integer.class.cast(store.get("sli.secret"))));
        log.info("sfdi.secret: == stored sfdi.secret: " + (sfdi.theSecret() == Integer.class.cast(store.get("sfdi.secret"))));
        log.info("sfri.secret: == stored sfri.secret: " + (sfri.theSecret() == Integer.class.cast(store.get("sfri.secret"))));
        log.info("sfai.secret: == stored sfai.secret: " + (sfai.theSecret() == Integer.class.cast(store.get("sfai.secret"))));

        result &= (sli != store.get("sli"));
        result &= (sfdi.theSecret() != Integer.class.cast(store.get("sfdi.secret")));
        result &= (sfri.theSecret() != Integer.class.cast(store.get("sfri.secret")));

        // The CDI spec requires that a single application scoped object of a given class should exist for the
        // lifetime of the application.  It seems reasonable to expect
        //
        // 1. that a proxy for that object obtained by @Inject should be reused throughout the lifetime of the application, and
        // 2. that the target of that proxy should remain the same throughout the lifetime of the application.
        result &= (sfai == store.get("sfai") && sfai.theSecret() == Integer.class.cast(store.get("sfai.secret")));

        result &= sleTest();
        result &= sfdeTest();
        result &= sfreTest();
        result &= sfaeTest();

        result &= sliTest();
        result &= sfdiTest();
        result &= sfriTest();
        result &= sfaiTest();

        return result;
    }

    @Override
    public void sleSetup() {
        sle.setUp(SLE);
    }

    @Override
    public boolean sleTest() {
        return sle.test(SLE);
    }

    @Override
    public void sfdeSetup() {
        sfde.setUp(SFDE);
    }

    @Override
    public boolean sfdeTest() {
        return sfde.test(SFDE);
    }

    @Override
    public void sfreSetup() {
        sfre.setUp(SFRE);
    }

    @Override
    public boolean sfreTest() {
        return sfre.test(SFRE);
    }

    @Override
    public void sfaeSetup() {
        sfae.setUp(SFAE);
    }

    @Override
    public boolean sfaeTest() {
        return sfae.test(SFAE);
    }

    @Override
    public void sliSetup() {
        sli.setUp(SLI);
    }

    @Override
    public boolean sliTest() {
        return sli.test(SLI);
    }

    @Override
    public void sfdiSetup() {
        sfdi.setUp(SFDI);
    }

    @Override
    public boolean sfdiTest() {
        return sfdi.test(SFDI);
    }

    @Override
    public void sfriSetup() {
        sfri.setUp(SFRI);
    }

    @Override
    public boolean sfriTest() {
        return sfri.test(SFRI);
    }

    @Override
    public void sfaiSetup() {
        sfai.setUp(SFAI);
    }

    @Override
    public boolean sfaiTest() {
        return sfai.test(SFAI);
    }

    @Override
    public boolean theSame(ReverseInjectionEJBHolderLocal that) {
        log.info("this secret: " + secret);
        log.info("that secret: " + that.theSecret());
        return this.secret == that.theSecret();
    }

    @Override
    public int theSecret() {
        return secret;
    }
}
