/*
 * 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.persistence.simple;

import java.util.HashMap;
import java.util.Map;

import jakarta.persistence.CacheRetrieveMode;
import jakarta.persistence.CacheStoreMode;

import org.apache.openjpa.persistence.JPAProperties;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.test.SingleEMTestCase;

public class TestRefresh extends SingleEMTestCase {

    @Override
    public void setUp() {
        super.setUp(CLEAR_TABLES, Item.class,
             "openjpa.AutoDetach", "commit",
             "openjpa.DataCache", "true",
             "openjpa.RemoteCommitProvider", "sjvm");
    }

    public void testFlushRefreshNewInstance() {
        em.getTransaction().begin();
        Item item = new Item();
        item.setItemData("Test Data");
        em.persist(item);
        em.flush();
        em.refresh(item);
        em.getTransaction().commit();
        assertEquals("Test Data", item.getItemData());
    }

    /**
     * Refresh always bypass L2 cache.
     * According to JPA 2.0 Spec Section 3.7.2:
     * "The retrieveMode property is ignored for the refresh method,
     *  which always causes data to be retrieved from the database, not the cache."
     */
    public void testRefreshBypassL2Cache() {
        String original = "Original L2 Cached Data";
        String sneakUpdate = "Sneak Update";
        em.getTransaction().begin();
        Item item = new Item();
        item.setItemData(original);
        em.persist(item);
        em.getTransaction().commit();
        assertCached(Item.class, item.getItemId());

        // Sneakily update with SQL
        String sql = "UPDATE I_ITEM SET I_DATA=?1 WHERE I_ID=?2";
        em.getTransaction().begin();
        int updateCount = em.createNativeQuery(sql)
            .setParameter(1, sneakUpdate)
            .setParameter(2, item.getItemId())
            .executeUpdate();
        assertEquals(1, updateCount);
        em.getTransaction().commit();

        em.getTransaction().begin();
        // Find will find the L2 cached data
        item = em.find(Item.class, item.getItemId());
        assertEquals(original, item.getItemData());
        // But refresh will get the actual database record
        em.refresh(item);
        assertEquals(sneakUpdate, item.getItemData());

        // Even if cache retrieve mode is set to USE
        em.setProperty(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
        em.refresh(item);
        assertEquals(sneakUpdate, item.getItemData());
        em.getTransaction().rollback();
    }

    public void testCacheRetrieveModeSetting() {
        OpenJPAEntityManager em = emf.createEntityManager();
        em.setProperty(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
        Map<String, Object> properties = em.getProperties();
        if (!properties.containsKey(JPAProperties.CACHE_RETRIEVE_MODE)) {
            System.err.println(properties);
            fail("Expected " + JPAProperties.CACHE_RETRIEVE_MODE + " properties be returned");
        }
        Object mode = properties.get(JPAProperties.CACHE_RETRIEVE_MODE);
        assertEquals(mode, CacheRetrieveMode.USE);
    }

    public void testCacheStoreModeSetting() {
        OpenJPAEntityManager em = emf.createEntityManager();
        em.setProperty(JPAProperties.CACHE_STORE_MODE, CacheStoreMode.USE);
        Map<String, Object> properties = em.getProperties();
        if (!properties.containsKey(JPAProperties.CACHE_STORE_MODE)) {
            System.err.println(properties);
            fail("Expected " + JPAProperties.CACHE_STORE_MODE + " properties be returned");
        }
        Object mode = properties.get(JPAProperties.CACHE_STORE_MODE);
        assertEquals(mode, CacheStoreMode.USE);
    }

    public void testRefreshAfterRemove() {
        try {
            em.getTransaction().begin();
            Item item = new Item();
            item.setItemData("Test Data");
            em.persist(item);
            em.flush();
            em.remove(item);
            em.flush();
            em.refresh(item);
            em.getTransaction().commit();
            fail("Did not catch expected IllegalArgumentException for refresh() of removed entity");
        } catch (IllegalArgumentException e) {
            // Expected exception
        }
    }

    public void testFindWithCacheRetrieveProperty() {
        String key = "Test property in find.";
        OpenJPAEntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Item item = new Item();
        item.setItemData(key);
        em.persist(item);
        em.flush();
        em.getTransaction().commit();
        int id = item.getItemId();
        em.clear();
        emf.getCache().evictAll();

        assertEquals(key, item.getItemData());

        em.setProperty(JPAProperties.CACHE_STORE_MODE, CacheStoreMode.USE);
        em.setProperty(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
        Map<String, Object> properties = em.getProperties();
        if (!properties.containsKey(JPAProperties.CACHE_STORE_MODE)) {
            System.err.println(properties);
            fail("Expected " + JPAProperties.CACHE_STORE_MODE + " properties be returned");
        }
        if (!properties.containsKey(JPAProperties.CACHE_RETRIEVE_MODE)) {
            System.err.println(properties);
            fail("Expected " + JPAProperties.CACHE_RETRIEVE_MODE + " properties be returned");
        }
        Map<String, Object> paramProperties = new HashMap<>();
        paramProperties.put(JPAProperties.CACHE_STORE_MODE, CacheStoreMode.BYPASS);
        paramProperties.put(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        Item fItem = em.find(Item.class, id, paramProperties);
        assertEquals(fItem.getItemData(), key);
        assertNotCached(Item.class, id);

        Object mode = em.getProperties().get(JPAProperties.CACHE_STORE_MODE);
        assertEquals(mode, CacheStoreMode.USE);
        mode = em.getProperties().get(JPAProperties.CACHE_RETRIEVE_MODE);
        assertEquals(mode, CacheRetrieveMode.USE);
    }

    public void testRefreshWithCacheRetrieveProperty() {
        String key = "Test property in refresh.";
        String updatedKey = "Updated test property in refresh.";
        OpenJPAEntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Item item = new Item();
        item.setItemData(key);
        em.persist(item);
        em.flush();
        em.getTransaction().commit();
        assertEquals(key, item.getItemData());

        int id = item.getItemId();
        emf.getCache().evictAll();

        assertEquals(key, item.getItemData());

        em.setProperty(JPAProperties.CACHE_STORE_MODE, CacheStoreMode.USE);
        em.setProperty(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
        Map<String, Object> properties = em.getProperties();
        if (!properties.containsKey(JPAProperties.CACHE_STORE_MODE)) {
            System.err.println(properties);
            fail("Expected " + JPAProperties.CACHE_STORE_MODE + " properties be returned");
        }
        if (!properties.containsKey(JPAProperties.CACHE_RETRIEVE_MODE)) {
            System.err.println(properties);
            fail("Expected " + JPAProperties.CACHE_RETRIEVE_MODE + " properties be returned");
        }
        Map<String, Object> paramProperties = new HashMap<>();
        paramProperties.put(JPAProperties.CACHE_STORE_MODE, CacheStoreMode.BYPASS);
        paramProperties.put(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS);
        Item fItem = em.find(Item.class, id, paramProperties);
        assertEquals(key, fItem.getItemData());
        assertNotCached(Item.class, id);

        fItem.setItemData(updatedKey);
        assertEquals(updatedKey, fItem.getItemData());

        em.refresh(fItem, paramProperties);
        assertEquals(key, fItem.getItemData());
        assertNotCached(Item.class, id);

        Object mode = em.getProperties().get(JPAProperties.CACHE_STORE_MODE);
        assertEquals(mode, CacheStoreMode.USE);
        mode = em.getProperties().get(JPAProperties.CACHE_RETRIEVE_MODE);
        assertEquals(mode, CacheRetrieveMode.USE);
    }

    void assertCached(Class<?> cls, Object oid) {
        assertTrue(cls + ":" + oid + " should be in L2 cache, but not", emf.getCache().contains(cls, oid));
    }

    void assertNotCached(Class<?> cls, Object oid) {
        assertTrue(cls + ":" + oid + " should not be in L2 cache, but is", !emf.getCache().contains(cls, oid));
    }

}
