/*****************************************************************
 * 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
 * <p/>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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.cayenne.query;

import org.apache.cayenne.DataRow;
import org.apache.cayenne.QueryResponse;
import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.log.JdbcEventLogger;
import org.apache.cayenne.test.jdbc.DBHelper;
import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.testmap.Artist;
import org.apache.cayenne.testdo.testmap.Painting;
import org.apache.cayenne.tx.BaseTransaction;
import org.apache.cayenne.tx.ExternalTransaction;
import org.apache.cayenne.unit.UnitDbAdapter;
import org.apache.cayenne.unit.di.server.CayenneProjects;
import org.apache.cayenne.unit.di.server.ServerCase;
import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.List;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;

@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
public class MappedQueryIT extends ServerCase {

    @Inject
    private DataContext context;

    @Inject
    private DBHelper dbHelper;

    @Inject
    private UnitDbAdapter accessStackAdapter;

    @Inject
    private JdbcEventLogger jdbcEventLogger;

    protected void createArtistsDataSet() throws Exception {
        TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");

        long dateBase = System.currentTimeMillis();

        for (int i = 1; i <= 20; i++) {
            tArtist.insert(i, "artist" + i, new java.sql.Date(dateBase + 10000 * i));
        }
    }

    @Test
    public void testSelectQuery() throws Exception {
        createArtistsDataSet();

        Artist a = MappedSelect.query("ParameterizedQueryWithLocalCache", Artist.class)
                .param("name", "artist14").select(context).get(0);
        assertNotNull(a);
        assertEquals("artist14", a.getArtistName());
    }

    @Test
    public void testSQLTemplateSelect() throws Exception {
        createArtistsDataSet();

        List<DataRow> result = MappedSelect.query("SelectTestLower", DataRow.class).select(context);
        assertEquals(20, result.size());
        assertThat(result.get(0), instanceOf(DataRow.class));
    }

    @Test
    public void testSQLTemplateUpdate() throws Exception {
        int updated = MappedExec.query("NonSelectingQuery").update(context)[0];

        assertEquals(1, updated);

        Painting painting = ObjectSelect.query(Painting.class).selectOne(context);
        assertEquals("No Painting Like This", painting.getPaintingTitle());
        assertEquals(12.5, painting.getEstimatedPrice().doubleValue(), 0);
    }

    @Test
    public void testProcedureQuery() throws Exception {
        if (!accessStackAdapter.supportsStoredProcedures()) {
            return;
        }

        if (!accessStackAdapter.canMakeObjectsOutOfProcedures()) {
            return;
        }

        // create an artist with painting in the database
        Artist a = context.newObject(Artist.class);
        a.setArtistName("An Artist");

        Painting p = context.newObject(Painting.class);
        p.setPaintingTitle("A Painting");
        // converting double to string prevents rounding weirdness...
        p.setEstimatedPrice(new BigDecimal(1000));
        a.addToPaintingArray(p);

        context.commitChanges();

        List<?> artists = runProcedureSelect(MappedSelect.query("ProcedureQuery", Artist.class)
                .param("aName", "An Artist")
                .param("paintingPrice", 3000).forceNoCache()).firstList();

        // check the results
        assertNotNull("Null result from StoredProcedure.", artists);
        assertEquals(1, artists.size());
        Artist artistRow = (Artist) artists.get(0);
        assertEquals("An Artist", artistRow.getArtistName());
    }

    @Test
    public void testEJBQLQuery() throws Exception {
        createArtistsDataSet();

        List result = MappedSelect.query("EjbqlQueryTest").select(context);
        assertEquals(20, result.size());
        assertThat(result.get(0), instanceOf(DataRow.class));
    }

    @Test
    public void testCacheKey() {
        // ensure queries initialized with different parameters receive different cache keys
        MappedSelect<Artist> query1 = MappedSelect
                .query("ParameterizedQueryWithLocalCache", Artist.class).param("name", "artist1");
        MappedSelect<Artist> query2 = MappedSelect
                .query("ParameterizedQueryWithLocalCache", Artist.class).param("name", "artist2");
        MappedSelect<Artist> query3 = MappedSelect
                .query("ParameterizedQueryWithLocalCache", Artist.class).param("name", "artist2");

        assertNotEquals(
                query1.getMetaData(context.getEntityResolver()).getCacheKey(),
                query2.getMetaData(context.getEntityResolver()).getCacheKey());
        assertEquals(
                query2.getMetaData(context.getEntityResolver()).getCacheKey(),
                query3.getMetaData(context.getEntityResolver()).getCacheKey());
    }

    protected QueryResponse runProcedureSelect(AbstractMappedQuery q) throws Exception {
        // Sybase blows whenever a transaction wraps a SP, so turn off
        // transactions

        // TODO: it is quite the opposite with PostgreSQL. If an SP returns an
        // open refcursor, it actually expects a TX in progress, so while we
        // don't have refcursor unit tests, this is something to keep in mind
        // e.g.
        // http://stackoverflow.com/questions/16921942/porting-apache-cayenne-from-oracle-to-postgresql

        BaseTransaction t = new ExternalTransaction(jdbcEventLogger);
        BaseTransaction.bindThreadTransaction(t);

        try {
            return context.performGenericQuery(q);
        } finally {
            BaseTransaction.bindThreadTransaction(null);
            t.commit();
        }
    }
}
