###############################################################################
# This file is part of the tango-takeoff project.
#
# Copyright Lund University
#
# Distributed under the GNU GPLv3 license. See LICENSE file for more info.
###############################################################################
from typing import Optional, Union

from takeoff.custom_types import Server
from takeoff.error import TakeOffError


def short_hostname(hostname: str) -> str:
    """Return the short hostname

    Starter always uses the short hostname as instance name
    and device name (tango/admin/<short hostname>).
    DBServerInfo usually includes the host FQDN.
    It should always be converted.
    """
    return hostname.strip().split(".")[0]


class Finder:
    def __init__(self, default_host: Optional[str], database):
        self.default_host = default_host
        self.database = database

    def search(self, server_names: list[str]) -> list[Server]:
        servers: set[Server] = set()

        for server_name in server_names:
            template_server = self._parse_server_name(server_name)
            candidates = self.database.get_server_list(template_server.name)

            if len(candidates.value_string) == 0:
                continue

            for candidate in candidates:
                info = self.database.get_server_info(candidate)

                server = Server(
                    info.name,
                    short_hostname(info.host),
                    int(info.level),
                )

                # No filters
                if template_server.host is None and template_server.level is None:
                    servers.add(server)

                # filter on host
                if (
                    template_server.host is not None
                    and template_server.host != server.host
                ):
                    continue

                # filter on level
                if (
                    template_server.level is not None
                    and template_server.level != server.level
                ):
                    continue

                servers.add(server)

        return list(servers)

    def find(self, server_names: list[str]) -> set[Server]:
        """
        Find Tango device servers that matches the server names
        """
        servers: set[Server] = set()
        errors = []

        for server_name in server_names:
            template_server = self._parse_server_name(server_name)
            if template_server.is_valid():
                servers.add(template_server)
                continue

            candidates = self.database.get_server_list(template_server.name)

            if len(candidates.value_string) == 0:
                errors.append(f"no server found for term '{server_name}'")

            for candidate in candidates:
                info = self.database.get_server_info(candidate)
                server = template_server._replace(
                    name=info.name,
                )

                # Replace the with info where there are gaps in the created Server

                if server.level is None:
                    # If level isn't specified on the cli, we keep the level defined in server_info
                    # except if it is 0 as the host will be uncontrolled.
                    # Level is set to 1 in that case.
                    try:
                        level = int(info.level) or 1
                    except ValueError:
                        level = 1

                    server = server._replace(level=level)

                # info.host is never None but a string that has the value ' ',
                # yes a space or 'null' (when removing startup level info from Astor).
                # So, we want to clean that if possible, and have a
                # proper None instead
                if server.host is None and info.host.strip() not in ("", "null"):
                    server = server._replace(host=short_hostname(info.host))

                servers.add(server)

        if errors:
            raise TakeOffError(errors)

        return servers

    def _parse_server_name(self, server_name: str) -> Server:
        parts = server_name.strip().split(",")

        if len(parts) > 3:
            raise TakeOffError(
                f"cannot parse '{server_name}' into valid [host,]server[,level]"
            )

        res: dict[str, Optional[Union[str, int]]] = {
            "host": None,
            "name": None,
            "level": None,
        }

        if len(parts) == 1:
            res["name"] = parts[0]

        elif len(parts) == 2:
            try:
                res["level"] = int(parts[1])
                res["name"] = parts[0]
            except ValueError:
                res["host"] = parts[0]
                res["name"] = parts[1]

        elif len(parts) == 3:
            res["host"] = parts[0]
            res["name"] = parts[1]
            res["level"] = int(parts[2])

        if res["host"] is None:
            res["host"] = self.default_host

        return Server(**res)  # type: ignore
