/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.conf;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;

import org.apache.openjpa.jdbc.sql.MariaDBDictionary;
import org.apache.openjpa.jdbc.sql.MySQLDictionary;
import org.apache.openjpa.jdbc.sql.OracleDictionary;
import org.apache.openjpa.kernel.QueryHints;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.OpenJPAQuery;
import org.apache.openjpa.persistence.jdbc.IsolationLevel;
import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;

/**
 * Tests JPA 2.0 API methods {@link Query#getSupportedHints()} and
 * {@link Query#getHints()}.
 *
 * @author Pinaki Poddar
 *
 */
public class TestQueryHints extends SingleEMFTestCase {
    EntityManager em;
    OpenJPAQuery<?> query;

    @Override
    public void setUp() {
       super.setUp((Object[])null);
       em = emf.createEntityManager();
       String sql = "select * from Person";
       query = OpenJPAPersistence.cast(em.createNativeQuery(sql));
    }

    public void testSupportedHintsContainProductDerivationHints() {
        assertSupportedHint(OracleDictionary.SELECT_HINT, true);
        assertSupportedHint(MySQLDictionary.SELECT_HINT, true);
        assertSupportedHint(MariaDBDictionary.SELECT_HINT, true);
    }

    public void testSupportedHintsContainFetchPlanHints() {
        assertSupportedHint("openjpa.FetchPlan.LockTimeout", true);
    }

    public void testSupportedHintsIgnoresSomeFetchPlanBeanStyleProperty() {
        assertSupportedHint("openjpa.FetchPlan.QueryResultCache", false);
    }

    public void testSupportedHintsContainQueryProperty() {
        assertSupportedHint("openjpa.Subclasses", true);
    }

    public void testSupportedHintsContainKernelQueryHints() {
        assertSupportedHint(QueryHints.HINT_IGNORE_PREPARED_QUERY, true);
    }

    public void testSupportedHintsContainJPAQueryHints() {
        assertSupportedHint("jakarta.persistence.query.timeout", true);
    }

    public void testUnrecognizedKeyIsIgnored() {
        String unrecognizedKey = "acme.org.hint.SomeThingUnknown";
        query.setHint(unrecognizedKey, "xyz");
        assertFalse(query.getHints().containsKey(unrecognizedKey));
        assertNull(query.getFetchPlan().getDelegate().getHint(unrecognizedKey));
     }

    public void testRecognizedKeyIsNotRecordedButAvailable() {
        String recognizedKey = "openjpa.some.derivation.hint";
        query.setHint(recognizedKey, "abc");
        assertFalse(query.getHints().containsKey(recognizedKey));
        assertEquals("abc", query.getFetchPlan().getDelegate().getHint(
                recognizedKey));
    }

    public void testSupportedKeyIsRecordedAndAvailable() {
        String supportedKey = "openjpa.FetchPlan.FetchBatchSize";
        query.setHint(supportedKey, 42);
        assertTrue(query.getHints().containsKey(supportedKey));
        assertEquals(42, query.getFetchPlan().getFetchBatchSize());
    }

    public void testSupportedKeyWrongValue() {
        String supportedKey = "openjpa.FetchPlan.FetchBatchSize";
        short goodValue = (short)42;
        float badValue = 57.9f;
        query.setHint(supportedKey, goodValue);
        assertTrue(query.getHints().containsKey(supportedKey));
        assertEquals(goodValue, query.getFetchPlan().getFetchBatchSize());

        try {
            query.setHint(supportedKey, badValue);
            fail("Expected to fail to set " + supportedKey + " hint to "
                    + badValue);
        } catch (IllegalArgumentException e) {
            // good
        }
    }

    public void testSupportedKeyIntegerValueConversion() {
        String supportedKey = "openjpa.hint.OptimizeResultCount";
        String goodValue = "57";
        int badValue = -3;
        query.setHint(supportedKey, goodValue);
        assertTrue(query.getHints().containsKey(supportedKey));
        assertEquals(57, query.getFetchPlan().getDelegate().getHint(
                supportedKey));

        try {
            query.setHint(supportedKey, badValue);
            fail("Expected to fail to set " + supportedKey + " hint to "
                    + badValue);
        } catch (IllegalArgumentException e) {
            // good
        }
    }

    public void testSupportedKeyBooleanValueConversion() {
        String supportedKey = QueryHints.HINT_IGNORE_PREPARED_QUERY;
        String goodValue = "true";
        query.setHint(supportedKey, goodValue);
        assertTrue(query.getHints().containsKey(supportedKey));
        assertEquals(true, query.getFetchPlan().getDelegate().getHint(
                supportedKey));

        goodValue = "false";
        query.setHint(supportedKey, goodValue);
        assertTrue(query.getHints().containsKey(supportedKey));
        assertEquals(false, query.getFetchPlan().getDelegate().getHint(
                supportedKey));
    }

    public void testJPAHintSetsFetchPlan() {
        query.setHint("jakarta.persistence.lock.timeout", 5671);
        query.setHint("jakarta.persistence.query.timeout", 7500);
        assertEquals(5671, query.getFetchPlan().getLockTimeout());
        assertEquals(7500, query.getFetchPlan().getQueryTimeout());
    }

    public void testInvalidLockTimeoutHint() {
        try {
            query.setHint("jakarta.persistence.lock.timeout", -5671);
            fail("Expected setHint to fail with an IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            // expected
        }
    }

    public void testInvalidQueryTimeoutHint() {
        try {
            query.setHint("jakarta.persistence.query.timeout", -7500);
            fail("Expected setHint to fail with an IllegalArgumentException");
        } catch (IllegalArgumentException e) {
            // expected
        }
    }

    /**
     * Verifies a valid fetchplan isolation level hint can be set and retrieved.
     */
    public void testFetchPlanIsolation() {
        query.setHint("openjpa.FetchPlan.Isolation", "SERIALIZABLE");
        assertTrue(query.getHints().containsKey("openjpa.FetchPlan.Isolation"));
        assertEquals(IsolationLevel.SERIALIZABLE, ((JDBCFetchPlan)query.getFetchPlan()).getIsolation());
    }

    /**
     * Verifies an invalid fetchplan isolation level hint is ignored.
     */
    public void testInvalidFetchPlanIsolation() {
        query.setHint("openjpa.FetchPlan.TransactionIsolation", "SERIALIZABLE");
        assertFalse(query.getHints().containsKey("openjpa.FetchPlan.TransactionIsolation"));
        assertNotEquals(IsolationLevel.SERIALIZABLE, ((JDBCFetchPlan)query.getFetchPlan()).getIsolation());
    }

    void assertSupportedHint(String hint, boolean contains) {
        if (contains)
            assertTrue("Expected supported hint [" + hint + "]",
                query.getSupportedHints().contains(hint));
        else
            assertFalse("Unexpected supported hint [" + hint + "]",
                query.getSupportedHints().contains(hint));
    }


}
