/*****************************************************************
 *   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
 *
 *    https://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.cayenne.dbsync.reverse.dbload;

import java.sql.DatabaseMetaData;
import java.sql.Types;

import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class AttributeLoaderIT extends BaseLoaderIT {

    @Test
    public void testAttributeLoad() throws Exception {
        createDbEntities();

        AttributeLoader loader = new AttributeLoader(adapter, EMPTY_CONFIG, new DefaultDbLoaderDelegate());
        loader.load(connection.getMetaData(), store);

        DbEntity artist = getDbEntity("ARTIST");
        DbAttribute a = getDbAttribute(artist, "ARTIST_ID");
        assertNotNull(a);
        if (accessStackAdapter.onlyGenericNumberType()) {
            // All integer types are mapped to NUMERIC in Oracle DB.
            assertEquals(Types.NUMERIC, a.getType());
        } else {
            assertEquals(Types.BIGINT, a.getType());
        }
        assertTrue(a.isMandatory());
        assertFalse(a.isGenerated());

        a = getDbAttribute(artist, "ARTIST_NAME");
        assertNotNull(a);
        assertEquals(Types.CHAR, a.getType());
        assertEquals(254, a.getMaxLength());
        assertTrue(a.isMandatory());

        a = getDbAttribute(artist, "DATE_OF_BIRTH");
        assertNotNull(a);
        if(accessStackAdapter.onlyGenericDateType()) {
            assertTrue(Types.DATE == a.getType() || Types.TIMESTAMP == a.getType());
        } else {
            assertEquals(Types.DATE, a.getType());
        }
        assertFalse(a.isMandatory());

        if(accessStackAdapter.supportsLobs()) {
            assertLobDbEntities();
        }

        if (adapter.supportsGeneratedKeys()) {
            assertGenerated();
        }
    }

    @Test
    public void testAttributeLoadTypes() throws Exception {
        DatabaseMetaData metaData = connection.getMetaData();
        DbLoaderDelegate delegate = new DefaultDbLoaderDelegate();

        // We need all data to check relationships, so simply load it all
        EntityLoader entityLoader = new EntityLoader(adapter, EMPTY_CONFIG, delegate);
        AttributeLoader attributeLoader = new AttributeLoader(adapter, EMPTY_CONFIG, delegate);

        entityLoader.load(metaData, store);
        attributeLoader.load(metaData, store);

        DbEntity dbe = getDbEntity("PAINTING");
        DbEntity floatTest = getDbEntity("FLOAT_TEST");
        DbEntity smallintTest = getDbEntity("SMALLINT_TEST");
        DbAttribute integerAttr = getDbAttribute(dbe, "PAINTING_ID");
        DbAttribute decimalAttr = getDbAttribute(dbe, "ESTIMATED_PRICE");
        DbAttribute varcharAttr = getDbAttribute(dbe, "PAINTING_TITLE");
        DbAttribute floatAttr = getDbAttribute(floatTest, "FLOAT_COL");
        DbAttribute smallintAttr = getDbAttribute(smallintTest, "SMALLINT_COL");

        // check decimal
        assertTrue(msgForTypeMismatch(Types.DECIMAL, decimalAttr), Types.DECIMAL == decimalAttr.getType()
                || Types.NUMERIC == decimalAttr.getType());
        assertEquals(2, decimalAttr.getScale());

        // check varchar
        assertEquals(msgForTypeMismatch(Types.VARCHAR, varcharAttr), Types.VARCHAR, varcharAttr.getType());
        assertEquals(255, varcharAttr.getMaxLength());

        // check integer
        // All integer types are mapped to NUMERIC in Oracle DB.
        if (accessStackAdapter.onlyGenericNumberType()) {
            assertEquals(msgForTypeMismatch(Types.NUMERIC, integerAttr), Types.NUMERIC, integerAttr.getType());
        } else {
            assertEquals(msgForTypeMismatch(Types.INTEGER, integerAttr), Types.INTEGER, integerAttr.getType());
        }

        // check float
        assertTrue(msgForTypeMismatch(Types.FLOAT, floatAttr), Types.FLOAT == floatAttr.getType()
                || Types.DOUBLE == floatAttr.getType() || Types.REAL == floatAttr.getType());

        // check smallint
        // All integer types are mapped to NUMERIC in Oracle DB.
        if (accessStackAdapter.onlyGenericNumberType()) {
            assertEquals(msgForTypeMismatch(Types.NUMERIC, smallintAttr), Types.NUMERIC, smallintAttr.getType());
        } else {
            assertTrue(msgForTypeMismatch(Types.SMALLINT, smallintAttr),
                       Types.SMALLINT == smallintAttr.getType() || Types.INTEGER == smallintAttr.getType());
        }
    }

    private void assertGenerated() {
        DbEntity bag = getDbEntity("GENERATED_COLUMN_TEST");
        DbAttribute id = getDbAttribute(bag, "GENERATED_COLUMN");
        assertTrue(id.isGenerated());
    }

    private void assertLobDbEntities() {
        DbEntity blobEnt = getDbEntity("BLOB_TEST");
        assertNotNull(blobEnt);
        DbAttribute blobAttr = getDbAttribute(blobEnt, "BLOB_COL");
        assertNotNull(blobAttr);
        assertTrue(msgForTypeMismatch(Types.BLOB, blobAttr),
                Types.BLOB == blobAttr.getType()
                        || Types.VARBINARY == blobAttr.getType()
                        || Types.LONGVARBINARY == blobAttr.getType());

        DbEntity clobEnt = getDbEntity("CLOB_TEST");
        assertNotNull(clobEnt);
        DbAttribute clobAttr = getDbAttribute(clobEnt, "CLOB_COL");
        assertNotNull(clobAttr);
        assertTrue(msgForTypeMismatch(Types.CLOB, clobAttr),
                Types.CLOB == clobAttr.getType()
                        || Types.VARCHAR == clobAttr.getType()
                        || Types.LONGVARCHAR == clobAttr.getType());
    }



    private DbAttribute getDbAttribute(DbEntity ent, String name) {
        DbAttribute da = ent.getAttribute(name);
        // sometimes table names get converted to lowercase
        if (da == null) {
            da = ent.getAttribute(name.toLowerCase());
        }

        return da;
    }

    private static String msgForTypeMismatch(int origType, DbAttribute newAttr) {
        String nt = TypesMapping.getSqlNameByType(newAttr.getType());
        String ot = TypesMapping.getSqlNameByType(origType);
        return attrMismatch(newAttr.getName(), "expected type: <" + ot + ">, but was <" + nt + ">");
    }

    private static String attrMismatch(String attrName, String msg) {
        return "[Error loading attribute '" + attrName + "': " + msg + "]";
    }
}
