# Copyright (c) Mengning Software. 2023. All rights reserved.
#
# Super IDE licensed under GNU Affero General Public License v3 (AGPL-3.0) .
# You can use this software according to the terms and conditions of the AGPL-3.0.
# You may obtain a copy of AGPL-3.0 at:
#
#    https://www.gnu.org/licenses/agpl-3.0.txt
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
# PURPOSE.
# See the AGPL-3.0 for more details.

import enum
import functools
import operator
import time

import click


class TestStatus(enum.Enum):
    PASSED = enum.auto()
    FAILED = enum.auto()
    SKIPPED = enum.auto()
    WARNED = enum.auto()
    ERRORED = enum.auto()

    @classmethod
    def from_string(cls, value: str):
        value = value.lower()
        if value.startswith(("failed", "fail")):
            return cls.FAILED
        if value.startswith(("passed", "pass", "success", "ok")):
            return cls.PASSED
        if value.startswith(("skipped", "skip", "ignore", "ignored")):
            return cls.SKIPPED
        if value.startswith("WARNING"):
            return cls.WARNED
        raise ValueError(f"Unknown test status `{value}`")

    def to_ansi_color(self):
        if self == TestStatus.FAILED:
            return "red"
        if self == TestStatus.PASSED:
            return "green"
        return "yellow"


class TestCaseSource:
    def __init__(self, filename, line=None):
        self.filename = filename
        self.line = line


class TestCase:
    def __init__(  # pylint: disable=too-many-arguments
        self,
        name,
        status,
        message=None,
        stdout=None,
        source=None,
        duration=0,
        exception=None,
    ):
        assert isinstance(status, TestStatus)
        if status == TestStatus.ERRORED:
            assert isinstance(exception, Exception)
        self.name = name.strip()
        self.status = status
        self.message = message
        self.stdout = stdout
        self.source = source
        self.duration = duration
        self.exception = exception

    def humanize(self):
        parts = []
        if self.source:
            parts.append("%s:%d: " % (self.source.filename, self.source.line))
        parts.append(self.name)
        if self.message:
            parts.append(": " + self.message)
        parts.extend(
            [
                "\t",
                "[%s]" % click.style(self.status.name, fg=self.status.to_ansi_color()),
            ]
        )
        return "".join(parts)


class TestSuite:
    def __init__(self, env_name, test_name, finished=False, test_dir=None):
        self.env_name = env_name
        self.test_name = test_name
        self.test_dir = test_dir
        self.timestamp = 0
        self.duration = 0
        self._cases = []
        self._finished = finished

    @property
    def cases(self):
        return self._cases

    @property
    def status(self):
        for s in (TestStatus.ERRORED, TestStatus.FAILED):
            if self.get_status_nums(s):
                return s
        if self._cases and any(c.status == TestStatus.PASSED for c in self._cases):
            return TestStatus.PASSED
        return TestStatus.SKIPPED

    def get_status_nums(self, status):
        return len([True for c in self._cases if c.status == status])

    def add_case(self, case: TestCase):
        assert isinstance(case, TestCase)
        self._cases.append(case)

    def is_finished(self):
        return self._finished

    def on_start(self):
        self.timestamp = time.time()

    def on_finish(self):
        if self.is_finished():
            return
        self._finished = True
        self.duration = time.time() - self.timestamp


class TestResult:
    def __init__(self, project_dir):
        self.project_dir = project_dir
        self._suites = []

    @property
    def suites(self):
        return self._suites

    def add_suite(self, suite):
        assert isinstance(suite, TestSuite)
        self._suites.append(suite)

    @property
    def duration(self):
        return functools.reduce(operator.add, [s.duration for s in self._suites])

    @property
    def case_nums(self):
        return functools.reduce(operator.add, [len(s.cases) for s in self._suites])

    @property
    def is_errored(self):
        return any(s.status == TestStatus.ERRORED for s in self._suites)

    def get_status_nums(self, status):
        return functools.reduce(
            operator.add, [s.get_status_nums(status) for s in self._suites]
        )
