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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.QueryTrace;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TupleType;
import com.datastax.driver.core.TupleValue;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import org.apache.cassandra.tracing.TraceStateImpl;

import static org.junit.Assert.assertEquals;

public class TraceCqlTest extends CQLTester
{
    static int DEFAULT_WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS;

    @BeforeClass
    public static void setUp()
    {
        // make sure we wait for trace events to complete, see CASSANDRA-12754
        DEFAULT_WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS = TraceStateImpl.WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS;
        TraceStateImpl.WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS = 5;
    }

    @AfterClass
    public static void tearDown()
    {
        TraceStateImpl.WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS = DEFAULT_WAIT_FOR_PENDING_EVENTS_TIMEOUT_SECS;
    }

    @Test
    public void testCqlStatementTracing() throws Throwable
    {
        requireNetwork();

        createTable("CREATE TABLE %s (id int primary key, v1 text, v2 text)");
        execute("INSERT INTO %s (id, v1, v2) VALUES (?, ?, ?)", 1, "Apache", "Cassandra");
        execute("INSERT INTO %s (id, v1, v2) VALUES (?, ?, ?)", 2, "trace", "test");

        try (Session session = sessionNet())
        {
            String cql = "SELECT id, v1, v2 FROM " + KEYSPACE + '.' + currentTable() + " WHERE id = ?";
            PreparedStatement pstmt = session.prepare(cql)
                                             .enableTracing();
            QueryTrace trace = session.execute(pstmt.bind(1)).getExecutionInfo().getQueryTrace();
            assertEquals(cql, trace.getParameters().get("query"));

            assertEquals("1", trace.getParameters().get("bound_var_0_id"));

            String cql2 = "SELECT id, v1, v2 FROM " + KEYSPACE + '.' + currentTable() + " WHERE id IN (?, ?, ?)";
            pstmt = session.prepare(cql2).enableTracing();
            trace = session.execute(pstmt.bind(19, 15, 16)).getExecutionInfo().getQueryTrace();
            assertEquals(cql2, trace.getParameters().get("query"));
            assertEquals("19", trace.getParameters().get("bound_var_0_id"));
            assertEquals("15", trace.getParameters().get("bound_var_1_id"));
            assertEquals("16", trace.getParameters().get("bound_var_2_id"));

            //some more complex tests for tables with map and tuple data types and long bound values
            createTable("CREATE TABLE %s (id int primary key, v1 text, v2 tuple<int, text, float>, v3 map<int, text>)");
            execute("INSERT INTO %s (id, v1, v2, v3) values (?, ?, ?, ?)", 12, "mahdix", tuple(3, "bar", 2.1f),
                    map(1290, "birthday", 39, "anniversary"));
            execute("INSERT INTO %s (id, v1, v2, v3) values (?, ?, ?, ?)", 274, "CassandraRocks", tuple(9, "foo", 3.14f),
                    map(9181, "statement", 716, "public speech"));

            cql = "SELECT id, v1, v2, v3 FROM " + KEYSPACE + '.' + currentTable() + " WHERE v2 = ? ALLOW FILTERING";
            pstmt = session.prepare(cql)
                           .enableTracing();
            TupleType tt = TupleType.of(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE, DataType.cint(),
                                        DataType.text(), DataType.cfloat());
            TupleValue value = tt.newValue();
            value.setInt(0, 3);
            value.setString(1, "bar");
            value.setFloat(2, 2.1f);

            trace = session.execute(pstmt.bind(value)).getExecutionInfo().getQueryTrace();
            assertEquals(cql, trace.getParameters().get("query"));
            assertEquals("(3, 'bar', 2.1)", trace.getParameters().get("bound_var_0_v2"));

            cql2 = "SELECT id, v1, v2, v3 FROM " + KEYSPACE + '.' + currentTable() + " WHERE v3 CONTAINS KEY ? ALLOW FILTERING";
            pstmt = session.prepare(cql2).enableTracing();
            trace = session.execute(pstmt.bind(9181)).getExecutionInfo().getQueryTrace();

            assertEquals(cql2, trace.getParameters().get("query"));
            assertEquals("9181", trace.getParameters().get("bound_var_0_key(v3)"));

            String boundValue = "Indulgence announcing uncommonly met she continuing two unpleasing terminated. Now " +
                                "busy say down the shed eyes roof paid her. Of shameless collected suspicion existence " +
                                "in. Share walls stuff think but the arise guest. Course suffer to do he sussex it " +
                                "window advice. Yet matter enable misery end extent common men should. Her indulgence " +
                                "but assistance favourable cultivated everything collecting." +
                                "On projection apartments unsatiable so if he entreaties appearance. Rose you wife " +
                                "how set lady half wish. Hard sing an in true felt. Welcomed stronger if steepest " +
                                "ecstatic an suitable finished of oh. Entered at excited at forming between so " +
                                "produce. Chicken unknown besides attacks gay compact out you. Continuing no " +
                                "simplicity no favourable on reasonably melancholy estimating. Own hence views two " +
                                "ask right whole ten seems. What near kept met call old west dine. Our announcing " +
                                "sufficient why pianoforte. Full age foo set feel her told. Tastes giving in passed" +
                                "direct me valley as supply. End great stood boy noisy often way taken short. Rent the " +
                                "size our more door. Years no place abode in \uFEFFno child my. Man pianoforte too " +
                                "solicitude friendship devonshire ten ask. Course sooner its silent but formal she " +
                                "led. Extensive he assurance extremity at breakfast. Dear sure ye sold fine sell on. " +
                                "Projection at up connection literature insensible motionless projecting." +
                                "Nor hence hoped her after other known defer his. For county now sister engage had " +
                                "season better had waited. Occasional mrs interested far expression acceptance. Day " +
                                "either mrs talent pulled men rather regret admire but. Life ye sake it shed. Five " +
                                "lady he cold in meet up. Service get met adapted matters offence for. Principles man " +
                                "any insipidity age you simplicity understood. Do offering pleasure no ecstatic " +
                                "whatever on mr directly. ";

            String cql3 = "SELECT id, v1, v2, v3 FROM " + KEYSPACE + '.' + currentTable() + " WHERE v3 CONTAINS ? ALLOW FILTERING";
            pstmt = session.prepare(cql3).enableTracing();
            trace = session.execute(pstmt.bind(boundValue)).getExecutionInfo().getQueryTrace();

            assertEquals(cql3, trace.getParameters().get("query"));

            //when tracing is done, this boundValue will be surrounded by single quote, and first 1000 characters
            //will be filtered. Here we take into account single quotes by adding them to the expected output
            assertEquals("'" + boundValue.substring(0, 999) + "...'", trace.getParameters().get("bound_var_0_value(v3)"));

            pstmt = session.prepare("INSERT INTO " + KEYSPACE + '.' + currentTable() + " (id, v1, v2, v3) values (?, ?, ?, ?)").enableTracing();
            BoundStatement boundStatement = pstmt.bind(13, "lukasz", value, map(2024, "birthday", 40, "anniversary"));

            boundStatement.unset(3); // test query tracing after UNSET collection type
            trace = session.execute(boundStatement).getExecutionInfo().getQueryTrace();
            Map<String, String> boundParameters = getBoundParameters(trace);
            assertEquals(Arrays.asList("13", "'lukasz'", "(3, 'bar', 2.1)", "<unset>"), new ArrayList<>(boundParameters.values()));

            boundStatement.unset(2); // test query tracing after UNSET tuple type
            trace = session.execute(boundStatement).getExecutionInfo().getQueryTrace();
            boundParameters = getBoundParameters(trace);
            assertEquals(Arrays.asList("13", "'lukasz'", "<unset>", "<unset>"), new ArrayList<>(boundParameters.values()));
        }
    }

    private Map<String, String> getBoundParameters(QueryTrace trace) {
        Map<String, String> boundParameters = new LinkedHashMap<>();
        trace.getParameters().forEach((paramName, paramValue) -> {
            if (paramName.startsWith("bound_")) {
                boundParameters.put(paramName, paramValue);
            }
        });
        return boundParameters;
    }
}
