# 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 xml.etree import ElementTree

import six

from . import base


def format_test_id(test_id):
    """Take a test id and return something that looks a bit like
    a class path"""
    if not isinstance(test_id, six.string_types):
        # Not sure how to deal with reftests yet
        raise NotImplementedError

    # Turn a path into something like a class heirachy
    return test_id.replace(".", "_").replace("/", ".")


class XUnitFormatter(base.BaseFormatter):
    """Formatter that produces XUnit-style XML output.

    The tree is created in-memory so this formatter may be problematic
    with very large log files.

    Note that the data model isn't a perfect match. In
    particular XUnit assumes that each test has a unittest-style
    class name and function name, which isn't the case for us. The
    implementation currently replaces path names with something that
    looks like class names, but this doesn't work for test types that
    actually produce class names, or for test types that have multiple
    components in their test id (e.g. reftests)."""

    def __init__(self):
        self.tree = ElementTree.ElementTree()
        self.root = None
        self.suite_start_time = None
        self.test_start_time = None

        self.tests_run = 0
        self.errors = 0
        self.failures = 0
        self.skips = 0

    def suite_start(self, data):
        self.root = ElementTree.Element("testsuite")
        self.tree.root = self.root
        self.suite_start_time = data["time"]

    def test_start(self, data):
        self.tests_run += 1
        self.test_start_time = data["time"]

    def _create_result(self, data):
        test = ElementTree.SubElement(self.root, "testcase")
        name = format_test_id(data["test"])
        extra = data.get("extra") or {}
        test.attrib["classname"] = extra.get("class_name") or name

        if "subtest" in data:
            test.attrib["name"] = data["subtest"]
            # We generally don't know how long subtests take
            test.attrib["time"] = "0"
        else:
            if "." in name:
                test_name = name.rsplit(".", 1)[1]
            else:
                test_name = name
            test.attrib["name"] = extra.get("method_name") or test_name
            test.attrib["time"] = "%.2f" % (
                (data["time"] - self.test_start_time) / 1000.0
            )

        if "expected" in data and data["expected"] != data["status"]:
            if data["status"] in ("NOTRUN", "ASSERT", "ERROR"):
                result = ElementTree.SubElement(test, "error")
                self.errors += 1
            else:
                result = ElementTree.SubElement(test, "failure")
                self.failures += 1

            result.attrib["message"] = "Expected %s, got %s" % (
                data["expected"],
                data["status"],
            )
            result.text = "%s\n%s" % (data.get("stack", ""), data.get("message", ""))

        elif data["status"] == "SKIP":
            result = ElementTree.SubElement(test, "skipped")
            self.skips += 1

    def test_status(self, data):
        self._create_result(data)

    def test_end(self, data):
        self._create_result(data)

    def suite_end(self, data):
        self.root.attrib.update(
            {
                "tests": str(self.tests_run),
                "errors": str(self.errors),
                "failures": str(self.failures),
                "skips": str(self.skips),
                "time": "%.2f" % ((data["time"] - self.suite_start_time) / 1000.0),
            }
        )
        xml_string = ElementTree.tostring(self.root, encoding="utf8")
        # pretty printing can not be done from xml.etree
        from xml.dom import minidom

        return minidom.parseString(xml_string).toprettyxml()
