"""
Common Pydantic Models for PICLE Client Shells
"""

import builtins
import functools
import json
import logging
import queue
import threading
import time
from datetime import datetime
from enum import Enum
from typing import List, Optional, Union
from uuid import uuid4  # random uuid

from pydantic import (
    BaseModel,
    Field,
    StrictBool,
    StrictInt,
    StrictStr,
)
from rich.console import Console

log = logging.getLogger(__name__)

# ---------------------------------------------------------------------------------------------
# COMMON FUNCTIONS
# ---------------------------------------------------------------------------------------------


def listen_events_thread(uuid, stop, NFCLIENT):
    """Helper function to pretty print events to command line"""
    richconsole = Console()
    start_time = time.time()
    time_fmt = "%d-%b-%Y %H:%M:%S.%f"
    richconsole.print(
        "-" * 45 + " Job Events " + "-" * 47 + "\n"
        f"{datetime.now().strftime(time_fmt)[:-3]} {uuid} job started"
    )
    while not (stop.is_set() or NFCLIENT.exit_event.is_set()):
        try:
            event = NFCLIENT.event_queue.get(block=True, timeout=0.1)
            NFCLIENT.event_queue.task_done()
        except queue.Empty:
            continue
        (
            empty,
            header,
            command,
            service,
            job_uuid,
            status,
            data,
        ) = event
        if job_uuid != uuid.encode("utf-8"):
            NFCLIENT.event_queue.put(event)
            continue

        # extract event parameters
        data = json.loads(data)
        service = data["service"]
        worker = data["worker"]
        task = data["task"]
        timestamp = data["timestamp"]
        message = data["message"]
        # color severity
        severity = data["severity"]
        severity = severity.replace("DEBUG", "[cyan]DEBUG[/cyan]")
        severity = severity.replace("INFO", "[green]INFO[/green]")
        severity = severity.replace("WARNING", "[yellow]WARNING[/yellow]")
        severity = severity.replace("CRITICAL", "[red]CRITICAL[/red]")
        # color status
        status = data["status"]
        status = status.replace("started", "[cyan]started[/cyan]")
        status = status.replace("completed", "[green]completed[/green]")
        status = status.replace("failed", "[red]failed[/red]")
        resource = data["resource"]
        if isinstance(resource, list):
            resource = ", ".join(resource)
        # log event message
        richconsole.print(
            f"{timestamp} {severity} {worker} {status} {service}.{task} {resource} - {message}"
        )

    elapsed = round(time.time() - start_time, 3)
    richconsole.print(
        f"{datetime.now().strftime(time_fmt)[:-3]} {uuid} job completed in {elapsed} seconds\n\n"
        + "-" * 45
        + " Job Results "
        + "-" * 44
        + "\n"
    )


def listen_events(fun):
    """Decorator to listen for events and print them to console"""

    @functools.wraps(fun)
    def wrapper(*args, **kwargs):
        NFCLIENT = builtins.NFCLIENT
        events_thread_stop = threading.Event()
        uuid = uuid4().hex
        progress = kwargs.get("progress", True)
        nowait = kwargs.get("nowait", False)

        # start events thread to handle job events printing
        if progress and nowait is False:
            events_thread = threading.Thread(
                target=listen_events_thread,
                name="NornirCliShell_events_listen_thread",
                args=(
                    uuid,
                    events_thread_stop,
                    NFCLIENT,
                ),
            )
            events_thread.start()

        # run decorated function
        try:
            res = fun(uuid, *args, **kwargs)
        finally:
            # stop events thread
            if NFCLIENT and progress and nowait is False:
                events_thread_stop.set()
                events_thread.join()

        return res

    return wrapper


def log_error_or_result(
    data: dict, verbose_result: bool = False, verbose_on_fail: bool = True
) -> dict:
    """
    Logs errors or messages from the provided data dictionary and returns a dictionary of results based on verbosity settings.

    Args:
        data (dict): A dictionary where each key is a worker name and each
            value is a dictionary containing job result
        verbose_result (bool, optional): If True, includes the full result
            dictionary for each worker in the return value
        verbose_on_fail (bool, optional): If True, includes the full result
            dictionary for failed tasks

    Returns:
        dict: A dictionary containing either the full result or just the "result"
            field for each worker, depending on verbosity settings.

    Logs:
        - Errors if present in the worker's result.
        - Informational messages if present and no errors exist.
    """
    ret = {}

    if data is None:
        log.error("Result data is empty.")
        return
    if not isinstance(data, dict):
        log.error(f"Data is not a dictionary but '{type(data)}'")
        return data

    for w_name, w_res in data.items():
        # decide what to log
        if w_res["errors"]:
            errors = "\n".join(w_res["errors"])
            log.error(f"{w_name} '{w_res['task']}' errors:\n{errors}")
        elif w_res["messages"]:
            messages = "\n".join(w_res["messages"])
            log.info(f"{w_name} '{w_res['task']}' messages:\n{messages}")

        # decide what results to return
        if verbose_result:
            ret[w_name] = w_res
        elif verbose_on_fail and w_res["failed"] is True:
            ret[w_name] = w_res
        elif w_res["result"]:
            ret[w_name] = w_res["result"]
        else:  # skip workers with no results
            pass

    return ret


# ---------------------------------------------------------------------------------------------
# COMMON MODELS
# ---------------------------------------------------------------------------------------------


class BoolEnum(Enum):
    TRUE = True
    FALSE = False


class ClientRunJobArgs(BaseModel):
    timeout: Optional[StrictInt] = Field(None, description="Job timeout")
    workers: Union[StrictStr, List[StrictStr]] = Field(
        "all", description="Filter workers to target"
    )
    verbose_result: StrictBool = Field(
        False,
        description="Control output details",
        json_schema_extra={"presence": True},
        alias="verbose-result",
    )
    progress: Optional[StrictBool] = Field(
        True,
        description="Display progress events",
        json_schema_extra={"presence": True},
    )
    nowait: Optional[StrictBool] = Field(
        False,
        description="Do not wait for job to complete",
        json_schema_extra={"presence": True},
    )

    @staticmethod
    def walk_norfab_files():
        NFCLIENT = builtins.NFCLIENT
        response = NFCLIENT.run_job("filesharing", "walk", kwargs={"url": "nf://"})
        wname, wres = next(iter(response.items()))
        return wres["result"]
