# engine.py
#
# Copyright Uberware. All Rights Reserved

"""Manage Engines."""

import json
import logging
import typing
from uuid import UUID

from .component import (
    NameOrID,
    NameOrIDList,
    abort,
    ensure_list,
    run_component,
)
from .config import SMEDGE

# Use Python standard logging
logger = logging.getLogger("smedge")


ENGINE = SMEDGE / "Engine"
"""Location of the Engine executable, uses config.SMEDGE by default"""


RegexList = typing.Union[str, typing.List[str]]
"""A type for regex list arguments"""


EngineType = typing.Dict[str, typing.Any]
"""Type used for a Job"""


def get_engines(
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
) -> typing.List[EngineType]:
    """Get engine data

    Engine settings will be a dictionary. Engine product options (if any)
    will be entries in the dict with a UUID string key, and will be a child
    dict of keys that override settings for the product with that UUID.

    If you supply no engine names, IDs, or regex strings, it will list all
    engines in the system.

    Arguments:
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Returns:
        A list of dicts with engine data

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "List"] + _engine_list(engines, regex)
    cmd.append("-JSON")
    result = run_component(cmd)
    data = json.loads(result.stdout)
    if not isinstance(data, list):
        abort(result, f"Got unexpected data: {type(data)}")
    return data


def set_engines(
    settings: EngineType,
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
):
    """Set engine data

    Engine settings will be a dictionary. Engine product options (if any)
    will be entries in the dict with a UUID string key, and will be a child
    dict of keys that override settings for the product with that UUID.

    If you supply no engine names, IDs, or regex strings, it will affect the
    local machine's Engine if available.

    Arguments:
        settings: The engine settings to apply to the given engines.
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Returns:
        A list of dicts with engine data

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "Set", json.dumps(settings, separators=(",", ":"))]
    cmd.extend(_engine_list(engines, regex))
    run_component(cmd)


def enable_engines(
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
):
    """Enable engines to run any work

    If you supply no engine names, IDs, or regex strings, it will affect the
    local machine's Engine if available.

    Arguments:
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "Enable"] + _engine_list(engines, regex)
    run_component(cmd)


def disable_engines(
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
    stop_work: bool = False,
):
    """Disable engines to run any work

    If you supply no engine names, IDs, or regex strings, it will affect the
    local machine's Engine if available.

    Arguments:
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names
        stop_work: True will immediately abort any running work.
            False allows any running work to finish normally.

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "Disable", str(stop_work)] + _engine_list(engines, regex)
    run_component(cmd)


def enable_product(
    product: NameOrID,
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
):
    """Enable engines to run work from the given product.

    If you supply no engine names, IDs, or regex strings, it will affect the
    local machine's Engine if available.

    Arguments:
        product: The name, ID, or shortcut to a product
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "EnableProduct", str(product)]
    cmd.extend(_engine_list(engines, regex))
    run_component(cmd)


def disable_product(
    product: NameOrID,
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
):
    """Disable engines to run work from the given product.

    If you supply no engine names, IDs, or regex strings, it will affect the
    local machine's Engine if available.

    Arguments:
        product: The name, ID, or shortcut for a product
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "DisableProduct", str(product)]
    cmd.extend(_engine_list(engines, regex))
    run_component(cmd)


def reset_engine_failures(
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
):
    """Reset engine failure counts

    If you supply no engine names, IDs, or regex strings, it will affect the
    local machine's Engine if available.

    Arguments:
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "ResetFailures"] + _engine_list(engines, regex)
    run_component(cmd)


def download_file(engine: NameOrID, file: NameOrID) -> str:
    """Download a file from an engine

    Arguments:
        engine: the name or ID of the engine to get the file from
        file: the path or shared ID of the file to get

    Returns:
        a string with the file contents. Note, this can be large.

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "DownloadFile", str(file), str(engine)]
    result = run_component(cmd)
    return result.stdout


def get_engine_dispatch_log(
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
) -> typing.Dict[UUID, str]:
    """Get engine dispatch log reports

    If you supply no engine names, IDs, or regex strings, it will get the
    local machine's log if available.

    Arguments:
        engines: A name, id, or list of names or ids
        regex: A regex pattern or list of patterns of names

    Returns:
        a string with the file contents. Note, this can be large.

    Raises:
        SmedgeComponentError: Something went wrong
    """
    cmd = [ENGINE, "DispatchLog"] + _engine_list(engines, regex)
    result = run_component(cmd)
    logs = {}
    last_id = None
    for line in result.stdout.split("\n"):
        if line.startswith("Report for: "):
            last_id = UUID(line[-36:])
            logs[last_id] = []
            continue
        if not last_id:
            abort(result, "Expected Engine ID, got: {line}")
        logs[last_id].append(line)
    return {engine: "\n".join(log) for engine, log in logs.items()}


def _engine_list(
    engines: typing.Optional[NameOrIDList] = None,
    regex: typing.Optional[RegexList] = None,
):
    """Prepares the engine and regex arguments for running Engine commands"""
    result = ensure_list(engines)
    if regex:
        result.extend(["-regex"] + ensure_list(regex))
    return result
