# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from collections import OrderedDict, defaultdict

from ..reader import LogHandler


class SummaryHandler(LogHandler):
    """Handler class for storing suite summary information.

    Can handle multiple suites in a single run. Summary
    information is stored on the self.summary instance variable.

    Per suite summary information can be obtained by calling 'get'
    or iterating over this class.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.summary = OrderedDict()
        self.current_suite = None

    @property
    def current(self):
        return self.summary.get(self.current_suite)

    def __getitem__(self, suite):
        """Return summary information for the given suite.

        The summary is of the form:

            {
              'counts': {
                '<check>': {
                  'count': int,
                  'expected': {
                    '<status>': int,
                  },
                  'unexpected': {
                    '<status>': int,
                  },
                  'known_intermittent': {
                    '<status>': int,
                  },
                },
              },
              'unexpected_logs': {
                '<test>': [<data>]
              },
              'intermittent_logs': {
                '<test>': [<data>]
              }
            }

        Valid values for <check> are `test`, `subtest` and `assert`. Valid
        <status> keys are defined in the :py:mod:`mozlog.logtypes` module.  The
        <test> key is the id as logged by `test_start`. Finally the <data>
        field is the log data from any `test_end` or `test_status` log messages
        that have an unexpected result.

        Mozlog's structuredlog has a `known_intermittent` field indicating if a
        `test` and `subtest` <status> are expected to arise intermittently.
        Known intermittent results are logged as both as `expected` and
        `known_intermittent`.
        """
        return self.summary[suite]

    def __iter__(self):
        """Iterate over summaries.

        Yields a tuple of (suite, summary). The summary returned is
        the same format as returned by 'get'.
        """
        yield from self.summary.items()

    @classmethod
    def aggregate(cls, key, counts, include_skip=True):
        """Helper method for aggregating count data by 'key' instead of by 'check'."""
        assert key in ("count", "expected", "unexpected", "known_intermittent")

        res = defaultdict(int)
        for check, val in counts.items():
            if key == "count":
                res[check] += val[key]
                continue

            for status, num in val[key].items():
                if not include_skip and status == "skip":
                    continue
                res[check] += num
        return res

    def suite_start(self, data):
        self.current_suite = data.get("name", f"suite {len(self.summary) + 1}")
        if self.current_suite not in self.summary:
            self.summary[self.current_suite] = {
                "counts": {
                    "test": {
                        "count": 0,
                        "expected": defaultdict(int),
                        "unexpected": defaultdict(int),
                        "known_intermittent": defaultdict(int),
                    },
                    "subtest": {
                        "count": 0,
                        "expected": defaultdict(int),
                        "unexpected": defaultdict(int),
                        "known_intermittent": defaultdict(int),
                    },
                    "assert": {
                        "count": 0,
                        "expected": defaultdict(int),
                        "unexpected": defaultdict(int),
                        "known_intermittent": defaultdict(int),
                    },
                },
                "unexpected_logs": OrderedDict(),
                "intermittent_logs": OrderedDict(),
                "harness_errors": [],
            }

    def test_start(self, data):
        self.current["counts"]["test"]["count"] += 1

    def test_status(self, data):
        logs = self.current["unexpected_logs"]
        intermittent_logs = self.current["intermittent_logs"]
        count = self.current["counts"]
        count["subtest"]["count"] += 1

        if "expected" in data:
            if data["status"] not in data.get("known_intermittent", []):
                count["subtest"]["unexpected"][data["status"].lower()] += 1
                if data["test"] not in logs:
                    logs[data["test"]] = []
                logs[data["test"]].append(data)
            else:
                count["subtest"]["expected"][data["status"].lower()] += 1
                count["subtest"]["known_intermittent"][data["status"].lower()] += 1
                if data["test"] not in intermittent_logs:
                    intermittent_logs[data["test"]] = []
                intermittent_logs[data["test"]].append(data)
        else:
            count["subtest"]["expected"][data["status"].lower()] += 1

    def test_end(self, data):
        logs = self.current["unexpected_logs"]
        intermittent_logs = self.current["intermittent_logs"]
        count = self.current["counts"]
        if "expected" in data:
            if data["status"] not in data.get("known_intermittent", []):
                count["test"]["unexpected"][data["status"].lower()] += 1
                if data["test"] not in logs:
                    logs[data["test"]] = []
                logs[data["test"]].append(data)
            else:
                count["test"]["expected"][data["status"].lower()] += 1
                count["test"]["known_intermittent"][data["status"].lower()] += 1
                if data["test"] not in intermittent_logs:
                    intermittent_logs[data["test"]] = []
                intermittent_logs[data["test"]].append(data)
        else:
            count["test"]["expected"][data["status"].lower()] += 1

    def assertion_count(self, data):
        count = self.current["counts"]
        count["assert"]["count"] += 1

        if data["min_expected"] <= data["count"] <= data["max_expected"]:
            if data["count"] > 0:
                count["assert"]["expected"]["fail"] += 1
            else:
                count["assert"]["expected"]["pass"] += 1
        elif data["max_expected"] < data["count"]:
            count["assert"]["unexpected"]["fail"] += 1
        else:
            count["assert"]["unexpected"]["pass"] += 1

    def log(self, data):
        if not self.current_suite:
            return

        logs = self.current["harness_errors"]
        level = data.get("level").upper()

        if level in ("CRITICAL", "ERROR"):
            logs.append(data)
