#  Quapp Platform Project
#  qibo_device.py
#  Copyright © CITYNOW Co. Ltd. All rights reserved.

from abc import ABC

from quapp_common.model.device.custom_device import CustomDevice
from quapp_common.data.response.job_response import JobResponse

from quapp_common.component.callback.update_job_metadata import update_job_metadata
from quapp_common.enum.status.job_status import JobStatus
from quapp_common.enum.media_type import MediaType
from quapp_common.enum.status.status_code import StatusCode
from quapp_common.data.response.authentication import Authentication
from quapp_common.data.response.custom_header import CustomHeader
from quapp_common.data.device.circuit_running_option import CircuitRunningOption
from quapp_common.enum.invocation_step import InvocationStep

from quapp_common.util.response_utils import build_error_job_response

from quapp_common.data.callback.callback_url import CallbackUrl
from quapp_common.util.json_parser_utils import parse



class QiboDevice(CustomDevice, ABC):

    def _produce_histogram_data(self, job_result) -> dict | None:
        return None

    def _get_provider_job_id(self, job) -> str:
        return None

    def _get_job_status(self, job) -> str:
        return "DONE"

    def _calculate_execution_time(self, job_result):
        return job_result.get("execution_time", None)

    def _get_job_result(self, job):
        return job
    def _get_shots(self, job_result) -> int | None:
        return None

    def _on_execution(self, authentication: Authentication,
        project_header: CustomHeader,
        workspace_header: CustomHeader,
        execution_callback: CallbackUrl, circuit,
        options: CircuitRunningOption):
        """

        @param authentication: authentication information
        @param project_header: project header information
        @param execution_callback: execution step callback urls
        @param circuit: circuit will be run
        @param options: options will use for running
        @return: job and job response
        """
        self.logger.debug("On execution started")

        job_response = JobResponse(authentication=authentication,
                                   project_header=project_header,
                                   workspace_header=workspace_header,
                                   status_code=StatusCode.DONE)

        update_job_metadata(job_response=job_response,
                            callback_url=execution_callback.on_start)
        try:
            job = self._create_job(circuit=circuit, options=options)
            job_response.provider_job_id = self._get_provider_job_id(job)
            job_response.job_status = self._get_job_status(job)

            job_result = self._get_job_result(job)
            job_response.job_result = job_result
            original_job_result = job_result

            update_job_metadata(job_response=job_response,
                                callback_url=execution_callback.on_done)

            return original_job_result, job_response

        except Exception as exception:
            self.logger.debug(
                "Execute job failed with error {}".format(str(exception)))

            job_response.status_code = StatusCode.ERROR
            job_response.content_type = MediaType.APPLICATION_JSON
            job_response.job_status = JobStatus.ERROR.value
            job_response.job_result = {"error": str(exception)}

            update_job_metadata(job_response=job_response,
                                callback_url=execution_callback.on_error)
            return None, None

    def run_circuit(self,
        circuit,
        post_processing_fn,
        options: CircuitRunningOption,
        callback_dict: dict,
        authentication: Authentication,
        project_header: CustomHeader,
        workspace_header: CustomHeader):

        original_job_result, job_response = self._on_execution(
            authentication=authentication,
            project_header=project_header,
            workspace_header=workspace_header,
            execution_callback=callback_dict.get(InvocationStep.EXECUTION),
            circuit=circuit,
            options=options)

        if original_job_result is None:
            return

        job_response = self._on_analysis(
            job_response=job_response,
            original_job_result=original_job_result,
            analysis_callback=callback_dict.get(InvocationStep.ANALYSIS))

        if job_response is None:
            return

        self._on_finalization(job_result=original_job_result.get('result'),
                              authentication=authentication,
                              post_processing_fn=post_processing_fn,
                              finalization_callback=callback_dict.get(
                                  InvocationStep.FINALIZATION),
                              project_header=project_header,
                              workspace_header=workspace_header)

    def _on_analysis(self, job_response: JobResponse,
                     analysis_callback: CallbackUrl, original_job_result):
        """

        @param job_response:
        @param analysis_callback:
        @param original_job_result:
        @return:
        """

        self.logger.debug("On analysis started")

        update_job_metadata(job_response, analysis_callback.on_start)

        try:
            self.logger.debug(
                    "Producing histogram data from original job result")

            job_response.job_histogram = self._produce_histogram_data(
                    original_job_result)


            self.logger.debug("Calculating execution time from parsed results")
            self._calculate_execution_time(original_job_result)
            job_response.execution_time = self.execution_time

            self.logger.debug("Retrieving shot count from job result")
            job_response.shots = self._get_shots(original_job_result)

            update_job_metadata(job_response, analysis_callback.on_done)

            self.logger.info("Analysis completed successfully")
            return job_response

        except Exception as exception:
            self.logger.exception(
                    f"Exception when analyzing job result: {exception}",
                    exc_info=True)

            job_response = build_error_job_response(exception, job_response,
                                                    message='Error when analyzing job result')

            update_job_metadata(job_response, analysis_callback.on_error)
            return None