/*
 * 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.citrusframework.yaks.jdbc;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.consol.citrus.Citrus;
import com.consol.citrus.TestCaseRunner;
import com.consol.citrus.actions.ExecuteSQLQueryAction;
import com.consol.citrus.annotations.CitrusFramework;
import com.consol.citrus.annotations.CitrusResource;
import com.consol.citrus.exceptions.CitrusRuntimeException;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;

import static com.consol.citrus.actions.ExecuteSQLAction.Builder.sql;
import static com.consol.citrus.actions.ExecuteSQLQueryAction.Builder.query;

/**
 * @author Christoph Deppisch
 */
public class JdbcSteps {

    @CitrusResource
    private TestCaseRunner runner;

    @CitrusFramework
    private Citrus citrus;

    private DataSource dataSource;
    private List<String> sqlQueryStatements = new ArrayList<>();

    @Before
    public void before(Scenario scenario) {
        if (dataSource == null && citrus.getCitrusContext().getReferenceResolver().resolveAll(DataSource.class).size() == 1L) {
            dataSource = citrus.getCitrusContext().getReferenceResolver().resolve(DataSource.class);
        }
    }

    @Given("^(?:D|d)ata source: ([^\"\\s]+)$")
    public void setDataSource(String id) {
        if (!citrus.getCitrusContext().getReferenceResolver().isResolvable(id)) {
            throw new CitrusRuntimeException("Unable to find data source for id: " + id);
        }

        dataSource = citrus.getCitrusContext().getReferenceResolver().resolve(id, DataSource.class);
    }

    @Given("^(?:D|d)atabase connection$")
    public void setConnection(DataTable properties) {
        Map<String, String> connectionProps = properties.asMap(String.class, String.class);

        String driver = connectionProps.getOrDefault("driver", "org.postgresql.Driver");
        String url = connectionProps.getOrDefault("url", "jdbc:postgresql://localhost:5432/testdb");
        String username = connectionProps.getOrDefault("username", "test");
        String password = connectionProps.getOrDefault("password", "test");
        boolean suppressClose = Boolean.parseBoolean(connectionProps.getOrDefault("suppressClose", Boolean.TRUE.toString()));

        SingleConnectionDataSource singleConnectionDataSource = new SingleConnectionDataSource(url, username, password, suppressClose);
        singleConnectionDataSource.setDriverClassName(driver);
        this.dataSource = singleConnectionDataSource;
    }

    @Given("^SQL query: (.+)$")
    public void addQueryStatement(String statement) {
        if (statement.trim().toUpperCase().startsWith("SELECT")) {
            sqlQueryStatements.add(statement);
        } else {
            throw new CitrusRuntimeException("Invalid SQL query - please use proper 'SELECT' statement");
        }
    }

    @Given("^SQL query statements:$")
    public void addQueryStatements(DataTable statements) {
        statements.asList().forEach(this::addQueryStatement);
    }

    @Then("^verify column ([^\"\\s]+)=(.+)$")
    public void verifyColumn(String name, String value) {
        runner.run(query(dataSource)
                                     .statements(sqlQueryStatements)
                                     .validate(name, value));
        sqlQueryStatements.clear();
    }

    @Then("^verify columns$")
    public void verifyResultSet(DataTable expectedResults) {
        ExecuteSQLQueryAction.Builder action = query(dataSource)
                                                    .statements(sqlQueryStatements);

        List<List<String>> rows = expectedResults.asLists(String.class);
        rows.forEach(row -> {
            if (!row.isEmpty()) {
                String columnName = row.remove(0);
                action.validate(columnName, row.toArray(new String[]{}));
            }
        });

        runner.run(action);
        sqlQueryStatements.clear();
    }

    @Then("^verify result set$")
    public void verifyResultSet(String verifyScript) {
        runner.run(query(dataSource)
                                     .statements(sqlQueryStatements)
                                     .groovy(verifyScript));

        sqlQueryStatements.clear();
    }

    @When("^(?:execute |perform )?SQL update: (.+)$")
    public void executeUpdate(String statement) {
        if (statement.trim().toUpperCase().startsWith("SELECT")) {
            throw new CitrusRuntimeException("Invalid SQL update statement - please use SQL query for 'SELECT' statements");
        } else {
            runner.run(sql(dataSource).statement(statement));
        }

    }

    @When("^(?:execute |perform )?SQL update$")
    public void executeUpdateMultiline(String statement) {
        executeUpdate(statement);
    }

    @When("^(?:execute |perform )?SQL updates$")
    public void executeUpdates(DataTable statements) {
        statements.asList().forEach(this::executeUpdate);
    }
}
