/*
 * Sakuli - Testing and Monitoring-Tool for Websites and common UIs.
 *
 * Copyright 2013 - 2016 the original author or authors.
 *
 * 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 org.sakuli.services.forwarder;

import org.apache.commons.lang.StringUtils;
import org.sakuli.datamodel.TestCase;
import org.sakuli.datamodel.TestCaseStep;
import org.sakuli.datamodel.TestSuite;
import org.sakuli.datamodel.state.TestCaseState;
import org.sakuli.datamodel.state.TestSuiteState;
import org.sakuli.exceptions.SakuliRuntimeException;
import org.sakuli.services.forwarder.gearman.TextPlaceholder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.SortedSet;
import java.util.stream.Collectors;

import static org.sakuli.services.forwarder.gearman.TextPlaceholder.*;

/**
 * @author tschneck
 *         Date: 2/24/16
 */
public abstract class AbstractOutputBuilder {
    public final static SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.YY HH:mm:ss");
    protected Logger LOGGER = LoggerFactory.getLogger(getClass());

    @Autowired
    protected ScreenshotDivConverter screenshotDivConverter;

    public static String replacePlaceHolder(String message, PlaceholderMap placeholderStringMap) {
        if (StringUtils.isBlank(message)) {
            throw new SakuliRuntimeException("Template for resolving test suite output is EMPTY!");
        }
        String modifiedString = message;
        for (TextPlaceholder key : placeholderStringMap.keySet()) {
            modifiedString = StringUtils.replace(modifiedString, key.getPattern(), placeholderStringMap.get(key));
        }
        //check if still placeholders can be resolved
        for (TextPlaceholder placeholder : placeholderStringMap.keySet()) {
            if (StringUtils.contains(modifiedString, placeholder.getPattern())) {
                return replacePlaceHolder(modifiedString, placeholderStringMap);
            }
        }
        return modifiedString;
    }

    static String generateStepInformation(SortedSet<TestCaseStep> steps) {
        StringBuilder sb = new StringBuilder();
        steps.stream().filter(step -> step.getState().isWarning() || step.getState().isError())
                .forEach(step -> {
                    sb.append(", step \"").append(step.getName()).append("\" ");
                    if (step.getState().isError()) {
                        sb.append("EXCEPTION: ").append(step.getExceptionMessages(true));
                    } else {
                        sb.append("over runtime (")
                                .append(TimeFormatter.formatToSec(step.getDuration()))
                                .append("/warn at ")
                                .append(TimeFormatter.formatToSec(step.getWarningTime()))
                                .append(")");
                    }
                });
        return sb.toString();
    }

    static String generateCaseInformation(SortedSet<TestCase> cases) {
        StringBuilder sb = new StringBuilder();
        cases.stream().filter(c -> c.getState().isWarning() || c.getState().isCritical() || c.getState().isError())
                .forEach(c -> {
                    sb.append(", case \"").append(c.getName()).append("\" ");
                    if (c.getState().isError()) {
                        sb.append("EXCEPTION: ").append(c.getExceptionMessages(true));
                    } else {
                        sb.append("over runtime (").append(TimeFormatter.formatToSec(c.getDuration()));
                        if (c.getState().isCritical()) {
                            sb.append("/crit at ").append(TimeFormatter.formatToSec(c.getCriticalTime()));
                        } else {
                            sb.append("/warn at ").append(TimeFormatter.formatToSec(c.getWarningTime()));
                        }
                        sb.append(")");
                    }
                });
        return sb.toString();
    }

    protected static String cutTo(String string, int summaryMaxLength) {
        if (string != null && string.length() > summaryMaxLength) {
            return StringUtils.substring(string, 0, summaryMaxLength) + " ...";
        }
        return string;
    }

    protected abstract int getSummaryMaxLength();

    protected abstract String getOutputScreenshotDivWidth();

    protected String formatTestSuiteSummaryStateMessage(TestSuite testSuite, String templateSuiteSummary) {
        if (StringUtils.isBlank(templateSuiteSummary)) {
            throw new SakuliRuntimeException("Template for resolving test suite output is EMPTY!");
        }
        String result = replacePlaceHolder(templateSuiteSummary, getTextPlaceholder(testSuite));
        LOGGER.debug("{{xxx}} patterns in template '{}' replaced with message '{}'", templateSuiteSummary, result);
        return cutTo(result, getSummaryMaxLength());
    }

    protected PlaceholderMap getTextPlaceholder(TestSuite testSuite) {
        PlaceholderMap placeholderMap = new PlaceholderMap();
        OutputState outputState = OutputState.lookupSakuliState(testSuite.getState());
        ScreenshotDiv screenshotDiv = screenshotDivConverter.convert(testSuite.getException());
        placeholderMap.put(STATE, outputState.name());
        placeholderMap.put(STATE_SHORT, outputState.getShortState());
        placeholderMap.put(STATE_DESC, testSuite.getState().getNagiosStateDescription());
        placeholderMap.put(SUITE_SUMMARY, generateStateSummary(testSuite.getState()));
        placeholderMap.put(NAME, testSuite.getName());
        placeholderMap.put(ID, testSuite.getId());
        placeholderMap.put(DURATION, String.format(Locale.ENGLISH, "%.2f", testSuite.getDuration()));
        placeholderMap.put(START_DATE, (testSuite.getStartDate() == null) ? "xx" : dateFormat.format(testSuite.getStartDate()));
        placeholderMap.put(STOP_DATE, (testSuite.getStopDate() == null) ? "xx" : dateFormat.format(testSuite.getStopDate()));
        placeholderMap.put(WARN_THRESHOLD, String.valueOf(testSuite.getWarningTime()));
        placeholderMap.put(CRITICAL_THRESHOLD, String.valueOf(testSuite.getCriticalTime()));
        placeholderMap.put(ERROR_SCREENSHOT, screenshotDiv != null ? screenshotDiv.getPayloadString() : null);
        placeholderMap.put(ERROR_MESSAGE, testSuite.getExceptionMessages(true));
        placeholderMap.put(SUITE_FOLDER, testSuite.getTestSuiteFolder() != null ? testSuite.getTestSuiteFolder().toString() : null);
        placeholderMap.put(HOST, testSuite.getHost());
        placeholderMap.put(BROWSER_INFO, testSuite.getBrowserInfo());
        placeholderMap.put(TD_CSS_CLASS, "service" + outputState.name());
        placeholderMap.put(CASE_INFORMATION, generateCaseInformation(testSuite.getTestCasesAsSortedSet()));
        placeholderMap.put(STEP_INFORMATION,
                testSuite.getTestCasesAsSortedSet().stream()
                        .map(c -> generateStepInformation(c.getStepsAsSortedSet()))
                        .collect(Collectors.joining()));
        return placeholderMap;
    }

    private String  generateStateSummary(TestSuiteState state) {
        StringBuilder summary = new StringBuilder(state.isError() ? "" : STATE_DESC.getPattern());
        switch (state) {
            case OK:
                summary.append(" (").append(DURATION.getPattern()).append("s)");
                break;
            case WARNING_IN_STEP:
                summary.append(STEP_INFORMATION.getPattern());
                break;
            case WARNING_IN_CASE:
                summary.append(CASE_INFORMATION.getPattern());
                break;
            case WARNING_IN_SUITE:
                summary.append(" (").append(DURATION.getPattern()).append("s/warn at ").append(WARN_THRESHOLD.getPattern()).append("s)");
                break;
            case CRITICAL_IN_CASE:
                summary.append(CASE_INFORMATION.getPattern());
                break;
            case CRITICAL_IN_SUITE:
                summary.append(" (").append(DURATION.getPattern()).append("s/crit at ").append(CRITICAL_THRESHOLD.getPattern()).append("s)");
                break;
            case ERRORS:
                summary.append("(").append(DURATION.getPattern()).append("s) ").append(STATE_DESC.getPattern()).append(": '").append(ERROR_MESSAGE.getPattern()).append("'");
                break;
            default:
                break;
        }
        return summary.toString();
    }

    protected PlaceholderMap getTextPlaceholder(TestCase testCase) {
        PlaceholderMap placeholderMap = new PlaceholderMap();
        OutputState outputState = OutputState.lookupSakuliState(testCase.getState());
        placeholderMap.put(STATE, outputState.name());
        placeholderMap.put(STATE_SHORT, outputState.getShortState());
        placeholderMap.put(STATE_DESC, (testCase.getState() == null)
                ? TestCaseState.ERRORS.getNagiosStateDescription()
                : testCase.getState().getNagiosStateDescription());
        placeholderMap.put(NAME, testCase.getName());
        placeholderMap.put(ID, testCase.getId());
        placeholderMap.put(DURATION, String.format(Locale.ENGLISH, "%.2f", testCase.getDuration()));
        placeholderMap.put(START_DATE, (testCase.getStartDate() == null) ? "xx" : dateFormat.format(testCase.getStartDate()));
        placeholderMap.put(STOP_DATE, (testCase.getStopDate() == null) ? "xx" : dateFormat.format(testCase.getStopDate()));
        placeholderMap.put(WARN_THRESHOLD, String.valueOf(testCase.getWarningTime()));
        placeholderMap.put(CRITICAL_THRESHOLD, String.valueOf(testCase.getCriticalTime()));
        placeholderMap.put(ERROR_MESSAGE, testCase.getExceptionMessages(true));
        placeholderMap.put(ERROR_SCREENSHOT, generateTestCaseScreenshotsHTML(testCase));
        placeholderMap.put(STEP_INFORMATION, generateStepInformation(testCase.getStepsAsSortedSet()));
        placeholderMap.put(CASE_FILE, testCase.getTcFile() != null ? testCase.getTcFile().toString() : null);
        placeholderMap.put(CASE_START_URL, testCase.getStartUrl());
        placeholderMap.put(CASE_LAST_URL, testCase.getLastURL());
        placeholderMap.put(TD_CSS_CLASS, "service" + outputState.name());
        return placeholderMap;
    }

    /**
     * Generates '<div></div>' tag for the screenshots included in the {@link TestCase} and the suppressed {@link TestCaseStep}s.
     */
    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    protected String generateTestCaseScreenshotsHTML(TestCase testCase) {
        StringBuilder sb = new StringBuilder();
        ScreenshotDiv caseDiv = screenshotDivConverter.convert(testCase.getException());
        if (caseDiv != null) {
            sb.append(caseDiv.getPayloadString());
        }
        for (TestCaseStep step : testCase.getStepsAsSortedSet()) {
            ScreenshotDiv stepDiv = screenshotDivConverter.convert(step.getException());
            if (stepDiv != null) {
                sb.append(stepDiv.getPayloadString());
            }
        }
        return sb.toString();
    }
}
