/*
 * 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.embed.lazy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import jakarta.persistence.EntityManager;

import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
import org.apache.openjpa.lib.jdbc.JDBCEvent;
import org.apache.openjpa.lib.jdbc.JDBCListener;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.test.AbstractPersistenceTestCase;

public class TestLazyEmbeddable extends AbstractPersistenceTestCase {

    protected List<String> _sql = new ArrayList<>();

    /*
     * Verifies an entity with annotated (@Persistent) lazy embeddable and xml-tagged
     * lazy embeddable (openjpa:persistent) with a mix of eager and lazy fields are lazily
     * loaded (or not) as expected.
     */
    public void testLazyEmbeddableFields() throws Exception {
        _sql.clear();
        HashMap<String, Object> props = new HashMap<>();
        props.put("openjpa.jdbc.JDBCListeners",
            new JDBCListener[] { new SQLListener() });
        OpenJPAEntityManagerFactorySPI emf1 =
            (OpenJPAEntityManagerFactorySPI)OpenJPAPersistence.
            createEntityManagerFactory("LazyEmbedPU",
            "org/apache/openjpa/persistence/embed/lazy/" +
            "embed-lazy-persistence.xml", props);

        try {
            EntityManager em = emf1.createEntityManager();

            Recliner rec = new Recliner();
            ReclinerId recId = new ReclinerId();
            recId.setColor("Camouflage");
            recId.setId(new Random().nextInt());
            rec.setId(recId);
            rec.setStyle(Style.RETRO);

            Guy guy = new Guy();
            guy.setName("Tom");
            guy.setHeight(76);
            guy.setWeight(275);
            rec.setGuy(guy);

            BeverageHolder bh = new BeverageHolder();
            bh.setDepth(2);
            bh.setDiameter(3);
            rec.setHolder(bh);

            em.getTransaction().begin();
            em.persist(rec);
            em.getTransaction().commit();

            em.clear();
            _sql.clear();

            Recliner r2 = em.find(Recliner.class, recId);
            assertNotNull("Find returned null object", r2);
            assertTrue(selectContains("REC_TABLE", _sql, "REC_STYLE", "RECID_ID", "RECID_COLOR"));
            assertFalse(selectContains("REC_TABLE", _sql, "GUY_HEIGHT", "GUY_WEIGHT", "GUY_NAME",
                "BH_DIAMETER", "BH_DEPTH"));
            em.detach(r2);
            // Lazy embeds should be null after detach.
            assertNull("Embedded field guy is null before getter is called", r2.getGuy());
            assertNull("Embedded field holder is null before getter is called", r2.getHolder());

            // verify lazy embeds will load on access post-detach and merge
            r2 = em.merge(r2);
            verifyLazyLoading(r2);

            em.clear();
            _sql.clear();
            // verify lazy embeds will load on access after find
            r2 = em.find(Recliner.class, recId);
            assertNotNull("Find returned null object", r2);
            assertTrue(selectContains("REC_TABLE", _sql, "REC_STYLE", "RECID_ID", "RECID_COLOR"));
            assertFalse(selectContains("REC_TABLE", _sql, "GUY_HEIGHT", "GUY_WEIGHT", "GUY_NAME",
                "BH_DIAMETER", "BH_DEPTH"));
            verifyLazyLoading(r2);
        } finally {
            cleanupEMF(emf1);
        }
    }



    private void verifyLazyLoading(Recliner r2) {
        _sql.clear();
        Guy g = r2.getGuy();
        assertNotNull("Guy is not null", g);
        assertTrue(selectContains("REC_TABLE", _sql, "GUY_NAME"));
        assertFalse(selectContains("REC_TABLE", _sql, "GUY_HEIGHT", "GUY_WEIGHT"));
        _sql.clear();
        g.getHeight();
        assertTrue(selectContains("REC_TABLE", _sql, "GUY_HEIGHT"));
        assertFalse(selectContains("REC_TABLE", _sql, "GUY_NAME", "GUY_WEIGHT", "BH_DIAMETER",
            "BH_DEPTH"));

        _sql.clear();
        BeverageHolder holder = r2.getHolder();
        assertNotNull("Holder is not null", holder);
        assertTrue(selectContains("REC_TABLE", _sql, "BH_DEPTH"));
        assertFalse(selectContains("REC_TABLE", _sql, "BH_DIAMETER"));
        _sql.clear();
        holder.getDiameter();
        assertTrue(selectContains("REC_TABLE", _sql, "BH_DIAMETER"));
        assertFalse(selectContains("REC_TABLE", _sql, "BH_DEPTH"));
    }



    private boolean selectContains(String table, List<String> sql, String...cols) {
        boolean foundSelect = false;
        for (String s: sql) {
            String stmt = s.toUpperCase();
            if (!stmt.startsWith("SELECT") && !stmt.contains(table)) {
                continue;
            }
            foundSelect = true;
            for (String col : cols) {
                String ucol = col.toUpperCase();
                if (!stmt.contains(ucol)) {
                   return false;
                }
            }
        }
        return foundSelect;
    }



    private static String toString(List<String> list) {
        StringBuilder buf = new StringBuilder();
        for (String s : list)
            buf.append(s).append("\r\n");
        return buf.toString();
    }

    /**
     * Closes a specific entity manager factory and cleans up
     * associated tables.
     */
    private void cleanupEMF(OpenJPAEntityManagerFactorySPI emf1)
      throws Exception {

        if (emf1 == null)
            return;

        try {
            clear(emf1);
        } catch (Exception e) {
            // if a test failed, swallow any exceptions that happen
            // during tear-down, as these just mask the original problem.
            if (testResult.wasSuccessful())
                throw e;
        } finally {
            closeEMF(emf1);
        }
    }

    public class SQLListener
        extends AbstractJDBCListener {

        @Override
        public void beforeExecuteStatement(JDBCEvent event) {
            if (event.getSQL() != null && _sql != null) {
                _sql.add(event.getSQL());
            }
        }
    }
}
