from functools import partial
from typing import Iterable

from citrine._rest.paginator import Paginator
from citrine._rest.resource import Resource
from citrine._serialization import properties
from citrine._utils.functions import format_escaped_url
from citrine.informatics.descriptors import Descriptor
from citrine.informatics.design_candidate import DesignCandidate
from citrine.informatics.predict_request import PredictRequest
from citrine.informatics.scores import Score
from citrine.informatics.executions.execution import Execution


class DesignExecution(Resource["DesignExecution"], Execution):
    """The execution of a DesignWorkflow.

    Possible statuses are INPROGRESS, SUCCEEDED, and FAILED.
    Design executions also have a ``status_description`` field with more information.

    """

    _paginator: Paginator = Paginator()
    _collection_key = 'response'
    workflow_id = properties.UUID('workflow_id', serializable=False)
    """:UUID: Unique identifier of the workflow that was executed"""
    version_number = properties.Integer("version_number", serializable=False)
    """:int: Integer identifier that increases each time the workflow is executed. The first
    execution has version_number = 1."""

    score = properties.Object(Score, 'score')
    """:Score: score by which this execution was evaluated"""
    descriptors = properties.List(properties.Object(Descriptor), 'descriptors')
    """:List[Descriptor]: all of the descriptors in the candidates generated by this execution"""

    def _path(self):
        return format_escaped_url(
            '/projects/{project_id}/design-workflows/{workflow_id}/executions/{execution_id}',
            project_id=self.project_id,
            workflow_id=self.workflow_id,
            execution_id=self.uid
        )

    @classmethod
    def _build_candidates(cls, subset_collection: Iterable[dict]) -> Iterable[DesignCandidate]:
        for candidate in subset_collection:
            yield DesignCandidate.build(candidate)

    def candidates(self, *, per_page: int = 100) -> Iterable[DesignCandidate]:
        """Fetch the Design Candidates for the particular execution, paginated."""
        path = self._path() + '/candidates'

        fetcher = partial(self._fetch_page, path=path, fetch_func=self._session.get_resource)

        return self._paginator.paginate(page_fetcher=fetcher,
                                        collection_builder=self._build_candidates,
                                        per_page=per_page)

    def predict(self,
                predict_request: PredictRequest) -> DesignCandidate:
        """Invoke a prediction on a design candidate."""
        path = self._path() + '/predict'

        res = self._session.post_resource(path, predict_request.dump(), version=self._api_version)
        return DesignCandidate.build(res)
