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

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

import jakarta.persistence.EntityExistsException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.RollbackException;

import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DerbyDictionary;
import org.apache.openjpa.persistence.ArgumentException;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;

/**
 * Test only works in Derby due to the simple-JNDI setup
 */
public class TestSwitchConnection extends SingleEMFTestCase {
    private String defaultJndiName = "jdbc/mocked";
    private String[] jndiNames = { "jdbc/mocked1" };

    protected void init(String cfName) {
        EntityManagerFactory emf1 = getEmf(true, "openjpa.ConnectionFactoryName", cfName);
        EntityManager em = emf1.createEntityManager();
        em.getTransaction().begin();
        em.createQuery("Delete from confPerson").executeUpdate();
        em.getTransaction().commit();
        em.close();
        closeEMF(emf1);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp(Person.class, CLEAR_TABLES);
        OpenJPAEntityManager em = emf.createEntityManager();
        JDBCConfiguration conf = (JDBCConfiguration) em.getConfiguration();
        if (!(conf.getDBDictionaryInstance() instanceof DerbyDictionary)) {
            // Disable for non-Derby, due to connectionUserName to schema mapping failures
            setTestsDisabled(true);
            getLog().trace("TestOverrideNonJtaDataSource can only be executed against Derby w/o a schema");
        } else {
            // create an EMF for each database.
            init(defaultJndiName);
            init(jndiNames[0]);
        }
    }

    protected EntityManagerFactory getEmf(String ...props) {
        return getEmf(false, props);
    }

    protected EntityManagerFactory getEmf(boolean syncMappings, String ...props) {
        // null out the driver to prevent system properties from taking effect.
        // do not set connectionFactoryModeManaged - or connectionFactory2 will be used.
        int additionalProp = syncMappings ? 4 : 2;
        Object[] modProps = new Object[props.length + additionalProp];
        modProps[0] = "openjpa.ConnectionDriverName";
        modProps[1] = "";
        if (syncMappings) {
            modProps[2] = "openjpa.jdbc.SynchronizeMappings";
            modProps[3] = "buildSchema";
        }
        System.arraycopy(props, 0, modProps, additionalProp, props.length);
        return createEMF(modProps);
    }

    protected EntityManager getEm(EntityManagerFactory emf1, String name, String value) {
        Map<String, Object> props = new HashMap<>();
        props.put(name, value);
        return emf1.createEntityManager(props);
    }

    @Override
    public String getPersistenceUnitName() {
        return "TestCfSwitching";
    }

    public void testConnectionFactoryName() {
        // Disable for non-Derby.
        // split out so that we can try jakarta.persistence.jtaDataSource in the future.
        overridePropertyOnEM("openjpa.ConnectionFactoryName", jndiNames[0]);
    }

    public void testJtaDataSource() {
        // Disable for non-Derby.
        // split out so that we can try jakarta.persistence.jtaDataSource in the future.
        overridePropertyOnEM("jakarta.persistence.jtaDataSource", jndiNames[0]);
    }

    public void overridePropertyOnEM(String name, String value) {
        // use the default JndiName for the base EntityManagerFactory
        OpenJPAEntityManagerFactorySPI emf1 = (OpenJPAEntityManagerFactorySPI)getEmf(name, defaultJndiName);
        assertNotNull(emf1);
        try {
            OpenJPAEntityManagerSPI em = emf1.createEntityManager();
            assertNotNull(em);

            EntityManager em1 = getEm(emf1, name, value);
            assertNotNull(em1);

            // 'prove' that we're using a different database by inserting the same row
            em.getTransaction().begin();
            em.persist(new Person(1, "em"));
            em.getTransaction().commit();

            em1.getTransaction().begin();
            em1.persist(new Person(1, "em1"));
            em1.getTransaction().commit();

            em.clear();
            em1.clear();

            Person p = em.find(Person.class, 1);
            Person p1 = em1.find(Person.class, 1);
            assertNotSame(p, p1);
            assertEquals("em", p.getName());
            assertEquals("em1", p1.getName());

            em.clear();
            em1.clear();

            // make sure inserting the same row again fails.
            em.getTransaction().begin();
            em.persist(new Person(1));
            try {
                em.getTransaction().commit();
                fail("Should not be able to commit the same row a second time");
            } catch (RollbackException rbe) {
                assertTrue(rbe.getCause() instanceof EntityExistsException);
                // expected
            }

            em1.getTransaction().begin();
            em1.persist(new Person(1));
            try {
                em1.getTransaction().commit();
                fail("Should not be able to commit the same row a second time");
            } catch (RollbackException rbe) {
                assertTrue(rbe.getCause() instanceof EntityExistsException);
                // expected
            }
            em.close();
            em1.close();
        } finally {
            closeEMF(emf1);
        }
    }

    public void testInvalidCfName() throws Exception {
        // Disable for non-Derby.
        // ensure EM creation fails - when provided an invalid JNDI name
        EntityManagerFactory emf1 = null;
        try {
            emf1 = getEmf("openjpa.ConnectionFactoryName", defaultJndiName);
            getEm(emf1, "openjpa.ConnectionFactoryName", "jdbc/NotReal");
            fail("Expected an excepton when creating an EM with a bogus JNDI name");
        } catch (ArgumentException e) {
            assertTrue(e.isFatal());
            assertTrue(e.getMessage().contains("jdbc/NotReal")); // ensure failing JNDI name is in the message
            assertTrue(e.getMessage().contains("EntityManager")); // ensure where the JNDI name came from is in message
        } finally {
            closeEMF(emf1);
        }
    }

    public void testDataCache() {
        EntityManagerFactory emf1 = null;

        try {
            emf1 = getEmf("openjpa.DataCache", "true",
                          "openjpa.ConnectionFactoryName", defaultJndiName);
            getEm(emf1, "openjpa.ConnectionFactoryName", "jdbc/NotReal");
            fail("Expected an excepton when creating an EM with a bogus JNDI name");
        } catch (ArgumentException e) {
            assertTrue(e.isFatal());
            assertTrue(e.getMessage().contains("jdbc/NotReal"));
            assertTrue(e.getMessage().contains("L2 Cache"));
        } finally {
            closeEMF(emf1);
        }
    }

    public void testQueryCache() {
        EntityManagerFactory emf1 = null;

        try {
            emf1 = getEmf("openjpa.QueryCache", "true",
                          "openjpa.ConnectionFactoryName", defaultJndiName);
            getEm(emf1, "openjpa.ConnectionFactoryName", "jdbc/NotReal");
            fail("Expected an excepton when creating an EM with a bogus JNDI name");
        } catch (ArgumentException e) {
            assertTrue(e.isFatal());
            assertTrue(e.getMessage().contains("jdbc/NotReal"));
            assertTrue(e.getMessage().contains("openjpa.QueryCache"));
        } finally {
            closeEMF(emf1);
        }
    }

    public void testSyncMappings() {
        EntityManagerFactory emf1 = null;

        try {
            emf1 = getEmf("openjpa.jdbc.SynchronizeMappings", "buildSchema",
                          "openjpa.ConnectionFactoryName", defaultJndiName);
            getEm(emf1, "openjpa.ConnectionFactoryName", "jdbc/NotReal");
            fail("Expected an excepton when creating an EM with a bogus JNDI name");
        } catch (ArgumentException e) {
            assertTrue(e.isFatal());
            assertTrue(e.getMessage().contains("jdbc/NotReal"));
            assertTrue(e.getMessage().contains("openjpa.jdbc.SynchronizeMappings"));
        } finally {
            closeEMF(emf1);
        }
    }
}
