###############################################################################
# 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 json
from dataclasses import asdict

import tango  # type: ignore

from takeoff.custom_types import Result, Server
from takeoff.error import TakeOffCommandFailed, TakeOffError


class PlainPrinter:
    def __init__(self, verbose):
        self._verbose = verbose

    def print_servers(self, servers: set[Server]):
        if len(servers) == 0:
            print("no servers matched")
            return

        print("Host".ljust(37) + "Name".ljust(37) + "Level")
        print("-" * 80)
        sorted_servers = list(servers)
        sorted_servers.sort(key=lambda x: x.level or 0)  # type: ignore
        for server in sorted_servers:
            host = ""
            if server.host is not None:
                host = server.host

            level = ""
            if server.level is not None:
                level = str(server.level)

            s = ""
            s += host.ljust(37)
            s += server.name.ljust(37)
            s += level.rjust(3)

            print(s)

    def print_result(self, result: Result):
        if self._verbose:
            for key, message in [
                ("started", "Started servers"),
                ("stopped", "Stopped servers"),
                ("already_running", "Already running servers"),
                ("already_stopped", "Already stopped servers"),
                ("changed_info", "Changed info"),
            ]:
                if getattr(result, key):
                    print(f"{message}: {' '.join(getattr(result, key))}")
        if result.failed_to_start:
            print(f"Error: Failed to start {' '.join(result.failed_to_start)}")
        if result.failed_to_stop:
            print(f"Error: Failed to stop {' '.join(result.failed_to_stop)}")
        if result.failed_to_start or result.failed_to_stop:
            raise TakeOffCommandFailed("Failed to start or stop some servers")

    def error(self, error: TakeOffError):
        print("Error:", error)

    def devfailed(self, error: tango.DevFailed):
        if self._verbose:
            print(error)
        else:
            print("Error:", error.args[0].desc)


class StructuredPrinter:
    def print_servers(self, servers: set[Server]):
        json_dicts = []
        for server in servers:
            json_dicts.append(server._asdict())
        obj = {"servers": json_dicts}
        print(json.dumps(obj, indent=4))

    def print_result(self, result: Result):
        obj = {"result": asdict(result), "changed": result.changed}
        print(json.dumps(obj, indent=4))
        if result.failed_to_start or result.failed_to_stop:
            raise TakeOffCommandFailed("Failed to start or stop some servers")

    def error(self, error: TakeOffError):
        print(json.dumps(error.toJSON(), indent=4))

    def devfailed(self, error: tango.DevFailed):
        print(json.dumps(error, indent=4, cls=DevFailedEncoder))


class DevFailedEncoder(json.JSONEncoder):
    """
    Encodes a tango.DevFailed error to a python dict, for easy JSON
    serialisation
    """

    def default(self, error: tango.DevFailed):
        parts = []
        for part in error.args:
            parts.append(
                {
                    "desc": part.desc,
                    "origin": part.origin,
                    "reason": part.reason,
                    "severity": part.severity,
                }
            )
        return {"tango.DevFailed": parts}
