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

from takeoff.custom_types import Host, Level, Server
from takeoff.downer import Downer
from takeoff.error import TakeOffError
from takeoff.finder import Finder
from takeoff.upper import Upper


class TakeOff:
    def __init__(self, upper: Upper, downer: Downer, finder: Finder, printer):
        self.upper = upper
        self.downer = downer
        self.finder = finder
        self.printer = printer

    def find(self, server_names: list[str]):
        """
        This gives a set of servers with the details that is expected when
        to be set when running 'up' or 'down'
        """
        self.print_servers(set(self.finder.search(server_names)))

    def print_servers(self, servers: set[Server]):
        self.printer.print_servers(servers)

    def up(
        self, server_names: list[str], dry_run: bool, timeout: int, starter_timeout: int
    ):
        """
        Tries to start all servers on the given host with given level
        """
        servers = self.finder.find(server_names)

        if dry_run:
            self.printer.print_servers(servers)
        else:
            self.upper.up(self._build_hosts(servers), timeout, starter_timeout)
            self.printer.print_result(self.upper.result)

    def down(
        self,
        server_names: list[str],
        dry_run: bool,
        force: bool,
        kill_timeout: int,
        starter_timeout: int,
    ):
        """
        Tries to stop all servers on the given host
        """
        servers = self.finder.find(server_names)
        if dry_run:
            self.printer.print_servers(servers)
        else:
            self.downer.down(
                self._build_hosts(servers), force, kill_timeout, starter_timeout
            )
            self.printer.print_result(self.downer.result)

    def restart(
        self,
        server_names: list[str],
        dry_run: bool,
        force: bool,
        kill_timeout: int,
        timeout: int,
        starter_timeout: int,
        wait_after_stop: int,
    ):
        """
        Tries to restart all servers on the given host
        """
        servers = self.finder.find(server_names)
        if dry_run:
            self.printer.print_servers(servers)
        else:
            hosts = self._build_hosts(servers)
            self.downer.down(hosts, force, kill_timeout, starter_timeout)
            time.sleep(wait_after_stop)
            self.upper.up(hosts, timeout, starter_timeout)
            self.printer.print_result(self.downer.result + self.upper.result)

    def _build_hosts(self, servers: set[Server]):
        """
        Builds the Host types based on incoming servers
        """

        # Validation. We cannot start or stop a server that does not have all the three
        # components name, host and level.
        errors = []
        hosts_raw = {}  # type: ignore
        for server in servers:
            if not server.is_valid():
                missing = []
                if server.host is None:
                    missing.append("'host'")
                if server.level is None:
                    missing.append("'level'")
                errors.append(
                    f"cannot start or stop server {server.name}: missing {', '.join(missing)}"
                )
                continue

            if server.host not in hosts_raw:
                hosts_raw[server.host] = {}

            if server.level not in hosts_raw[server.host]:
                hosts_raw[server.host][server.level] = []

            hosts_raw[server.host][server.level].append(server.name)

        # If we got errors, bail out
        if errors:
            raise TakeOffError(errors)

        # Create proper hosts
        hosts: list[Host] = []
        for hostname in hosts_raw:
            levels: list[Level] = []
            for level in hosts_raw[hostname]:
                levels.append(Level(level, hosts_raw[hostname][level]))

            hosts.append(Host(hostname, levels))

        return hosts
