"""Implements the JobParametersInterface for generic_command."""

import logging
from collections import namedtuple
from typing import Dict, List, Optional, Set

from pydantic import Field, validator

from jade.models import JadeBaseModel
from jade.common import DEFAULT_SUBMISSION_GROUP
from jade.jobs.job_parameters_interface import JobParametersInterface


logger = logging.getLogger(__name__)

_EXTENSION = "generic_command"


class GenericCommandParameters(JobParametersInterface):
    """A class used for creating a job for a generic command."""

    parameters_type = namedtuple("GenericCommand", "command")

    def __init__(self, **kwargs):
        self._model = GenericCommandParametersModel(**kwargs)

    def __str__(self):
        return "<GenericCommandParameters: {}>".format(self.name)

    def __getattr__(self, name):
        if name in GenericCommandParametersModel.__fields__:
            return getattr(self._model, name)
        raise AttributeError(f"'GenericCommandParameters' object has no attribute '{name}'")

    def __setattr__(self, name, value):
        if name == "extension":
            raise AttributeError(f"'GenericCommandParameters' does not allow setting 'extension'")
        if name in GenericCommandParametersModel.__fields__:
            setattr(self._model, name, value)
        self.__dict__[name] = value

    @property
    def command(self):
        if self._model.use_multi_node_manager:
            return f"jade-internal run-multi-node-job {self.name} {self._model.command}"
        return self._model.command

    @property
    def estimated_run_minutes(self):
        return self._model.estimated_run_minutes

    @property
    def extension(self):
        return _EXTENSION

    @property
    def name(self):
        return self._create_name()

    def _create_name(self):
        return str(self._model.job_id)

    def serialize(self):
        assert self._model.job_id is not None
        # If job sizes get huge then we should exclude parameters with default values.
        return self._model.dict()

    @classmethod
    def deserialize(cls, data):
        return cls(**data)

    @property
    def cancel_on_blocking_job_failure(self):
        return self._model.cancel_on_blocking_job_failure

    def get_blocking_jobs(self):
        return self._model.blocked_by

    def remove_blocking_job(self, name):
        self._model.blocked_by.remove(name)

    def set_blocking_jobs(self, blocking_jobs):
        self._model.blocked_by = blocking_jobs

    @property
    def submission_group(self):
        return self._model.submission_group


class GenericCommandParametersModel(JadeBaseModel):
    """Model definition for generic command parameters"""

    use_multi_node_manager: Optional[bool] = Field(
        title="use_multi_node_manager",
        description="If true JADE will run this job with its multi-node manager.",
        default=False,
    )
    command: str = Field(
        title="command",
        description="Command that can be invoked in a terminal (shell characters not allowed)",
    )
    blocked_by: Optional[Set[str]] = Field(
        title="blocked_by",
        description="Array of job names that must complete before this one can start.",
        default=set(),
    )
    cancel_on_blocking_job_failure: Optional[bool] = Field(
        title="cancel_on_blocking_job_failure",
        description="If true JADE will cancel this job if any of its blocking jobs fail.",
        default=False,
    )
    estimated_run_minutes: Optional[int] = Field(
        title="estimated_run_minutes",
        description="JADE will use this value along with num-processes and walltime to "
        "build per-node batches of jobs if time-based-batching is enabled.",
    )
    submission_group: Optional[str] = Field(
        title="submission_group",
        description="Optional name of a submission group",
        default=DEFAULT_SUBMISSION_GROUP,
    )
    append_output_dir: Optional[bool] = Field(
        title="append_output_dir",
        description="If true JADE will append --jade-runtime-output=X where X is the output "
        "directory specified in jade submit-jobs.",
        default=False,
    )
    ext: Optional[Dict] = Field(
        title="ext",
        description="User-defined extension data to be used at runtime. Must be serializable in "
        "JSON format.",
        default={},
    )
    job_id: Optional[int] = Field(
        title="job_id",
        description="Unique job identifier, generated by JADE",
    )
    extension: Optional[str] = Field(
        title="extension",
        description="job extension type, generated by JADE",
        default=_EXTENSION,
    )

    @validator("append_output_dir")
    def handle_append_output_dir(cls, value, values):
        if values["use_multi_node_manager"]:
            logger.debug("Override 'append_output_dir' because 'use_multi_node_manager' is set")
            return True
        return value

    @validator("blocked_by")
    def handle_blocked_by(cls, value):
        return {str(x) for x in value}
