"""iDRAC query bios registry

Command provides raw query bios registry.
python idrac_ctl.py bios-registry --attr_name SystemServiceTag,OldSetupPassword

Will return SystemServiceTag,OldSetupPassword and list of all attributes.

python idrac_ctl.py bios-registry --attr_list --attr_name SystemServiceTag,OldSetupPassword

Author Mus spyroot@gmail.com
"""
import warnings
from abc import abstractmethod
from typing import Optional

import requests

from idrac_ctl import Singleton, ApiRequestType, IDracManager, CommandResult, UnexpectedResponse, InvalidArgument
from idrac_ctl.shared import ScheduleJobType
from datetime import datetime


class BiosChangeSettings(IDracManager, scm_type=ApiRequestType.BiosChangeSettings,
                         name='bios_change_settings',
                         metaclass=Singleton):
    """A command changes bios settings
    """

    def __init__(self, *args, **kwargs):
        super(BiosChangeSettings, self).__init__(*args, **kwargs)

    @staticmethod
    @abstractmethod
    def register_subcommand(cls):
        """Register command and all optional flags.
        :param cls:
        :return:
        """
        cmd_parser = cls.base_parser(is_reboot=True, is_expanded=False)
        cmd_parser.add_argument('--attr_name', type=str,
                                required=True, dest="attr_name", metavar="attribute name",
                                default=None,
                                help="attribute name or list. Example --attr_name MemTest,EmbSata")

        cmd_parser.add_argument('--attr_value', type=str,
                                required=True, dest="attr_value", metavar="attribute value",
                                default=None,
                                help="attribute name or list. Example --attr_values Disabled,RaidMode")

        cmd_parser.add_argument('apply', choices=['on-reset', 'auto-boot', 'maintenance'],
                                default="on-reset",
                                help="choose when to apply change.")

        cmd_parser.add_argument("--start_date",
                                help="The start of maintenance window format YYYY-MM-DD",
                                required=False,
                                dest="start_date", metavar="start date",
                                type=str)

        cmd_parser.add_argument("--start_time",
                                help="The start of maintenance window format HH:MM:SS",
                                required=False, default="00:00:00",
                                dest="start_time", metavar="start time",
                                type=str)

        cmd_parser.add_argument('--duration', type=int, required=False,
                                dest="default_duration",
                                metavar="duration",
                                default=600,
                                help="maximum duration for a maintenance window from a start time")

        help_text = "command change bios values"
        return cmd_parser, "bios-change", help_text

    @staticmethod
    def crete_bios_config(current_config, attr_name, attr_val) -> dict:
        """Create new config for a bios.
        :param current_config:
        :param attr_name:
        :param attr_val:
        :return:
        """

        bios_payload = {
            "Attributes": {
            }
        }

        attribute_names = []
        attribute_values = []

        if attr_name is not None and len(attr_name) > 0:
            if "," in attr_name:
                attribute_names = attr_name.split(",")
            else:
                attribute_names = [attr_name]

        if attr_val is not None and len(attr_val) > 0:
            if "," in attr_val:
                attribute_values = attr_val.split(",")
            else:
                attribute_values = [attr_val]

        if len(attribute_names) != len(attribute_values):
            raise InvalidArgument("Number of attribute "
                                  "and values mismatched.")

        for name, val in zip(attribute_names, attribute_values):
            bios_payload["Attributes"][name.strip()] = val.strip()

        for k, v in bios_payload["Attributes"].items():
            for current_data in current_config:
                if k in current_data.values() and current_data['Type'] == "Integer":
                    bios_payload['Attributes'][k] = int(v)

        return bios_payload

    def execute(self,
                attr_name: Optional[str] = None,
                attr_value: Optional[str] = None,
                apply: Optional[str] = "on-reset",
                filename: Optional[str] = None,
                data_type: Optional[str] = "json",
                verbose: Optional[bool] = False,
                do_async: Optional[bool] = False,
                do_reboot: Optional[bool] = False,
                start_date: Optional[str] = "",
                start_time: Optional[str] = "",
                default_duration: Optional[int] = 600,
                **kwargs) -> CommandResult:
        """Executes command to change bios settings.

        :param do_reboot:
        :param default_duration:
        :param apply:
        :param attr_value:  attribute value or list of values
        :param attr_name: attribute name or list of values
        :param do_async: note async will subscribe to an event loop.
        :param filename: if filename indicate call will save a bios setting to a file.
        :param verbose: enables verbose output
        :param data_type: json or xml
        :param start_time:
        :param start_date:
        :return: CommandResult and if filename provide will save to a file.
        """
        target_api = "/redfish/v1/Systems/System.Embedded.1/Bios/BiosRegistry"
        cmd_result = self.base_query(target_api,
                                     filename=filename,
                                     do_async=do_async,
                                     do_expanded=False)

        registry = cmd_result.data['RegistryEntries']
        attribute_data = registry['Attributes']

        payload = self.crete_bios_config(attribute_data, attr_name, attr_value)
        if apply.strip() == "auto-boot":
            start_timestamp = datetime.fromisoformat(f'{start_date}T{start_time}')
            base_payload = self.schedule_job(ScheduleJobType.AutoReboot,
                                             start_time=str(start_timestamp),
                                             duration_time=default_duration)
        elif apply.strip() == "maintenance":
            start_timestamp = datetime.fromisoformat(f'{start_date}T{start_time}')
            base_payload = self.schedule_job(ScheduleJobType.NoReboot,
                                             start_time=str(start_timestamp),
                                             duration_time=default_duration)
        else:
            base_payload = self.schedule_job(ScheduleJobType.OnReset,
                                             start_time=None, duration_time=None)

        base_payload.update(payload)
        if verbose:
            print(f"payload: {base_payload}")

        target_api = "/redfish/v1/Systems/System.Embedded.1/Bios/Settings"
        api_result = self.base_patch(target_api, payload=payload,
                                     do_async=do_async, expected_status=200)
        if verbose:
            resp = api_result.extra
            print(f"api_result.data: hdr {resp.headers}")
            print(f"api_result.data: states {resp.status_code}")
            print(f"api_result.data: data {resp.json()}")

        result_data = api_result.data

        if api_result.extra is not None:
            resp = api_result.extra
            try:
                if isinstance(resp, requests.models.Response):
                    json_data = resp.json()
                    if verbose:
                        self.default_json_printer(json_data)

                    job_id = self.job_id_from_header(resp)
                    if job_id is not None:
                        data = self.fetch_job(job_id)
                        if isinstance(api_result.data, dict):
                            result_data.update(data)
            except UnexpectedResponse as ur:
                warnings.warn(str(ur))
                pass

        if do_reboot:
            cmd_result = self.sync_invoke(ApiRequestType.ChassisQuery,
                                          "chassis_service_query",
                                          data_filter="PowerState")

            if isinstance(cmd_result.data, dict) and 'PowerState' in cmd_result.data:
                pd_state = cmd_result.data['PowerState']
                if pd_state == 'On':
                    cmd_result = self.sync_invoke(ApiRequestType.RebootHost,
                                                  "reboot",
                                                  reset_type="ForceRestart")
                    if 'Status' in cmd_result.data:
                        result_data.update({"Reboot": cmd_result.data['Status']})
                else:
                    warnings.warn(f"Can't reboot a host , chassis power state {pd_state}")
            else:
                warnings.warn(f"Failed fetch chassis power state")

        return CommandResult({}, None, None)
