import re
import xml.dom.minidom
from typing import (
    Any,
    List,
    Optional,
)

from pcs import utils
from pcs.common import (
    const,
    pacemaker,
)
from pcs.common.str_tools import format_list_custom_last_separator

# pylint: disable=not-callable

# main functions


def parse_argv(argv, extra_options=None):
    """
    Commandline options: no options
    """
    options = {"id": None, "role": None, "score": None, "score-attribute": None}
    if extra_options:
        options.update(dict(extra_options))

    # parse options
    while argv:
        found = False
        option = argv.pop(0)
        for name in options:
            if option.startswith(name + "="):
                options[name] = option.split("=", 1)[1]
                found = True
                break
        if not found:
            argv.insert(0, option)
            break
    return options, argv


def dom_rule_add(dom_element, options, rule_argv, cib_schema_version):
    # pylint: disable=too-many-branches
    """
    Commandline options: no options
    """
    # validate options
    if options.get("score") and options.get("score-attribute"):
        utils.err("can not specify both score and score-attribute")
    if options.get("score") and not utils.is_score(options["score"]):
        utils.err("invalid score'{score}'".format(score=options["score"]))
    if options.get("role"):
        role = options["role"].capitalize()
        utils.print_depracation_warning_for_legacy_roles(options["role"])
        supported_roles = (
            const.PCMK_ROLES_PROMOTED + const.PCMK_ROLES_UNPROMOTED
        )
        if role not in supported_roles:
            utils.err(
                "invalid role '{role}', use {supported_roles}".format(
                    role=options["role"],
                    supported_roles=format_list_custom_last_separator(
                        list(supported_roles), " or "
                    ),
                )
            )
        options["role"] = pacemaker.role.get_value_for_cib(
            role,
            cib_schema_version >= const.PCMK_NEW_ROLES_CIB_VERSION,
        )
    if options.get("id"):
        id_valid, id_error = utils.validate_xml_id(options["id"], "rule id")
        if not id_valid:
            utils.err(id_error)
        if utils.does_id_exist(dom_element.ownerDocument, options["id"]):
            utils.err(
                "id '%s' is already in use, please specify another one"
                % options["id"]
            )

    # parse rule
    if not rule_argv:
        utils.err("no rule expression was specified")
    try:
        preprocessor = TokenPreprocessor()
        dom_rule = CibBuilder(cib_schema_version).build(
            dom_element,
            RuleParser().parse(preprocessor.run(rule_argv)),
            options.get("id"),
        )
    except SyntaxError as e:
        utils.err(
            "'%s' is not a valid rule expression: %s" % (" ".join(rule_argv), e)
        )
    except UnexpectedEndOfInput:
        utils.err(
            "'%s' is not a valid rule expression: unexpected end of rule"
            % " ".join(rule_argv)
        )
    except (ParserException, CibBuilderException):
        utils.err("'%s' is not a valid rule expression" % " ".join(rule_argv))

    # add options into rule xml
    if not options.get("score") and not options.get("score-attribute"):
        options["score"] = "INFINITY"
    for name, value in options.items():
        if name != "id" and value is not None:
            dom_rule.setAttribute(name, value)
    # score or score-attribute is required for the nested rules in order to have
    # valid CIB, pacemaker does not use the score of the nested rules
    for rule in dom_rule.getElementsByTagName("rule"):
        rule.setAttribute("score", "0")
    if dom_element.hasAttribute("score"):
        dom_element.removeAttribute("score")
    if dom_element.hasAttribute("node"):
        dom_element.removeAttribute("node")
    return dom_element


class ExportDetailed:
    def __init__(self):
        self.show_detail = False
        self.rule_expired = False

    def get_string(self, rule, rule_expired, show_detail, indent=""):
        self.show_detail = show_detail
        self.rule_expired = rule_expired
        return indent + ("\n" + indent).join(self.list_rule(rule))

    def list_rule(self, rule):
        rule_parts = [
            "Rule{0}: {1}".format(
                " (expired)" if self.rule_expired else "",
                " ".join(self._list_attributes(rule)),
            )
        ]
        for child in rule.childNodes:
            if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
                continue
            if child.tagName == "expression":
                self.indent_append(rule_parts, self.list_expression(child))
            elif child.tagName == "date_expression":
                self.indent_append(rule_parts, self.list_date_expression(child))
            elif child.tagName == "rule":
                self.indent_append(rule_parts, self.list_rule(child))
        return rule_parts

    def list_expression(self, expression):
        if "value" in expression.attributes.keys():
            exp_parts = [
                expression.getAttribute("attribute"),
                expression.getAttribute("operation"),
            ]
            if expression.hasAttribute("type"):
                exp_parts.append(expression.getAttribute("type"))
            exp_parts.append(expression.getAttribute("value"))
        else:
            exp_parts = [
                expression.getAttribute("operation"),
                expression.getAttribute("attribute"),
            ]
        if self.show_detail:
            exp_parts.append("(id:%s)" % expression.getAttribute("id"))
        return ["Expression: %s" % " ".join(exp_parts)]

    def list_date_expression(self, expression):
        operation = expression.getAttribute("operation")
        if operation == "date_spec":
            date_spec_parts = self._list_attributes(
                expression.getElementsByTagName("date_spec")[0]
            )
            exp_parts = ["Expression:"]
            if self.show_detail:
                exp_parts.append("(id:%s)" % expression.getAttribute("id"))
            return self.indent_append(
                [" ".join(exp_parts)],
                ["Date Spec: %s" % " ".join(date_spec_parts)],
            )
        if operation == "in_range":
            exp_parts = ["date", "in_range"]
            if expression.hasAttribute("start"):
                exp_parts.extend([expression.getAttribute("start"), "to"])
            if expression.hasAttribute("end"):
                exp_parts.append(expression.getAttribute("end"))
            durations = expression.getElementsByTagName("duration")
            if durations:
                exp_parts.append("duration")
                duration_parts = self._list_attributes(durations[0])
            if self.show_detail:
                exp_parts.append("(id:%s)" % expression.getAttribute("id"))
            result = ["Expression: %s" % " ".join(exp_parts)]
            if durations:
                self.indent_append(
                    result, ["Duration: %s" % " ".join(duration_parts)]
                )
            return result
        exp_parts = ["date", expression.getAttribute("operation")]
        if expression.hasAttribute("start"):
            exp_parts.append(expression.getAttribute("start"))
        if expression.hasAttribute("end"):
            exp_parts.append(e
xpression.getAttribute("end"))
        if self.show_detail:
            exp_parts.append("(id:%s)" % expression.getAttribute("id"))
        return ["Expression: " + " ".join(exp_parts)]

    def _list_attributes(self, element):
        attributes = utils.dom_attrs_to_list(element, with_id=False)
        if self.show_detail:
            attributes.append("(id:%s)" % (element.getAttribute("id")))
        return attributes

    @staticmethod
    def inde
nt_append(target, source, indent="  "):
        for part in source:
            target.append(indent + part)
        return target


class ExportAsExpression:
    def __init__(self):
        self.normalize = False

 