/*
 * Copyright DataStax, Inc.
 *
 * Licensed 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 com.datastax.driver.mapping;

import com.datastax.driver.core.CCMTestsSupport;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.CodecNotFoundException;
import com.datastax.driver.core.utils.CassandraVersion;
import com.datastax.driver.mapping.annotations.*;
import com.google.common.collect.Lists;
import org.testng.annotations.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@SuppressWarnings("unused")
public class MapperAccessorParamsTest extends CCMTestsSupport {

    @Override
    public void onTestContextInitialized() {
        execute(
                "CREATE TABLE user ( key int primary key, gender int, home_phone text, work_phone text)",
                "CREATE INDEX on user(gender)",
                "CREATE TABLE user_str ( key int primary key, gender text)",
                "CREATE INDEX on user_str(gender)"
        );
    }

    @Test(groups = "short")
    @CassandraVersion(value = "2.0", description = "Uses named parameters")
    public void should_allow_less_parameters_than_bind_markers_if_there_are_repeated_names() {
        UserPhoneAccessor accessor = new MappingManager(session())
                .createAccessor(UserPhoneAccessor.class);

        session().execute("delete from user where key = 0");
        accessor.updatePhones_positional("1111", "2222", 0);
        assertPhonesEqual(0, "1111", "2222");

        session().execute("delete from user where key = 0");
        accessor.updatePhones_named(0, "1111", "2222");
        assertPhonesEqual(0, "1111", "2222");

        session().execute("delete from user where key = 0");
        accessor.updatePhones_fallback("1111", "2222", 0);
        assertPhonesEqual(0, "1111", "2222");

        session().execute("delete from user where key = 0");
        accessor.updateBothPhones(0, "1111");
        assertPhonesEqual(0, "1111", "1111");

        session().execute("delete from user where key = 0");
        accessor.updatePhones_fallback2("1111", "2222", 0);
        assertPhonesEqual(0, "1111", "2222");
    }

    @Test(groups = "short", expectedExceptions = RuntimeException.class)
    public void should_fail_if_not_enough_parameters() {
        new MappingManager(session())
                .createAccessor(UserPhoneAccessor_NotEnoughParams.class);
    }

    @Test(groups = "short", expectedExceptions = RuntimeException.class)
    public void should_fail_if_too_many_parameters() {
        new MappingManager(session())
                .createAccessor(UserPhoneAccessor_TooManyParams.class);
    }

    /**
     * Ensures that a wrong parameter type is detected when
     * the accessor is called with a wrong data type.
     *
     * @jira_ticket JAVA-974
     */
    @Test(groups = "short", expectedExceptions = CodecNotFoundException.class)
    public void should_fail_if_wrong_parameter_type() {
        UserPhoneAccessor_WrongParameterTypes accessor = new MappingManager(session())
                .createAccessor(UserPhoneAccessor_WrongParameterTypes.class);
        accessor.findUsersBykeys(Lists.newArrayList(1, 2, 3));
    }

    private void assertPhonesEqual(int key, String home, String work) {
        Row row = session().execute("select * from user where key = " + key).one();
        assertThat(row.getString("home_phone")).isEqualTo(home);
        assertThat(row.getString("work_phone")).isEqualTo(work);
    }

    enum Enum {
        MALE, FEMALE
    }

    /**
     * Tests various ways to match method parameters to query bind markers.
     */
    @Accessor
    public interface UserPhoneAccessor {
        /**
         * Standard positional markers
         */
        @Query("update user set home_phone = ?, work_phone = ? where key = ?")
        void updatePhones_positional(String homePhone, String workPhone, int key);

        /**
         * Standard named markers
         */
        @Query("update user set home_phone = :home, work_phone = :work where key = :key")
        void updatePhones_named(@Param("key") int key, @Param("home") String homePhone, @Param("work") String workPhone);

        /**
         * Named markers with no @Param. Should fallback to positional matching
         */
        @Query("update user set home_phone = :home, work_phone = :work where key = :key")
        void updatePhones_fallback(String homePhone, String workPhone, int key);

        /**
         * Named markers with repeated names
         */
        @Query("update user set home_phone = :phone, work_phone = :phone where key = :key")
        void updateBothPhones(@Param("key") int key, @Param("phone") String uniquePhone);

        /**
         * Named markers with repeated names, but no @Param. Should fallback to positional matching
         */
        @Query("update user set home_phone = :phone, work_phone = :phone where key = :key")
        void updatePhones_fallback2(String homePhone, String workPhone, int key);
    }

    @Accessor
    public interface UserPhoneAccessor_NotEnoughParams {
        @SuppressWarnings("unused")
        @Query("update user set home_phone = :phone, work_phone = :phone where key = :key")
        void updateBothPhones(@Param("key") int key);
    }

    @Accessor
    public interface UserPhoneAccessor_TooManyParams {
        @SuppressWarnings("unused")
        @Query("update user set home_phone = ?, work_phone = ? where key = ?")
        void updatePhones(String homePhone, String workPhone, int key, int extra);
    }

    @Table(name = "user")
    public static class User {
        @PartitionKey
        private int key;

        public User() {
        }

        public User(int k) {
            this.key = k;
        }

        public int getKey() {
            return this.key;
        }

        public void setKey(int pk) {
            this.key = pk;
        }

    }

    @Table(name = "user")
    public static class UserStr {
        @PartitionKey
        private int key;

        private Enum gender;

        public UserStr() {
        }

        public UserStr(int k, Enum val) {
            this.key = k;
            this.gender = val;
        }

        public int getKey() {
            return this.key;
        }

        public void setKey(int pk) {
            this.key = pk;
        }

        public Enum getGender() {
            return this.gender;
        }

        public void setGender(Enum val) {
            this.gender = val;
        }
    }

    @Accessor
    public interface UserPhoneAccessor_WrongParameterTypes {
        @Query("select * from user where key IN (?)")
            // WRONG, should be "where key IN ?"
        void findUsersBykeys(List<Integer> keys);
    }

}
