from pcs.common import reports
from pcs.common.reports import ReportProcessor
from pcs.common.reports.item import ReportItem
from pcs.lib.cib.node import PacemakerNode
from pcs.lib.cib.resource import primitive
from pcs.lib.resource_agent import (
    find_valid_resource_agent_by_name,
    ResourceAgentName,
)

AGENT_NAME = ResourceAgentName("ocf", "pacemaker", "remote")


def get_agent(report_processor, cmd_runner):
    return find_valid_resource_agent_by_name(
        report_processor,
        cmd_runner,
        AGENT_NAME.full_name,
    )


_IS_REMOTE_AGENT_XPATH_SNIPPET = """
    @class="{0}" and @provider="{1}" and @type="{2}"
""".format(
    AGENT_NAME.standard, AGENT_NAME.provider, AGENT_NAME.type
)

_HAS_SERVER_XPATH_SNIPPET = """
    instance_attributes/nvpair[
        @name="server"
        and
        string-length(@value) > 0
    ]
"""


def find_node_list(tree):
    """
    Return list of remote nodes from the specified element tree

    etree.Element tree -- an element to search remote nodes in
    """
    node_list = [
        PacemakerNode(
            nvpair.getparent().getparent().attrib["id"], nvpair.attrib["value"]
        )
        for nvpair in tree.xpath(
            ".//primitive[{is_remote}]/{has_server}".format(
                is_remote=_IS_REMOTE_AGENT_XPATH_SNIPPET,
                has_server=_HAS_SERVER_XPATH_SNIPPET,
            )
        )
    ]

    node_list.extend(
        [
            PacemakerNode(primitive.attrib["id"], primitive.attrib["id"])
            for primitive in tree.xpath(
                ".//primitive[{is_remote} and not({has_server})]".format(
                    is_remote=_IS_REMOTE_AGENT_XPATH_SNIPPET,
                    has_server=_HAS_SERVER_XPATH_SNIPPET,
                )
            )
        ]
    )

    return node_list


def find_node_resources(resources_section, node_identifier):
    """
    Return list of resource elements that match to node_identifier

    etree.Element resources_section is a search element
    string node_identifier could be id of the resource or its instance attribute
        "server"
    """
    return resources_section.xpath(
        """
            .//primitive[
                {is_remote} and (
                    @id="{identifier}"
                    or
                    instance_attributes/nvpair[
                        @name="server"
                        and
                        @value="{identifier}"
                    ]
                )
            ]
        """.format(
            is_remote=_IS_REMOTE_AGENT_XPATH_SNIPPET, identifier=node_identifier
        )
    )


def get_node_name_from_resource(resource_element):
    """
    Return the node name from a remote node resource, None for other resources

    etree.Element resource_element
    """
    if not (
        resource_element.attrib.get("class", "") == AGENT_NAME.standard
        and resource_element.attrib.get("provider", "") == AGENT_NAME.provider
        and resource_element.attrib.get("type", "") == AGENT_NAME.type
    ):
        return None
    return resource_element.attrib["id"]


def _validate_server_not_used(agent, option_dict):
    if "server" in option_dict:
        return [
            ReportItem.error(
                reports.messages.InvalidOptions(
                    ["server"],
                    sorted(
                        [
                            attr.name
                            for attr in agent.get_full_info().parameters
                            if attr.name != "server"
                        ]
                    ),
                    "resource",
                ),
            )
        ]
    return []


def validate_host_not_conflicts(
    existing_nodes_addrs, node_name, instance_attributes
):
    host = instance_attributes.get("server", node_name)
    if host in existing_nodes_addrs:
        return [ReportItem.error(reports.messages.IdAlreadyExists(host))]
    return []


def validate_create(
    existing_nodes_names,
    existing_nodes_addrs,
    resource_agent,
    new_node_name,
    new_node_addr,
    instance_attributes,
):
    """
    validate inputs for create

    list of string existing_nodes_names -- node names already in use
    list of string existing_nodes_addrs -- node addresses already in use
    ResourceAgent resource_agent -- pacemaker_remote resource agent
    string new_node_name -- the name of the future node
    string new_node_addr -- the address of the future node
    dict instance_attributes -- data for the future resource instance attributes
    """
    report_list = _validate_server_not_used(resource_agent, instance_attributes)

    addr_is_used = False
    if new_node_addr in existing_nodes_addrs:
        report_list.append(
            ReportItem.error(reports.messages.IdAlreadyExists(new_node_addr))
        )
        addr_is_used = True

    if not addr_is_used or new_node_addr != new_node_name:
        if new_node_name in existing_nodes_names:
            report_list.append(
                ReportItem.error(
                    reports.messages.IdAlreadyExists(new_node_name)
                )
            )

    return report_list


def prepare_instance_atributes(instance_attributes, host):
    enriched_instance_attributes = instance_attributes.copy()
    enriched_instance_attributes["server"] = host
    return enriched_instance_attributes


def create(
    report_processor: ReportProcessor,
    resource_agent,
    resources_section,
    id_provider,
    host,
    node_name,
    raw_operation_list=None,
    meta_attributes=None,
    instance_attributes=None,
    allow_invalid_operation=False,
    allow_invalid_instance_attributes=False,
    use_default_operations=True,
):
    # pylint: disable=too-many-arguments
    """
    Prepare all parts of remote resource and append it into the cib.

    report_processor is a tool for warning/info/error reporting
    cmd_runner is tool for launching external commands
    etree.Element resources_section is place where new element will be appended
    string node_name is name of the remote node and id of new resource as well
    list of dict raw_operation_list specifies operations of resource
    dict meta_attributes specifies meta attributes of resource
    dict instance_attributes specifies instance attributes of resource
    bool allow_invalid_operation is flag for skipping validation of operations
    bool allow_invalid_instance_attributes is flag for skipping validation of
        instance_attributes
    bool use_default_operations is flag for completion operations with default
        actions specified in resource agent
    """
    all_instance_attributes = instance_attributes.copy()
    all_instance_attributes.update({"server": host})
    return primitive.create(
        report_processor,
        resources_section,
        id_provider,
        node_name,
        resource_agent,
        raw_operation_list,
        meta_attributes,
        all_instance_attributes,
        allow_invalid_operation,
        allow_invalid_instance_attributes,
        use_default_operations,
        # TODO remove this dirty fix
        # This is for passing info from the top (here) through another library
        # command (primitive.create) to instance attributes validator in
        # resource_agent. We handle the "server" attribute ourselves in this
        # command so we want to make sure it is not reported as an allowed
        # option.
        # How to fix:
        # 1) do not call one lib command from another
        # 2) split validation and cib modification in primitive.create
        # 3) call the validation from here and handle the results or config
        #    the validator before / when running it
        do_not_report_instance_attribute_server_exists=True,
    )
