#######################################################################################
# Copyright ETSI Contributors and Others.
#
# 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.
import logging
from temporalio import activity
from n2vc.temporal_libjuju import ConnectionInfo, Libjuju
from osm_common.temporal_constants import (
    ACTIVITY_TEST_VIM_CONNECTIVITY,
    ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST,
    ACTIVITY_DEPLOY_CHARM,
    ACTIVITY_CHECK_CHARM_STATUS,
)
from osm_common.dataclasses.temporal_dataclasses import (
    TestVimConnectivityInput,
    CreateModelInput,
    VduInstantiateInput,
    CharmInfo,
)


class JujuPaasConnector:
    """Handles Juju Controller operations.

    Args:
        db  (object):       Data Access Object
    """

    def __init__(self, db):
        self.db = db
        self.logger = logging.getLogger(f"lcm.act.{self.__class__.__name__}")

    def _decrypt_password(self, vim_content: dict) -> str:
        """Decrypt a password.
            vim_content     (dict):     VIM details as a dictionary

        Returns:
            plain text password (str)
        """
        return self.db.decrypt(
            vim_content["vim_password"],
            schema_version=vim_content["schema_version"],
            salt=vim_content["_id"],
        )

    def _get_connection_info(self, vim_id: str) -> ConnectionInfo:
        """Get VIM details from database using vim_id and returns
         the Connection Info to connect Juju Controller.

        Args:
            vim_id  (str):      VIM ID

        Returns:
            ConnectionInfo  (object)
        """
        vim_content = self.db.get_one("vim_accounts", {"_id": vim_id})
        endpoint = vim_content["vim_url"]
        username = vim_content["vim_user"]
        vim_config = vim_content["config"]
        cacert = vim_config["ca_cert_content"]
        cloud_name = vim_config["cloud"]
        cloud_credentials = vim_config["cloud_credentials"]
        password = self._decrypt_password(vim_content)
        return ConnectionInfo(
            endpoint, username, password, cacert, cloud_name, cloud_credentials
        )

    def _get_libjuju(self, vim_uuid):
        connection_info = self._get_connection_info(vim_uuid)
        return Libjuju(connection_info)

    @activity.defn(name=ACTIVITY_TEST_VIM_CONNECTIVITY)
    async def test_vim_connectivity(
        self, test_connectivity_input: TestVimConnectivityInput
    ) -> None:
        """Validates the credentials by attempting to connect to the given Juju Controller.

        Collaborators:
            DB Read:            vim_accounts
            Juju Controller:    Connect only

        Raises  (Retryable):
            ApplicationError    If any of password, cacert, cloud_credentials is invalid
                                or Juju controller is not reachable

        Activity Lifecycle:
            This activity should complete relatively quickly (in a few seconds).
            However, it would be reasonable to wait more than 72 seconds (network timeout)
            incase there are network issues.

            This activity will not report a heartbeat due to its
            short-running nature.

            It is recommended, although not necessary to implement a
            back-off strategy for this activity, as it will naturally block
            and wait on each connection attempt.
        """
        vim_id = test_connectivity_input.vim_uuid
        controller = None
        try:
            libjuju = self._get_libjuju(vim_id)
            controller = await libjuju.get_controller()
            message = f"Connection to juju controller succeeded for {vim_id}"
            self.logger.info(message)
        finally:
            await libjuju.disconnect_controller(controller)

    @activity.defn(name=ACTIVITY_CREATE_MODEL_IF_DOESNT_EXIST)
    async def create_model_if_doesnt_exist(
        self, create_model_input: CreateModelInput
    ) -> None:
        """Connects to Juju Controller. Create a new model if model_name does not exist

        Collaborators:
            DB Read:            vim_accounts
            Juju Controller:    Connect and create model.

        Raises  (Retryable):
            ApplicationError    If Juju controller is not reachable

        Activity Lifecycle:
            This activity should complete relatively quickly (in a few seconds).
            However, it would be reasonable to wait more than 72 seconds (network timeout)
            incase there are network issues.

            This activity will not report a heartbeat due to its
            short-running nature.

            It is recommended, although not necessary to implement a
            back-off strategy for this activity, as it will naturally block
            and wait on each connection attempt.
        """
        model_name = create_model_input.model_name
        libjuju = self._get_libjuju(create_model_input.vim_uuid)
        await libjuju.add_model(model_name)

    @activity.defn(name=ACTIVITY_DEPLOY_CHARM)
    async def deploy_charm(self, deploy_charm_input: VduInstantiateInput) -> None:
        """Deploys a charm.

        Collaborators:
            DB Read:            vim_accounts
            Juju Controller:    Connect and deploy charm

        Raises  (Retryable):
            ApplicationError    If Juju controller is not reachable

        Activity Lifecycle:
            This activity should complete relatively quickly (in a few seconds).
            However, it would be reasonable to wait more than 72 seconds (network timeout)
            incase there are network issues.

            This activity will not report a heartbeat due to its
            short-running nature.

            It is recommended, although not necessary to implement a
            back-off strategy for this activity, as it will naturally block
            and wait on each connection attempt.
        """
        model_name = deploy_charm_input.model_name
        libjuju = self._get_libjuju(deploy_charm_input.vim_uuid)
        charm_info = deploy_charm_input.charm_info
        await libjuju.deploy_charm(
            application_name=charm_info.app_name,
            path=charm_info.entity_url,
            model_name=model_name,
            channel=charm_info.channel,
        )

    @activity.defn(name=ACTIVITY_CHECK_CHARM_STATUS)
    async def check_charm_status(self, check_charm_status: VduInstantiateInput) -> None:
        """Validates the credentials by attempting to connect to the given Juju Controller.

        Collaborators:
            DB Read:            vim_accounts
            Juju Controller:    Connect to controller and check charm status.

        Raises  (Retryable):
            ApplicationError    If any of password, cacert, cloud_credentials is invalid
                                or Juju controller is not reachable

        Activity Lifecycle:
            This activity should complete relatively quickly (in a few seconds).
            However, it would be reasonable to wait more than 72 seconds (network timeout)
            incase there are network issues.

            This activity will not report a heartbeat due to its
            short-running nature.

            It is recommended, although not necessary to implement a
            back-off strategy for this activity, as it will naturally block
            and wait on each connection attempt.
        """
        pass


class CharmInfoUtils:
    @staticmethod
    def get_charm_info(vdu: dict, sw_image_descs: list) -> CharmInfo:
        """Extract the charm info of a VDU.
        Args:
            vdu (dict):    contains the charm information.
            sw_image_descs (list): list of images in the VNF.

        Returns:
            CharmInfo  (object)
        """
        app_name = vdu.get("id")
        channel = CharmInfoUtils._get_property_value(
            "channel", vdu.get("configurable-properties")
        )
        entity_url = CharmInfoUtils._get_entity_url(
            vdu.get("sw-image-desc"), sw_image_descs
        )
        return CharmInfo(app_name, channel, entity_url)

    @staticmethod
    def _get_property_value(wanted_property: str, configurable_properties: list) -> str:
        """Extract the value corresponding to a given key-value pairs in
            vdu.configurable-properties

        Args:
            wanted_property (str): property used as key.
            configurable_properties (list): list of configurable-properties in VDU.

        Returns:
            value of the wanted property  (str)
        """
        filtered_property = next(
            filter(
                lambda property: property.get("key") == wanted_property,
                configurable_properties,
            ),
            None,
        )
        return filtered_property.get("value") if filtered_property else None

    @staticmethod
    def _get_entity_url(sw_image_desc_id: str, sw_image_descs: list) -> str:
        """Extract the image field for a given image_id
        Args:
            sw_image_desc_id (str): ID of the image used by a VDU.
            sw_image_descs (list): information of available images in the VNF.

        Returns:
            image of the sw_image_desc_id (str)
        """
        filtered_image = next(
            filter(lambda image: image.get("id") == sw_image_desc_id, sw_image_descs),
            None,
        )
        return filtered_image.get("image") if filtered_image else None
