"""
 *
 * This file is part of rasdaman community.
 *
 * Rasdaman community is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rasdaman community is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU  General Public License for more details.
 *
 * You should have received a copy of the GNU  General Public License
 * along with rasdaman community.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003 - 2016 Peter Baumann / rasdaman GmbH.
 *
 * For more information please see <http://www.rasdaman.org>
 * or contact Peter Baumann via <baumann@rasdaman.com>.
 *
"""
from abc import abstractmethod, ABCMeta
from config_manager import ConfigManager
from util.file_util import get_all_dirpaths, write_to_file, \
    get_all_filepaths
from util.log import log
from util.commands import sudo_chown
from util.string_util import binary_to_string
from services import executor, linux_distro


class TestResultStatus:
    """
    Represents  the test result status
    """
    INVALID = 0
    SUCCESS = 1
    FAILURE = 2

    @staticmethod
    def to_str(status):
        return ["INVALID", "SUCCESS", "FAILURE"][status]


class TestResult:
    FAIL_PREFIX = '\n\n==================================== failed tests ====================================\n'
    FAIL_SUFFIX = '\n\n======================================================================================\n'
    def __init__(self, status, failed_test_items):
        """
        Test result data structure
        :param int status: a status represented through TestResultStatus
        :param list[str] failed_test_items: a list of failed tests
        :return:
        """
        self.status = status
        self.failed_tests = failed_test_items

    def return_status(self):
        if self.status == TestResultStatus.SUCCESS:
            return 1
        else:
            return self.failed_tests

    def __str__(self):
        result = 'TEST STATUS: ' + TestResultStatus.to_str(self.status)
        if len(self.failed_tests) > 0:
            result += self.FAIL_PREFIX
        for res in self.failed_tests:
            result += '\n' + str(res)
        if len(self.failed_tests) > 0:
            result += self.FAIL_SUFFIX
        return result


class RasdamanSystemTestParser:
    # Each test is separated by such a line in our systemtests :)
    FAILED_SUITE = "Failed tests    :"
    FAILED_CASES = [" FAIL ", "FAILED", "failed"]
    SUITE_SUMMARY_START = 'Test summary for'
    SUITE_SUMMARY_END = "-------------------------------------------------------"

    def __init__(self, test_output):
        """
        Parses the test results
        :param test_output:  the output of the system tests
        """
        self.test_output = test_output

    def parse(self):
        """
        Parses the rasdaman system tests and returns a list of failed test cases
        :rtype: list[FailedTestItem]
        """
        ret = []
        suite_failed = False
        suite_summary = ""
        lines = binary_to_string(self.test_output).split('\n')
        for line in lines:
            if self._is_failed_case(line):
                ret.append(line)
                continue

            if suite_summary != "" or self.SUITE_SUMMARY_START in line:
                suite_summary += line + '\n'
                if self.FAILED_SUITE in line:
                    suite_failed = True
                elif self.SUITE_SUMMARY_END in line:
                    if suite_failed:
                        ret.append(line + '\n' + suite_summary)
                    # reset
                    suite_summary = ""
                    suite_failed = False

        return ret

    def _is_failed_case(self, line):
        for failed_case in self.FAILED_CASES:
            if failed_case in line:
                return True
        return False


class Tester:
    __metaclass__ = ABCMeta

    @abstractmethod
    def test(self):
        """
        Tests the current installation of rasdaman and returns the results of
        the testing session
        :rtype TestResult: the results of the test
        """

    @abstractmethod
    def is_enabled(self):
        return False


class RasdamanSystemTester(Tester):
    SYSTEM_TEST_LOCATION = "systemtest"

    def __init__(self, profile):
        """
        System tester for rasdaman
        :param Profile profile: the profile to be used
        """
        self.source_path = profile.source_path
        self.systemtest_path = self.source_path + self.SYSTEM_TEST_LOCATION
        self.install_path = profile.install_path
        self.petascope_db = profile.petascopedb_url
        self.profile = profile

    def test(self):
        """
        Tests the system and returns the results of the testing session
        :rtype: TestResult
        """
        log.info("Starting system tests...")
        cmd = ["./run_test.sh", self.install_path + "bin/"]
        test_output, _, err = executor.execute(cmd, self.systemtest_path,
            throw_on_error=False)
        self.__save_test_output(test_output)
        self.__save_rasdaman_logs()
        self.__save_tomcat_logs()
        status = TestResultStatus.SUCCESS if err == 0 else TestResultStatus.FAILURE
        test_items = RasdamanSystemTestParser(test_output).parse()
        log.info("Systemtests executed.")
        return TestResult(status, test_items)

    def __save_test_output(self, output):
        """
        Saves the test output in $RMANHOME/test.output, and the test output data
        in $RMANHOME/test_output.tar.gz
        :param str output: the output as a string
        """
        test_output_path = self.systemtest_path + "/test.output"
        write_to_file(test_output_path, output)
        cmd = ["tar", "cfz", "test_output.tar.gz", test_output_path]
        output_directories = get_all_dirpaths(self.systemtest_path, "output")
        if len(output_directories) > 0:
            cmd.extend(output_directories)
        test_logs = get_all_filepaths(self.systemtest_path, "test.log")
        if len(test_logs) > 0:
            cmd.extend(test_logs)
        cmd.append("--ignore-failed-read")
        executor.execute(cmd, self.source_path, throw_on_error=False)

    def __save_rasdaman_logs(self):
        """
        Saves the rasdaman logs in $RMANHOME/rasdaman_logs.tar.gz
        """
        cmd = ["tar", "cfz", "rasdaman_logs.tar.gz", "log", "--ignore-failed-read"]
        executor.execute(cmd, self.install_path, throw_on_error=False)

    def __save_tomcat_logs(self):
        """
        Saves the Tomcat logs in $RMANHOME/tomcat_logs.tar.gz
        """
        if self.profile.webapps_enabled and self.profile.webapps_deployment == "external":
            archive_path = "tomcat_logs.tar.gz"
            cmd = ["tar", "cfz", archive_path, linux_distro.get_tomcat_logs(),
                   "--ignore-failed-read"]
            executor.executeSudo(cmd, self.install_path, throw_on_error=False)
            sudo_chown(self.install_path + archive_path, ConfigManager.default_user)

    def is_enabled(self):
        return True


class MockTester(Tester):
    def __init__(self):
        """
        Does not do any testing and always returns success
        """
        pass

    def test(self):
        return TestResult(TestResultStatus.SUCCESS, [])

    def is_enabled(self):
        return False


def test():
    sample_systemtest_output = """

test.sh:  61/63    OK   .18s   unary_mdd_minus.rasql
test.sh:  62/63  FAIL   .17s   unary_mdd_modulo.rasql
test.sh:  63/63    OK   .20s   unary_mdd_mult.rasql
test.sh:
test.sh: -------------------------------------------------------
test.sh: Test summary for nullvalues
test.sh:
test.sh:   Test finished at: Thu Nov 29 22:11:35 UTC 2018
test.sh:   Elapsed time    : 16.74s
test.sh:   Total tests run : 3
test.sh:   Successful tests: 2
test.sh:   Failed tests    : 1
test.sh:   Detail test log : /opt/rasdaman/source/systemtest/testcases_mandatory/test_nullvalues/test.log
test.sh: -------------------------------------------------------

test.sh: inserting struct_4d... ok.
test.sh: exporting struct_4d_systemtest to netcdf... ok.
test.sh: exported output matches oracle... ok.
test.sh: testing json export, octet_3d_json_export
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, octet_4d_json_export
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, struct_3d_json_export
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, octet_3d_json_export_fillvalues
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, struct_3d_json_export_fillvalues
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, octet_3d_json_export_transpose
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, double_3d_json_export_transpose
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: testing json export, struct_3d_json_export_transpose
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh:
test.sh: -------------------------------------------------------
test.sh: Test summary for test_netcdf
test.sh:
test.sh:   Test finished at: Thu Nov 29 22:11:12 UTC 2018
test.sh:   Elapsed time    : 34.73s
test.sh:   Total tests run : 108
test.sh:   Successful tests: 100
test.sh:   Failed tests    : 8
test.sh:   Detail test log : /opt/rasdaman/source/systemtest/testcases_mandatory/test_netcdf/test.log
test.sh: -------------------------------------------------------
test.sh:

test.sh: test wcst_import.sh test_wcst_import_daemon/ingest.json --watch 0.5
test.sh: checking --daemon start... ok.
test.sh: checking pidfile removal during --watch [interval]... failed, expected: '0', got '1'.
test.sh: checking data access during --watch [interval]... ok.
test.sh:
test.sh: check daemon status on manual daemon kill...
test.sh: checking --daemon status... ok.
test.sh: checking --daemon start... ok.
test.sh: checking --daemon stop... ok.
test.sh:
test.sh: check that daemon stops when pid file is removed...
test.sh: checking --daemon stop... ok.
test.sh:
test.sh: -------------------------------------------------------
test.sh: Test summary for test_wcst_import_daemon
test.sh:
test.sh:   Test finished at: Thu Nov 29 22:22:22 UTC 2018
test.sh:   Elapsed time    : 7.62s
test.sh:   Total tests run : 11
test.sh:   Successful tests: 10
test.sh:   Failed tests    : 1
test.sh:   Detail test log : /opt/rasdaman/source/systemtest/testcases_services/test_open/test_wcst_import_daemon/test.log
test.sh: -------------------------------------------------------

TEST SUMMARY

   OK   42s  testcases_mandatory/test_clipping/test.sh
   OK   41s  testcases_mandatory/test_conversion/test.sh
   OK   17s  testcases_mandatory/test_examples/test.sh
   OK   11s  testcases_mandatory/test_grib/test.sh
   OK  127s  testcases_mandatory/test_manipulation/test.sh
 FAIL   40s  testcases_mandatory/test_netcdf/test.sh
   OK   17s  testcases_mandatory/test_nullvalues/test.sh"""

    exp_result = """TEST STATUS: FAILURE

==================================== failed tests ====================================

test.sh:  62/63  FAIL   .17s   unary_mdd_modulo.rasql
test.sh: -------------------------------------------------------
test.sh: Test summary for nullvalues
test.sh:
test.sh:   Test finished at: Thu Nov 29 22:11:35 UTC 2018
test.sh:   Elapsed time    : 16.74s
test.sh:   Total tests run : 3
test.sh:   Successful tests: 2
test.sh:   Failed tests    : 1
test.sh:   Detail test log : /opt/rasdaman/source/systemtest/testcases_mandatory/test_nullvalues/test.log
test.sh: -------------------------------------------------------

test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: exported output matches oracle... failed, expected: '0', got '1'.
test.sh: -------------------------------------------------------
test.sh: Test summary for test_netcdf
test.sh:
test.sh:   Test finished at: Thu Nov 29 22:11:12 UTC 2018
test.sh:   Elapsed time    : 34.73s
test.sh:   Total tests run : 108
test.sh:   Successful tests: 100
test.sh:   Failed tests    : 8
test.sh:   Detail test log : /opt/rasdaman/source/systemtest/testcases_mandatory/test_netcdf/test.log
test.sh: -------------------------------------------------------

test.sh: checking pidfile removal during --watch [interval]... failed, expected: '0', got '1'.
test.sh: -------------------------------------------------------
test.sh: Test summary for test_wcst_import_daemon
test.sh:
test.sh:   Test finished at: Thu Nov 29 22:22:22 UTC 2018
test.sh:   Elapsed time    : 7.62s
test.sh:   Total tests run : 11
test.sh:   Successful tests: 10
test.sh:   Failed tests    : 1
test.sh:   Detail test log : /opt/rasdaman/source/systemtest/testcases_services/test_open/test_wcst_import_daemon/test.log
test.sh: -------------------------------------------------------

 FAIL   40s  testcases_mandatory/test_netcdf/test.sh

======================================================================================
"""

    failed = RasdamanSystemTestParser(sample_systemtest_output).parse()
    result = TestResult(TestResultStatus.FAILURE, failed)
    return str(result) == exp_result


if __name__== "__main__":
    test()
