package cz.moneta.test.dsl.util.task.cvm.onlinedb;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import cz.moneta.test.dsl.Harness;
import org.jooq.Record;
import org.jooq.Result;
import org.junit.Assert;

import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.function.Consumer;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class OnlineDbVerificationTasks {

    private Harness harness;

    public OnlineDbVerificationTasks(Harness harness) {
        this.harness = harness;
    }

    public String assertOnlineDbRecordIsValid(
            final Record onlineDbRecord,
            final String expectedTaskName,
            final String expectedRtdmWso2EndpointUrl,
            final int expectedDelayInSeconds,
            final Consumer<JsonObject> checkDataContent) {
        assertEquals("Event should be of type " + expectedTaskName,
                expectedTaskName, onlineDbRecord.getValue("TASK_NAME"));

        String activityId = "";
        try {
            String taskData = new String((byte[]) onlineDbRecord.getValue("TASK_DATA"));
            JsonObject taskDataJson = new Gson().fromJson(taskData, JsonObject.class);
            activityId = taskDataJson.get("activityId").getAsString();
            JsonObject data = taskDataJson.getAsJsonObject("data");
            assertEquals("Event data should contain correct target WSO2 endpoint to call in future",
                    expectedRtdmWso2EndpointUrl, data.get("rtdmWso2EndpointUrl").getAsString());

            checkDataContent.accept(data);
        } catch (JsonParseException | ClassCastException ex) {
            fail("Task data should be valid structured JSON: " + ex);
        }

        final ZonedDateTime expectedExecutionTime = ZonedDateTime.now().withFixedOffsetZone().plusSeconds(expectedDelayInSeconds);
        ZonedDateTime expectedShiftedExecutionTime = harness.tasks().cvm().onlineDbGetData()
                .getNearestContactCenterWorkingHours(expectedExecutionTime);

        ZonedDateTime executionTime = ZonedDateTime.parse((String) onlineDbRecord.getValue("EXECUTION_TIME"));
        if (expectedShiftedExecutionTime.equals(expectedExecutionTime)) {
            assertTrue("Event should be planned in " + expectedExecutionTime + " (+/- 10 seconds) but was in " + executionTime,
                    Math.abs(executionTime.toEpochSecond() -
                            expectedExecutionTime.toEpochSecond()) < 10);
        } else {
            final ZonedDateTime expectedShiftedExecutionTimeMax = expectedShiftedExecutionTime.plusMinutes(30);
            assertTrue("Event should be planned between " + expectedShiftedExecutionTime + " and " + expectedShiftedExecutionTimeMax +
                            "  but was in " + executionTime,
                    expectedShiftedExecutionTime.compareTo(executionTime) <= 0
                            && executionTime.compareTo(expectedShiftedExecutionTimeMax) <= 0);

        }

        return activityId;
    }

    public void assertTriggerLogRecordIsPlanned(final Record triggerLogRecord,
                                                final Record onlineDbRecordPattern,
                                                final String expectedWso2TargetEndpointName,
                                                final Consumer<String> checkLogData) {
        assertEquals("Planned call to correct WSO2 API should be logged",
                expectedWso2TargetEndpointName, triggerLogRecord.getValue("WSO2_TARGET_ENDPOINT_NAME"));
        assertEquals("Logged planed execution time should be same with the planned event",
                onlineDbRecordPattern.getValue("EXECUTION_TIME"), triggerLogRecord.getValue("EXECUTION_TIME"));
        assertEquals("Logged task name should be same with the planned event",
                onlineDbRecordPattern.getValue("TASK_NAME"), triggerLogRecord.getValue("TASK_NAME"));
        assertEquals("Logged task instance should be same with the planned event",
                onlineDbRecordPattern.getValue("TASK_INSTANCE"), triggerLogRecord.getValue("TASK_INSTANCE"));

        ZonedDateTime planningTime = ZonedDateTime.parse((String) triggerLogRecord.getValue("PLANNING_TIME"));
        assertTrue("Event should be planned in last 30 seconds, but was in " + planningTime,
                Instant.now().getEpochSecond() >= planningTime.toEpochSecond()
                        && Instant.now().getEpochSecond() - planningTime.toEpochSecond() <= 30);
        assertNull("Planned call should not been executed yet",
                triggerLogRecord.getValue("SUCCEEDED_TIME"));
        assertNull("Planned call should not been failed",
                triggerLogRecord.getValue("FAILED_TIME"));
        assertNull("Planned call should not been canceled",
                triggerLogRecord.getValue("CANCELED_TIME"));

        checkLogData.accept(triggerLogRecord.getValue("DATA").toString());
    }

    public void assertTriggerLogRecordSucceeded(final Record triggerLogRecord,
                                                final String expectedWso2TargetEndpointName,
                                                ZonedDateTime rtdmEventDateTime) {
        assertEquals("Planned call to correct WSO2 API should be logged",
                expectedWso2TargetEndpointName, triggerLogRecord.getValue("WSO2_TARGET_ENDPOINT_NAME"));

        ZonedDateTime succeededTime = ZonedDateTime.parse((String) triggerLogRecord.getValue("SUCCEEDED_TIME"));
        assertTrue("It should succeeded in last day, but was in " + succeededTime,
                Instant.now().getEpochSecond() >= succeededTime.toEpochSecond()
                        && Instant.now().getEpochSecond() - succeededTime.toEpochSecond() <= 24 * 60 * 60);
        assertTrue("Date/time of succeeded event and trigger log in RTRD should be similar, but was "
                        + rtdmEventDateTime + " in event vs " + succeededTime + " in trigger log",
                Math.abs(rtdmEventDateTime.toEpochSecond() - succeededTime.toEpochSecond()) < 20);


        ZonedDateTime executionTime = ZonedDateTime.parse((String) triggerLogRecord.getValue("EXECUTION_TIME"));
        assertTrue("Event was planned to execute max 1 minute before succeeded time, but succeeded in " + succeededTime +
                        " and executed in " + executionTime,
                succeededTime.toEpochSecond() >= executionTime.toEpochSecond()
                        && succeededTime.toEpochSecond() - executionTime.toEpochSecond() <= 60);

        assertNull("Planned call should not been failed",
                triggerLogRecord.getValue("FAILED_TIME"));
        assertNull("Planned call should not been canceled",
                triggerLogRecord.getValue("CANCELED_TIME"));
    }

    public void assertLogRecordsPlannedToMondayMorning(String expectedWso2TargetEndpoint,
                                                       Record triggerLogRecord,
                                                       Boolean shouldBePlannedInLastFiveMinutes) {
        Assert.assertEquals("Planned call to correct WSO2 API should be logged",
                expectedWso2TargetEndpoint, triggerLogRecord.getValue("WSO2_TARGET_ENDPOINT_NAME"));

        ZonedDateTime executionTime = ZonedDateTime.parse((String) triggerLogRecord.getValue("EXECUTION_TIME"));
        ZonedDateTime expectedExecutionTime = harness.tasks().cvm().onlineDbGetData().getNearestContactCenterWorkingHours(
                ZonedDateTime.now().withFixedOffsetZone());
        final ZonedDateTime expectedExecutionTimeMax = expectedExecutionTime.plusMinutes(30);
        assertTrue("Planned time should be next monday morning (or nearest working day) between "
                        + expectedExecutionTime + " and  " + expectedExecutionTimeMax + ", but was in " + executionTime,
                expectedExecutionTime.compareTo(executionTime) <= 0 && executionTime.compareTo(expectedExecutionTimeMax) <= 0);

        ZonedDateTime planningTime = ZonedDateTime.parse((String) triggerLogRecord.getValue("PLANNING_TIME"));
        if (shouldBePlannedInLastFiveMinutes) {
            assertTrue("Event should been planned in last five minutes, but was in " + planningTime,
                    ZonedDateTime.now().toEpochSecond() - planningTime.toEpochSecond() < 5 * 60);
        } else {
            assertTrue("Event should been planned before last five minutes, but was in " + planningTime,
                    ZonedDateTime.now().toEpochSecond() - planningTime.toEpochSecond() > 5 * 60);
        }
    }

    public void assertOnlineDbRecordFindSubMessageAndCheckAttribute(Result<Record> records,
                                                                    String findAttributeName,
                                                                    String findAttributeValue,
                                                                    String checkAttributeName,
                                                                    String checkAttributeValue) {
        Optional<Object> subMessageId = records.stream()
                .filter(record -> findAttributeName.equals(record.getValue("NAME"))
                        && findAttributeValue.equals(record.getValue("VAL")))
                .map(record -> record.getValue("MSG_ID"))
                .findAny();

        assertTrue(String.format("There should be message id of record with NAME = %s and VAL = %s", findAttributeName, findAttributeValue),
                subMessageId.orElse(null) instanceof String);

        if (records.stream().noneMatch(record -> subMessageId.get().equals(record.getValue("MSG_ID"))
                && checkAttributeName.equals(record.getValue("NAME"))
                && checkAttributeValue.equals(record.getValue("VAL")))) {
            fail(String.format("Found MSG_ID %s should have record with NAME = %s and VAL = %s",
                    subMessageId.get(), checkAttributeName, checkAttributeValue));
        }
    }
}
