# Microsoft Azure Linux Agent
#
# Copyright 2018 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Requires Python 2.6+ and Openssl 1.0+
#

import socket
import time

from azurelinuxagent.common.datacontract import DataContract, DataContractList
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.utils.textutil import getattrib
from azurelinuxagent.common.version import DISTRO_VERSION, DISTRO_NAME, CURRENT_VERSION


VERSION_0 = "0.0.0.0"


class VMInfo(DataContract):
    def __init__(self,
                 subscriptionId=None,
                 vmName=None,
                 roleName=None,
                 roleInstanceName=None,
                 tenantName=None):
        self.subscriptionId = subscriptionId
        self.vmName = vmName
        self.roleName = roleName
        self.roleInstanceName = roleInstanceName
        self.tenantName = tenantName


class CertificateData(DataContract):
    def __init__(self, certificateData=None):
        self.certificateData = certificateData


class Cert(DataContract):
    def __init__(self,
                 name=None,
                 thumbprint=None,
                 certificateDataUri=None,
                 storeName=None,
                 storeLocation=None):
        self.name = name
        self.thumbprint = thumbprint
        self.certificateDataUri = certificateDataUri
        self.storeLocation = storeLocation
        self.storeName = storeName


class CertList(DataContract):
    def __init__(self):
        self.certificates = DataContractList(Cert)


class VMAgentFamily(object):
    def __init__(self, name):
        self.name = name
        # Two-state: None, string. Set to None if version not specified in the GS
        self.version = None
        # Tri-state: None, True, False. Set to None if this property not specified in the GS.
        self.is_version_from_rsm = None
        # Tri-state: None, True, False. Set to None if this property not specified in the GS.
        self.is_vm_enabled_for_rsm_upgrades = None

        self.uris = []

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "[name: '{0}' uris: {1}]".format(self.name, self.uris)


class ExtensionState(object):
    Enabled = ustr("enabled")
    Disabled = ustr("disabled")


class ExtensionRequestedState(object):
    """
    This is the state of the Handler as requested by the Goal State.
    CRP only supports 2 states as of now - Enabled and Uninstall
    Disabled was used for older XML extensions and we keep it to support backward compatibility.
    """
    Enabled = ustr("enabled")
    Disabled = ustr("disabled")
    Uninstall = ustr("uninstall")
    All = [Enabled, Disabled, Uninstall]


class ExtensionSettings(object):
    """
    The runtime settings associated with a Handler
    -   Maps to Extension.PluginSettings.Plugin.RuntimeSettings for single config extensions in the ExtensionConfig.xml
        Eg: 1.settings, 2.settings
    -   Maps to Extension.PluginSettings.Plugin.ExtensionRuntimeSettings for multi-config extensions in the
        ExtensionConfig.xml
        Eg: <extensionName>.1.settings, <extensionName>.2.settings
    """
    def __init__(self,
                 name=None,
                 sequenceNumber=None,
                 publicSettings=None,
                 protectedSettings=None,
                 certificateThumbprint=None,
                 dependencyLevel=0,
                 state=ExtensionState.Enabled):
        self.name = name
        self.sequenceNumber = sequenceNumber
        self.publicSettings = publicSettings
        self.protectedSettings = protectedSettings
        self.certificateThumbprint = certificateThumbprint
        self.dependencyLevel = dependencyLevel
        self.state = state

    def dependency_level_sort_key(self, handler_state):
        level = self.dependencyLevel
        # Process uninstall or disabled before enabled, in reverse order
        # Prioritize Handler state and Extension state both when sorting extensions
        # remap 0 to -1, 1 to -2, 2 to -3, etc
        if handler_state != ExtensionRequestedState.Enabled or self.state != ExtensionState.Enabled:
            level = (0 - level) - 1

        return level

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "{0}".format(self.name)


class Extension(object):
    """
    The main Plugin/handler specified by the publishers.
    Maps to Extension.PluginSettings.Plugins.Plugin in the ExtensionConfig.xml file
    Eg: Microsoft.OSTC.CustomScript
    """

    def __init__(self, name=None):
        self.name = name
        self.version = None
        self.state = None
        self.settings = []
        self.manifest_uris = []
        self.supports_multi_config = False
        self.encoded_signature = None
        self.__invalid_handler_setting_reason = None

    @property
    def is_invalid_setting(self):
        return self.__invalid_handler_setting_reason is not None

    @property
    def invalid_setting_reason(self):
        return self.__invalid_handler_setting_reason

    @invalid_setting_reason.setter
    def invalid_setting_reason(self, value):
        self.__invalid_handler_setting_reason = value

    def dependency_level_sort_key(self):
        levels = [e.dependencyLevel for e in self.settings]
        if len(levels) == 0:
            level = 0
        else:
            level = min(levels)
        # Process uninstall or disabled before enabled, in reverse order
        # remap 0 to -1, 1 to -2, 2 to -3, etc
        if self.state != u"enabled":
            level = (0 - level) - 1
        return level

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return "{0}-{1}".format(self.name, self.version)

class InVMGoalStateMetaData(DataContract):
    """
    Object for parsing the GoalState MetaData received from CRP
    Eg: <InVMGoalStateMetaData inSvdSeqNo="2" createdOnTicks="637405409304121230" activityId="555e551c-600e-4fb4-90ba-8ab8ec28eccc" correlationId="400de90b-522e-491f-9d89-ec944661f531" />
    """
    def __init__(self, in_vm_metadata_node):
        self.correlation_id = getattrib(in_vm_metadata_node, "correlationId")
        self.activity_id = getattrib(in_vm_metadata_node, "activityId")
        self.created_on_ticks = getattrib(in_vm_metadata_node, "createdOnTicks")
        self.in_svd_seq_no = getattrib(in_vm_metadata_node, "inSvdSeqNo")


class ExtHandlerPackage(DataContract):
    def __init__(self, version=None):
        self.version = version
        self.uris = []
        # TODO update the naming to align with metadata protocol
        self.isinternal = False
        self.disallow_major_upgrade = False


class ExtHandlerPackageList(DataContract):
    def __init__(self):
        self.versions = DataContractList(ExtHandlerPackage)


class VMProperties(DataContract):
    def __init__(self, certificateThumbprint=None):
        # TODO need to confirm the property name
        self.certificateThumbprint = certificateThumbprint


class ProvisionStatus(DataContract):
    def __init__(self, status=None, subStatus=None, description=None):
        self.status = status
        self.subStatus = subStatus
        self.description = description
        self.properties = VMProperties()


class ExtensionSubStatus(DataContract):
    def __init__(self, name=None, status=None, code=None, message=None):
        self.name = name
        self.status = status
        self.code = code
        self.message = message


class ExtensionStatus(DataContract):
    def __init__(self,
                 name=None,
                 configurationAppliedTime=None,
                 operation=None,
                 status=None,
                 seq_no=None,
                 code=None,
                 message=None):
        self.name = name
        self.configurationAppliedTime = configurationAppliedTime
        self.operation = operation
        self.status = status
        self.sequenceNumber = seq_no
        self.code = code
        self.message = message
        self.substatusList = DataContractList(ExtensionSubStatus)


class ExtHandlerStatus(DataContract):
    def __init__(self,
                 name=None,
                 version=None,
                 status=None,
                 code=0,
                 message=None):
        self.name = name
        self.version = version
        self.status = status
        self.code = code
        self.message = message
        self.supports_multi_config = False
        self.extension_status = None


class VMAgentStatus(DataContract):
    def __init__(self, status=None, message=None, gs_aggregate_status=None, update_status=None):
        self.status = status
        self.message = message
        self.hostname = socket.gethostname()
        self.version = str(CURRENT_VERSION)
        self.osname = DISTRO_NAME
        self.osversion = DISTRO_VERSION
        self.extensionHandlers = DataContractList(ExtHandlerStatus)
        self.vm_artifacts_aggregate_status = VMArtifactsAggregateStatus(gs_aggregate_status)
        self.update_status = update_status
        self._supports_fast_track = False

    @property
    def supports_fast_track(self):
        return self._supports_fast_track

    def set_supports_fast_track(self, value):
        self._supports_fast_track = value


class VMStatus(DataContract):
    def __init__(self, status, message, gs_aggregate_status=None, vm_agent_update_status=None):
        self.vmAgent = VMAgentStatus(status=status, message=message, gs_aggregate_status=gs_aggregate_status,
                                     update_status=vm_agent_update_status)


class GoalStateAggregateStatus(DataContract):
    def __init__(self, seq_no, status=None, message="", code=None):
        self.message = message
        self.in_svd_seq_no = seq_no
        self.status = status
        self.code = code
        self.__utc_timestamp = time.gmtime()

    @property
    def processed_time(self):
        return self.__utc_timestamp


class VMArtifactsAggregateStatus(DataContract):
    def __init__(self, gs_aggregate_status=None):
        self.goal_state_aggregate_status = gs_aggregate_status


class RemoteAccessUser(DataContract):
    def __init__(self, name, encrypted_password, expiration):
        self.name = name
        self.encrypted_password = encrypted_password
        self.expiration = expiration


class RemoteAccessUsersList(DataContract):
    def __init__(self):
        self.users = DataContractList(RemoteAccessUser)


class VMAgentUpdateStatuses(object):
    Success = ustr("Success")
    Transitioning = ustr("Transitioning")
    Error = ustr("Error")
    Unknown = ustr("Unknown")


class VMAgentUpdateStatus(object):
    def __init__(self, expected_version, status=VMAgentUpdateStatuses.Success, message="", code=0):
        self.expected_version = expected_version
        self.status = status
        self.message = message
        self.code = code
