package nl.jqno.equalsverifier.integration.extra_features;

import jakarta.persistence.Basic;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import java.util.Arrays;
import java.util.Objects;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import nl.jqno.equalsverifier.internal.testhelpers.ExpectedException;
import org.junit.jupiter.api.Test;

// CHECKSTYLE OFF: HiddenField

public class JakartaLazyEntityTest {

    @Test
    public void gettersAreUsed() {
        EqualsVerifier
            .forClass(CorrectJakartaLazyFieldContainer.class)
            .suppress(Warning.NONFINAL_FIELDS)
            .verify();
    }

    @Test
    public void basicGetterNotUsed_givenEagerLoading() {
        EqualsVerifier
            .forClass(CorrectBasicJakartaEagerFieldContainer.class)
            .suppress(Warning.NONFINAL_FIELDS)
            .verify();
    }

    @Test
    public void basicGetterNotUsed_givenCorrespondingFieldIgnored() {
        EqualsVerifier
            .forClass(CorrectBasicJakartaIgnoredLazyFieldContainer.class)
            .withIgnoredFields("basic")
            .suppress(Warning.NONFINAL_FIELDS)
            .verify();
    }

    @Test
    public void basicGetterNotUsed_givenWarningSuppressed() {
        EqualsVerifier
            .forClass(CorrectBasicJakartaIgnoredLazyFieldContainer.class)
            .suppress(Warning.ALL_FIELDS_SHOULD_BE_USED)
            .verify();
    }

    @Test
    public void basicGetterUsed_givenAnnotationIsOnGetter() {
        getterNotUsed(IncorrectBasicJakartaLazyGetterContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectBasicJakartaLazyGetterContainer.class);
    }

    @Test
    public void basicGetterNotUsedInHashCode() {
        getterNotUsed(IncorrectBasicJakartaLazyFieldContainerHashCode.class, "hashCode");
        getterNotUsed_warningSuppressed(IncorrectBasicJakartaLazyFieldContainerHashCode.class);
    }

    @Test
    public void basicGetterNotUsed() {
        getterNotUsed(IncorrectBasicJakartaLazyFieldContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectBasicJakartaLazyFieldContainer.class);
    }

    @Test
    public void oneToOneGetterNotUsed() {
        getterNotUsed(IncorrectOneToOneJakartaLazyFieldContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectOneToOneJakartaLazyFieldContainer.class);
    }

    @Test
    public void oneToManyGetterNotUsed() {
        getterNotUsed(IncorrectOneToManyJakartaLazyFieldContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectOneToManyJakartaLazyFieldContainer.class);
    }

    @Test
    public void manyToOneGetterNotUsed() {
        getterNotUsed(IncorrectManyToOneJakartaLazyFieldContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectManyToOneJakartaLazyFieldContainer.class);
    }

    @Test
    public void manyToManyGetterNotUsed() {
        getterNotUsed(IncorrectManyToManyJakartaLazyFieldContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectManyToManyJakartaLazyFieldContainer.class);
    }

    @Test
    public void elementCollectionGetterNotUsed() {
        getterNotUsed(IncorrectElementCollectionJakartaLazyFieldContainer.class, "equals");
        getterNotUsed_warningSuppressed(IncorrectElementCollectionJakartaLazyFieldContainer.class);
    }

    @Test
    public void lazyGettersPickedUpInSuper() {
        EqualsVerifier.forClass(LazyGetterContainer.class).usingGetClass().verify();
        EqualsVerifier.forClass(ChildOfLazyGetterContainer.class).usingGetClass().verify();
    }

    @Test
    public void differentCodingStyle_single() {
        EqualsVerifier
            .forClass(DifferentCodingStyleContainer.class)
            .suppress(Warning.NONFINAL_FIELDS)
            .withFieldnameToGetterConverter(fn ->
                "get" + Character.toUpperCase(fn.charAt(2)) + fn.substring(3)
            )
            .verify();
    }

    @Test
    public void differentCodingStyle_configured() {
        EqualsVerifier
            .configure()
            .suppress(Warning.NONFINAL_FIELDS)
            .withFieldnameToGetterConverter(fn ->
                "get" + Character.toUpperCase(fn.charAt(2)) + fn.substring(3)
            )
            .forClass(DifferentCodingStyleContainer.class)
            .verify();
    }

    @Test
    public void differentCodingStyle_multiple() {
        EqualsVerifier
            .forClasses(Arrays.asList(DifferentCodingStyleContainer.class))
            .suppress(Warning.NONFINAL_FIELDS)
            .withFieldnameToGetterConverter(fn ->
                "get" + Character.toUpperCase(fn.charAt(2)) + fn.substring(3)
            )
            .verify();
    }

    private void getterNotUsed(Class<?> type, String method) {
        ExpectedException
            .when(() -> EqualsVerifier.forClass(type).suppress(Warning.NONFINAL_FIELDS).verify())
            .assertFailure()
            .assertMessageContains("JPA Entity", method, "direct reference");
    }

    private void getterNotUsed_warningSuppressed(Class<?> type) {
        EqualsVerifier
            .forClass(type)
            .suppress(Warning.JPA_GETTER, Warning.NONFINAL_FIELDS)
            .verify();
    }

    @Entity
    static class CorrectJakartaLazyFieldContainer {

        @Basic(fetch = FetchType.LAZY)
        private String basic;

        @OneToOne(fetch = FetchType.LAZY)
        private String oneToOne;

        @OneToMany(fetch = FetchType.LAZY)
        private String oneToMany;

        @ManyToOne(fetch = FetchType.LAZY)
        private String manyToOne;

        @ManyToMany(fetch = FetchType.LAZY)
        private String manyToMany;

        @ElementCollection(fetch = FetchType.LAZY)
        private String elementCollection;

        public String getBasic() {
            return basic;
        }

        public String getOneToOne() {
            return oneToOne;
        }

        public String getOneToMany() {
            return oneToMany;
        }

        public String getManyToOne() {
            return manyToOne;
        }

        public String getManyToMany() {
            return manyToMany;
        }

        public String getElementCollection() {
            return elementCollection;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof CorrectJakartaLazyFieldContainer)) {
                return false;
            }
            CorrectJakartaLazyFieldContainer other = (CorrectJakartaLazyFieldContainer) obj;
            return (
                Objects.equals(getBasic(), other.getBasic()) &&
                Objects.equals(getOneToOne(), other.getOneToOne()) &&
                Objects.equals(getOneToMany(), other.getOneToMany()) &&
                Objects.equals(getManyToOne(), other.getManyToOne()) &&
                Objects.equals(getManyToMany(), other.getManyToMany()) &&
                Objects.equals(getElementCollection(), other.getElementCollection())
            );
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                getBasic(),
                getOneToOne(),
                getOneToMany(),
                getManyToOne(),
                getManyToMany(),
                getElementCollection()
            );
        }
    }

    @Entity
    static class CorrectBasicJakartaEagerFieldContainer {

        @Basic
        private String basic;

        public String getBasic() {
            return basic;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof CorrectBasicJakartaEagerFieldContainer)) {
                return false;
            }
            CorrectBasicJakartaEagerFieldContainer other =
                (CorrectBasicJakartaEagerFieldContainer) obj;
            return Objects.equals(basic, other.basic);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getBasic());
        }
    }

    @Entity
    static class CorrectBasicJakartaIgnoredLazyFieldContainer {

        private String somethingElse;

        @Basic(fetch = FetchType.LAZY)
        private String basic;

        public String getBasic() {
            return basic;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof CorrectBasicJakartaIgnoredLazyFieldContainer)) {
                return false;
            }
            CorrectBasicJakartaIgnoredLazyFieldContainer other =
                (CorrectBasicJakartaIgnoredLazyFieldContainer) obj;
            return Objects.equals(somethingElse, other.somethingElse);
        }

        @Override
        public int hashCode() {
            return Objects.hash(somethingElse);
        }
    }

    @Entity
    static class IncorrectBasicJakartaLazyGetterContainer {

        private String basic;

        @Basic(fetch = FetchType.LAZY)
        public String getBasic() {
            return basic;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectBasicJakartaLazyGetterContainer)) {
                return false;
            }
            IncorrectBasicJakartaLazyGetterContainer other =
                (IncorrectBasicJakartaLazyGetterContainer) obj;
            return Objects.equals(basic, other.basic);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getBasic());
        }
    }

    @Entity
    static class IncorrectBasicJakartaLazyFieldContainerHashCode {

        @Basic(fetch = FetchType.LAZY)
        private String basic;

        public String getBasic() {
            return basic;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectBasicJakartaLazyFieldContainerHashCode)) {
                return false;
            }
            IncorrectBasicJakartaLazyFieldContainerHashCode other =
                (IncorrectBasicJakartaLazyFieldContainerHashCode) obj;
            return Objects.equals(getBasic(), other.getBasic());
        }

        @Override
        public int hashCode() {
            return Objects.hash(basic);
        }
    }

    @Entity
    static class IncorrectBasicJakartaLazyFieldContainer {

        @Basic(fetch = FetchType.LAZY)
        private String basic;

        public String getBasic() {
            return basic;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectBasicJakartaLazyFieldContainer)) {
                return false;
            }
            IncorrectBasicJakartaLazyFieldContainer other =
                (IncorrectBasicJakartaLazyFieldContainer) obj;
            return Objects.equals(basic, other.basic);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getBasic());
        }
    }

    @Entity
    static class IncorrectOneToOneJakartaLazyFieldContainer {

        @OneToOne(fetch = FetchType.LAZY)
        private String oneToOne;

        public String getOneToOne() {
            return oneToOne;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectOneToOneJakartaLazyFieldContainer)) {
                return false;
            }
            IncorrectOneToOneJakartaLazyFieldContainer other =
                (IncorrectOneToOneJakartaLazyFieldContainer) obj;
            return Objects.equals(oneToOne, other.oneToOne);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getOneToOne());
        }
    }

    @Entity
    static class IncorrectOneToManyJakartaLazyFieldContainer {

        @OneToMany(fetch = FetchType.LAZY)
        private String oneToMany;

        public String getOneToMany() {
            return oneToMany;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectOneToManyJakartaLazyFieldContainer)) {
                return false;
            }
            IncorrectOneToManyJakartaLazyFieldContainer other =
                (IncorrectOneToManyJakartaLazyFieldContainer) obj;
            return Objects.equals(oneToMany, other.oneToMany);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getOneToMany());
        }
    }

    @Entity
    static class IncorrectManyToOneJakartaLazyFieldContainer {

        @ManyToOne(fetch = FetchType.LAZY)
        private String manyToOne;

        public String getManyToOne() {
            return manyToOne;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectManyToOneJakartaLazyFieldContainer)) {
                return false;
            }
            IncorrectManyToOneJakartaLazyFieldContainer other =
                (IncorrectManyToOneJakartaLazyFieldContainer) obj;
            return Objects.equals(manyToOne, other.manyToOne);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getManyToOne());
        }
    }

    @Entity
    static class IncorrectManyToManyJakartaLazyFieldContainer {

        @ManyToMany(fetch = FetchType.LAZY)
        private String manyToMany;

        public String getManyToMany() {
            return manyToMany;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectManyToManyJakartaLazyFieldContainer)) {
                return false;
            }
            IncorrectManyToManyJakartaLazyFieldContainer other =
                (IncorrectManyToManyJakartaLazyFieldContainer) obj;
            return Objects.equals(manyToMany, other.manyToMany);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getManyToMany());
        }
    }

    @Entity
    static class IncorrectElementCollectionJakartaLazyFieldContainer {

        @ElementCollection(fetch = FetchType.LAZY)
        private String elementCollection;

        public String getElementCollection() {
            return elementCollection;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof IncorrectElementCollectionJakartaLazyFieldContainer)) {
                return false;
            }
            IncorrectElementCollectionJakartaLazyFieldContainer other =
                (IncorrectElementCollectionJakartaLazyFieldContainer) obj;
            return Objects.equals(elementCollection, other.elementCollection);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getElementCollection());
        }
    }

    @Entity
    static class LazyGetterContainer {

        @Basic(fetch = FetchType.LAZY)
        private String s;

        public String getS() {
            return s;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            return Objects.equals(getS(), ((LazyGetterContainer) obj).getS());
        }

        @Override
        public int hashCode() {
            return Objects.hash(getS());
        }
    }

    @Entity
    static class ChildOfLazyGetterContainer extends LazyGetterContainer {

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            return super.equals(obj);
        }
    }

    @Entity
    static class DifferentCodingStyleContainer {

        // CHECKSTYLE OFF: MemberName
        @OneToMany(fetch = FetchType.LAZY)
        private String m_oneToMany;

        @ManyToOne(fetch = FetchType.LAZY)
        private String m_manyToOne;

        // CHECKSTYLE ON: MemberName

        public String getOneToMany() {
            return m_oneToMany;
        }

        public String getManyToOne() {
            return m_manyToOne;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof DifferentCodingStyleContainer)) {
                return false;
            }
            DifferentCodingStyleContainer other = (DifferentCodingStyleContainer) obj;
            return (
                Objects.equals(getOneToMany(), other.getOneToMany()) &&
                Objects.equals(getManyToOne(), other.getManyToOne())
            );
        }

        @Override
        public int hashCode() {
            return Objects.hash(getOneToMany(), getManyToOne());
        }
    }
}
