/*
 * 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.iotdb.db.queryengine.plan.statement;

import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.db.exception.sql.SemanticException;
import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator;
import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement;

import org.apache.tsfile.utils.Pair;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;

import static org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement.RAW_AGGREGATION_HYBRID_QUERY_ERROR_MSG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

public class QueryStatementTest {

  private static final Logger logger = LoggerFactory.getLogger(QueryStatementTest.class);

  private static final String ALIGN_BY_DEVICE_ONE_LEVEL_ERROR =
      "ALIGN BY DEVICE: the suffix paths can only be measurement or one-level wildcard";

  @Test
  public void semanticCheckTest() {
    List<Pair<String, String>> errorSqlWithMessages =
        Arrays.asList(
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 "
                    + "GROUP BY ([2017-11-01T00:00:00, 2017-11-07T23:00:00),1d)",
                "Common queries and aggregated queries are not allowed to appear at the same time"),
            new Pair<>(
                "SELECT count(s1),s2 FROM root.sg.d1", RAW_AGGREGATION_HYBRID_QUERY_ERROR_MSG),

            // test for where clause
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 WHERE count(s1) > 0",
                "aggregate functions are not supported in WHERE clause"),

            // test for having clause
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 HAVING(s1 > 0)",
                "Expression of HAVING clause must to be an Aggregation"),
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 HAVING(count(s1) > 0)",
                "Expression of HAVING clause can not be used in NonAggregationQuery"),
            new Pair<>(
                "SELECT count(d1.s1) FROM root.sg.d1 GROUP BY level=1 HAVING (count(s1) > 0)",
                "When Having used with GroupByLevel: "
                    + "the suffix paths can only be measurement or one-level wildcard"),
            new Pair<>(
                "SELECT count(s1) FROM root.sg.d1 GROUP BY level=1 HAVING (count(sg.d1.s1) > 0)",
                "When Having used with GroupByLevel: "
                    + "the suffix paths can only be measurement or one-level wildcard"),

            // test for align by device clause
            new Pair<>(
                "SELECT d1.s1 FROM root.sg.d1 align by device", ALIGN_BY_DEVICE_ONE_LEVEL_ERROR),
            new Pair<>(
                "SELECT count(s1) FROM root.sg.d1 group by variation(sg.s1) align by device",
                ALIGN_BY_DEVICE_ONE_LEVEL_ERROR),
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 order by root.sg.d1.s1 align by device",
                ALIGN_BY_DEVICE_ONE_LEVEL_ERROR),
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 where root.sg.d1.s1 > 0 align by device",
                ALIGN_BY_DEVICE_ONE_LEVEL_ERROR),
            new Pair<>(
                "SELECT count(s1) FROM root.sg.d1 having(count(root.sg.d1.s1) > 0) align by device",
                ALIGN_BY_DEVICE_ONE_LEVEL_ERROR),
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 order by timeseries align by device",
                "Sorting by timeseries is only supported in last queries."),

            // test for last query
            new Pair<>(
                "SELECT last s1 FROM root.sg.d1 align by device",
                "Last query doesn't support align by device."),
            new Pair<>(
                "SELECT last s1+s2 FROM root.sg.d1",
                "Last queries can only be applied on raw time series."),
            new Pair<>(
                "SELECT last s1 FROM root.sg.d1 order by device",
                "Sorting by device is only supported in ALIGN BY DEVICE queries."),
            new Pair<>(
                "SELECT last s1 FROM root.sg.d1 SLIMIT 1 SOFFSET 2",
                "SLIMIT and SOFFSET can not be used in LastQuery."),

            // test for select into clause
            new Pair<>(
                "SELECT s1 INTO root.sg.d2(t1) FROM root.sg.d1 SLIMIT 5",
                "select into: slimit clauses are not supported."),
            new Pair<>(
                "SELECT s1 INTO root.sg.d2(t1) FROM root.sg.d1 SOFFSET 6",
                "select into: soffset clauses are not supported."),
            new Pair<>(
                "SELECT last s1 INTO root.sg.d2(t1) FROM root.sg.d1",
                "select into: last clauses are not supported."),
            new Pair<>(
                "SELECT count(s1) INTO root.sg.d2(t1) FROM root.sg.d1 GROUP BY TAGS(a)",
                "select into: GROUP BY TAGS clause are not supported."),
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 order by timeseries",
                "Sorting by timeseries is only supported in last queries."),
            new Pair<>(
                "SELECT s1 FROM root.sg.d1 order by device",
                "Sorting by device is only supported in ALIGN BY DEVICE queries."));

    for (Pair<String, String> pair : errorSqlWithMessages) {
      String errorSql = pair.left;
      String errorMsg = pair.right;
      try {
        checkErrorQuerySql(errorSql);
      } catch (SemanticException e) {
        assertEquals(errorMsg, e.getMessage());
        continue;
      } catch (Exception ex) {
        fail(String.format("Meets exception %s in test sql: `%s`", errorMsg, errorSql));
      }
      fail(String.format("Sql: `%s` must throw exception: %s", errorSql, errorMsg));
    }
  }

  @Test
  public void getPathsTest() {
    String sql = "SELECT count(s1 + s3) FROM root.sg.d1 WHERE s2 > 0 and time < 1000";
    QueryStatement statement =
        (QueryStatement) StatementGenerator.createStatement(sql, ZonedDateTime.now().getOffset());
    List<PartialPath> fullPaths = statement.getPaths();
    assertEquals(2, fullPaths.size());
    assertEquals("root.sg.d1.s1", fullPaths.get(0).getFullPath());
    assertEquals("root.sg.d1.s3", fullPaths.get(1).getFullPath());
  }

  private void checkErrorQuerySql(String sql) {
    QueryStatement statement =
        (QueryStatement) StatementGenerator.createStatement(sql, ZonedDateTime.now().getOffset());
    statement.semanticCheck();
  }
}
