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

import java.util.ArrayList;
import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.Subquery;

import org.apache.openjpa.persistence.test.SQLListenerTestCase;

public class TestCriteriaMultiselectAliasing extends SQLListenerTestCase {
    CriteriaQuery<Tuple> critQuery;
    String critQueryString;

    String doCB = System.getProperty("doCB");

    @Override
    public void setUp() throws Exception {
        // Only run on Oracle....the asserts at this time look for SQL specific to the
        // way things are generated for Oracle.
        setSupportedDatabases(
            org.apache.openjpa.jdbc.sql.OracleDictionary.class);
        if (isTestsDisabled()) {
            return;
        }

        super.setUp(DimDay.class, FactWorkAssignment.class
            ,"openjpa.Log","SQL=TRACE,Tests=TRACE", "openjpa.ConnectionFactoryProperties",
            "PrintParameters=true, PrettyPrint=true, PrettyPrintLineLength=72"
            );

        critQuery = createCriteriaBuilder();
        critQueryString = critQuery.toString();
        System.out.println("critQueryString = " + critQueryString);
    }

    public void test (){
        if ("true".equals(doCB)){
            this.ttestCriteriaQuery();
        }
        else{
            this.ttestGeneratedCriteriaQueryString();
        }

    }
    /**
     * This method produce wrong query like (note the extra T_DIM_DAY t3):
     * SELECT t0.empl_cnt FROM
     * T_FACT_WORK_ASGNMT t0, T_DIM_DAY t1, T_DIM_DAY t3
     * WHERE (t0.CLNT_OBJ_ID = ? AND t1.ROLL_13_MNTH_IND = ? AND t0.pers_obj_id IN (
     *     SELECT t2.pers_obj_id FROM T_FACT_WORK_ASGNMT t2 WHERE (t2.CLNT_OBJ_ID = ? AND
     *     t3.MNTH_STRT_DAY_KY >= ?)))
     * [params=(String) dummy1, (int) 1, (String) dummy1, (long) 20150201]
     *
     * The correct query should be:
     * SELECT t0.empl_cnt FROM
     * T_FACT_WORK_ASGNMT t0, T_DIM_DAY t1
     * WHERE (t0.CLNT_OBJ_ID = ? AND t1.ROLL_13_MNTH_IND = ? AND t0.pers_obj_id IN (
     *     SELECT t2.pers_obj_id FROM T_FACT_WORK_ASGNMT t2 WHERE (t2.CLNT_OBJ_ID = ? AND
     *     t3.MNTH_STRT_DAY_KY >= ?)))
     * [params=(String) dummy1, (int) 1, (String) dummy1, (long) 20150201]
     *
     */
    public void ttestCriteriaQuery() {
        EntityManager em = emf.createEntityManager();
        resetSQL();
        em.createQuery(critQuery).getResultList();
        assertNotSQL(".*T_DIM_DAY t3.*");
        assertSQL(".*T_DIM_DAY t1.*");
        em.close();
    }

    /**
     * If we execute just the string generated by Criteria Builder, we
     * do not see an extra alias.  We see:
     * SELECT t0.empl_cnt FROM T_FACT_WORK_ASGNMT t0, T_DIM_DAY t1
     * WHERE (t0.CLNT_OBJ_ID = ? AND t1.ROLL_13_MNTH_IND = ? AND
     *   t0.pers_obj_id IN (SELECT t2.pers_obj_id FROM
     *   T_FACT_WORK_ASGNMT t2 WHERE (t2.CLNT_OBJ_ID = ? AND
     *   t1.MNTH_STRT_DAY_KY = ?)))
     */
    public void ttestGeneratedCriteriaQueryString(){
        if (!"true".equals(doCB)){
        EntityManager em = emf.createEntityManager();
        System.out.println("NOT doing CB");
        resetSQL();
        em.createQuery(critQueryString).getResultList();
        assertNotSQL(".*T_DIM_DAY t3.*");
        assertSQL(".*T_DIM_DAY t1.*");
        em.close();
        }
    }

    public CriteriaQuery<Tuple> createCriteriaBuilder(){
            EntityManager em = emf.createEntityManager();

            List<Predicate> predicates = new ArrayList<>();
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<Tuple> cq = cb.createTupleQuery();
            Root<DimDay> day = cq.from(DimDay.class);

            Root<FactWorkAssignment> wa =  cq.from(FactWorkAssignment.class);

            predicates.add(cb.equal(wa.get(FactWorkAssignment_.orgOID), "dummy1"));
            predicates.add(cb.equal(day.get(DimDay_.roll13MonthInd), 1));

            Subquery<String> subQuery = cq.subquery(String.class);

            Root<FactWorkAssignment> wa1 = subQuery.from(FactWorkAssignment.class);

            subQuery.select(wa1.get(FactWorkAssignment_.personObjId));
            List<Predicate> subQueryPredicates = new ArrayList<>();
            subQueryPredicates.add(cb.equal(wa1.get(FactWorkAssignment_.orgOID), "dummy1"));

            //Removing this seem to "fix" the issue....I think the fact that we use 'day' from
            //the 'outer' query has an affect....is it OK to use 'day' from the outer query??   I'm
            //assuming so since 'testGeneratedCriteriaQueryString' generates the expected SQL.
//            subQueryPredicates.add(cb.greaterThanOrEqualTo(day.get(DimDay_.monthStrtDate), new Long(20150201L)));
            subQueryPredicates.add(cb.equal(day.get(DimDay_.monthStrtDate), 20150201L));

            //Doing this places the 'T_DIM_DAY t3' in the 'inner'/sub query.  Is this the proper solution??  Or just a
            //hacky work around?
            //Root<DimDay> day2 =  subQuery.from(DimDay.class);
            //subQueryPredicates.add(cb.greaterThanOrEqualTo(day2.get(DimDay_.monthStrtDate), new Long(20150201L)));

            subQuery.where(subQueryPredicates.toArray(new Predicate[] {}));

            Predicate predicate = wa.get(FactWorkAssignment_.personObjId).in(subQuery);

            predicates.add(predicate);

            List<Selection<?>> selections = new ArrayList<>();

            Expression<Integer> expHC = wa.get(FactWorkAssignment_.employeeCount);
            selections.add(expHC);

            cq.multiselect(selections).where(predicates.toArray(new Predicate[] {}));

            return cq;
        }
}
