"""Beaker provider implementation."""

import inspect
import logging

import click
from dynaconf import Validator
from rich.console import Console
from rich.progress import track
from rich.syntax import Syntax

logger = logging.getLogger(__name__)

from broker import helpers
from broker.binds.beaker import BeakerBind
from broker.exceptions import BrokerError, ProviderError
from broker.hosts import Host
from broker.providers import Provider


class Beaker(Provider):
    """Beaker provider class providing a Broker interface around the Beaker bind."""

    _validators = [
        Validator("beaker.hub_url", must_exist=True),
        Validator("beaker.max_job_wait", default="24h"),
    ]
    _checkout_options = [
        click.option(
            "--job-xml",
            type=click.Path(exists=True, dir_okay=False),
            help="Path to the job XML file to submit",
        ),
        click.option(
            "--job-id",
            type=str,
            help="Beaker job ID to clone",
        ),
    ]
    _execute_options = [
        click.option(
            "--job-xml",
            type=str,
            help="Path to the job XML file to submit",
        ),
    ]
    _extend_options = [
        click.option(
            "--extend-duration",
            type=click.IntRange(1, 99),
            help="Number of hours to extend the job. Must be between 1 and 99",
        )
    ]

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.hub_url = self._settings.beaker.hub_url
        self.runtime = kwargs.pop("bind", BeakerBind)(self.hub_url, **kwargs)

    def _host_release(self):
        caller_host = inspect.stack()[1][0].f_locals["host"]
        if not (job_id := getattr(caller_host, "job_id", None)):
            job_id = self.runtime.jobid_from_system(caller_host.hostname)
        return self.release(caller_host.hostname, job_id)

    def _set_attributes(self, host_inst, broker_args=None, misc_attrs=None):
        host_inst.__dict__.update(
            {
                "_prov_inst": self,
                "_broker_provider": "Beaker",
                "_broker_provider_instance": self.instance,
                "_broker_args": broker_args,
                "release": self._host_release,
            }
        )
        if isinstance(misc_attrs, dict):
            host_inst._attrs = misc_attrs

    def _compile_host_info(self, host, broker_info=True):
        """Compiles host information into a dictionary suitable for use in the inventory.

        :param host (beaker.host.Host): The host to compile information for.

        :return: A dictionary containing the compiled host information.
        """
        curated_host_info = self.runtime.system_details_curated(host)
        if broker_info:
            curated_host_info.update(
                {
                    "_broker_provider": "Beaker",
                    "_broker_provider_instance": self.instance,
                    "_broker_args": getattr(host, "_broker_args", {}),
                }
            )
        if not curated_host_info.get("job_id"):
            curated_host_info["job_id"] = self.runtime.jobid_from_system(
                curated_host_info["hostname"]
            )
        return curated_host_info

    def construct_host(self, provider_params, host_classes, **kwargs):
        """Construct a broker host from a beaker system information.

        :param provider_params: a beaker system information dictionary

        :param host_classes: host object

        :return: constructed broker host object
        """
        logger.debug(f"constructing with {provider_params=}\n{host_classes=}\n{kwargs=}")
        if not provider_params:
            host_inst = host_classes[kwargs.get("type", "host")](**kwargs)
            # cont_inst = self._cont_inst_by_name(host_inst.name)
            self._set_attributes(host_inst, broker_args=kwargs)
        else:
            host_info = self._compile_host_info(provider_params["hostname"], broker_info=False)
            host_inst = host_classes[kwargs.get("type", "host")](
                broker_settings=self._settings, **provider_params
            )
            self._set_attributes(host_inst, broker_args=kwargs, misc_attrs=host_info)
        return host_inst

    @Provider.register_action("job_xml", "job_id")
    def submit_job(self, max_wait=None, **kwargs):
        """Submit a job to Beaker and wait for it to complete."""
        job = kwargs.get("job_xml") or kwargs.get("job_id")
        max_wait = max_wait or self._settings.beaker.get("max_job_wait")
        result = self.runtime.execute_job(job, max_wait)
        logger.debug(f"Job completed with results: {result}")
        return result

    def provider_help(self, jobs=False, job=None, **kwargs):
        """Print useful information from the Beaker provider."""
        rich_console = Console(no_color=self._settings.less_colors)
        if job:
            if not job.startswith("J:"):
                job = f"J:{job}"
            job_xml = self.runtime.job_clone(job, prettyxml=True, dryrun=True).stdout
            syntax = Syntax(job_xml, "xml", theme="monokai", line_numbers=True)
            rich_console.print(syntax)
            return {"job_id": job, "xml": job_xml}
        elif jobs:
            result = self.runtime.job_list(**kwargs).stdout.splitlines()
            if res_filter := kwargs.get("results_filter"):
                result = helpers.eval_filter(result, res_filter, "res")
                result = result if isinstance(result, list) else [result]
            if not result:
                logger.warning("No jobs found!")
                return
            job_table = helpers.dictlist_to_table(
                [{"name": j} for j in result],
                title="Available Jobs",
                _id=False,
                headers=False,
            )
            rich_console.print(job_table)
            return result

    def release(self, host_name, job_id):
        """Release a hosts reserved from Beaker by cancelling the job."""
        return self.runtime.job_cancel(job_id)
        # return self.runtime.system_release(host_name)

    def extend(self, host_name, extend_duration=99):
        """Extend the duration of a Beaker reservation."""
        try:
            Host(hostname=host_name).execute(f"/usr/bin/extendtesttime.sh {extend_duration}")
        except BrokerError as err:
            raise ProviderError(
                f"Failed to extend host {host_name}: {err}\n"
                f"Try running: root@{host_name} /usr/bin/extendtesttime.sh {extend_duration}"
            ) from err

    def get_inventory(self, *args):
        """Get a list of hosts and their information from Beaker."""
        hosts = self.runtime.user_systems()
        compiled_host_info = [
            self._compile_host_info(host)
            for host in track(hosts, description="Compiling host information")
        ]
        return compiled_host_info
