#######################################################################################
# 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 asyncio
import traceback

from osm_common.temporal.activities.paas import CreateModel
from osm_common.temporal.activities.ns import (
    GetVnfDetails,
    GetNsRecord,
    UpdateNsState,
)
from osm_common.temporal.workflows.lcm import LcmOperationWorkflow
from osm_common.temporal.workflows.ns import NsInstantiateWorkflow
from osm_common.temporal.workflows.vnf import VnfInstantiateWorkflow
from osm_common.temporal.states import NsState
from temporalio import workflow
from temporalio.converter import value_to_type
from temporalio.exceptions import ActivityError, ChildWorkflowError


@workflow.defn(name=NsInstantiateWorkflow.__name__, sandboxed=False)
class NsInstantiateWorkflowImpl(LcmOperationWorkflow):
    @workflow.run
    async def wrap_nslcmop(self, workflow_input: NsInstantiateWorkflow.Input) -> None:
        await super().wrap_nslcmop(workflow_input=workflow_input)

    async def run(self, workflow_input: NsInstantiateWorkflow.Input) -> None:
        self.logger.info(
            f"Executing {NsInstantiateWorkflow.__name__} with {workflow_input}"
        )

        # TODO: Can we clean up the input? Perhaps this workflow could receive NsInstantiateInput directly.
        ns_uuid = workflow_input.nslcmop["nsInstanceId"]
        vim_uuid = workflow_input.nslcmop["operationParams"]["vimAccountId"]
        model_name = self._get_namespace(ns_uuid, vim_uuid)
        try:
            await workflow.execute_activity(
                activity=CreateModel.__name__,
                arg=CreateModel.Input(vim_uuid=vim_uuid, model_name=model_name),
                activity_id=f"{CreateModel.__name__}-{ns_uuid}",
                schedule_to_close_timeout=NsInstantiateWorkflow.default_schedule_to_close_timeout,
                retry_policy=NsInstantiateWorkflow.no_retry_policy,
            )
            activities_results = await asyncio.gather(
                workflow.execute_activity(
                    activity=GetVnfDetails.__name__,
                    arg=GetVnfDetails.Input(ns_uuid=ns_uuid),
                    activity_id=f"{GetVnfDetails.__name__}-{ns_uuid}",
                    schedule_to_close_timeout=NsInstantiateWorkflow.default_schedule_to_close_timeout,
                    retry_policy=NsInstantiateWorkflow.no_retry_policy,
                ),
                workflow.execute_activity(
                    activity=GetNsRecord.__name__,
                    arg=GetNsRecord.Input(nsr_uuid=ns_uuid),
                    activity_id=f"{GetNsRecord.__name__}-{ns_uuid}",
                    schedule_to_close_timeout=NsInstantiateWorkflow.default_schedule_to_close_timeout,
                    retry_policy=NsInstantiateWorkflow.no_retry_policy,
                ),
            )
            get_vnf_details, get_ns_record = value_to_type(
                GetVnfDetails.Output, activities_results[0]
            ), value_to_type(GetNsRecord.Output, activities_results[1])

            await asyncio.gather(
                *(
                    workflow.execute_child_workflow(
                        workflow=VnfInstantiateWorkflow.__name__,
                        arg=VnfInstantiateWorkflow.Input(
                            vnfr_uuid=vnfr_uuid,
                            model_name=model_name,
                            instantiation_config=NsInstantiateWorkflowImpl.get_vnf_config(
                                vnf_member_index_ref, get_ns_record.nsr
                            ),
                        ),
                        id=f"{VnfInstantiateWorkflow.__name__}-{vnfr_uuid}",
                    )
                    for vnfr_uuid, vnf_member_index_ref in get_vnf_details.vnf_details
                )
            )

        except ActivityError as e:
            err_details = str(e.cause.with_traceback(e.__traceback__))
            await self.update_ns_state(ns_uuid, NsState.INSTANTIATED, err_details)
            self.logger.error(
                f"{NsInstantiateWorkflow.__name__} failed with {err_details}"
            )
            raise e

        except ChildWorkflowError as e:
            err_details = str(e.cause.with_traceback(e.cause.__traceback__))
            await self.update_ns_state(ns_uuid, NsState.INSTANTIATED, err_details)
            self.logger.error(
                f"{NsInstantiateWorkflow.__name__} failed with {err_details}"
            )
            raise e

        except Exception as e:
            err_details = str(traceback.format_exc())
            await self.update_ns_state(ns_uuid, NsState.INSTANTIATED, err_details)
            self.logger.error(
                f"{NsInstantiateWorkflow.__name__} failed with {err_details}"
            )
            raise e

        await self.update_ns_state(ns_uuid, NsState.INSTANTIATED, "Done")

    @staticmethod
    async def update_ns_state(
        ns_uuid: str,
        state: NsState,
        message: str,
    ) -> None:
        activity_input = UpdateNsState.Input(ns_uuid, state, message)
        await workflow.execute_activity(
            activity=UpdateNsState.__name__,
            arg=activity_input,
            activity_id=f"{UpdateNsState.__name__}-{ns_uuid}",
            schedule_to_close_timeout=NsInstantiateWorkflow.default_schedule_to_close_timeout,
            retry_policy=NsInstantiateWorkflow.retry_policy,
        )

    def _get_namespace(self, ns_id: str, vim_id: str) -> str:
        """The NS namespace is the combination if the NS ID and the VIM ID."""
        return ns_id[-12:] + "-" + vim_id[-12:]

    @staticmethod
    def get_vnf_config(vnf_member_index_ref: str, nsr: dict) -> dict:
        """Get the VNF instantiation config
        Args:
            vnf_member_index_ref    (str):     VNF member-index-ref
            nsr                     (dict):     NS record

        Returns:
            vnf_config  (dict)  VNF instantiation config

        """
        for vnf_config in nsr.get("instantiate_params", {}).get("vnf", {}):
            if vnf_config.get("member-vnf-index") == vnf_member_index_ref:
                return vnf_config
        return {}
