#
# Copyright (C) 2024 CESNET z.s.p.o.
#
# oarepo-workflows is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
# details.
#
"""Generators for needs/excludes/queries based on workflows."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, override

from opensearch_dsl.query import Query

from oarepo_workflows import FromRecordWorkflow, Workflow, WorkflowRequest
from oarepo_workflows.errors import (
    EventTypeNotInWorkflow,
    InvalidWorkflowError,
    MissingWorkflowError,
    RequestTypeNotInWorkflow,
)
from oarepo_workflows.proxies import current_oarepo_workflows

if TYPE_CHECKING:
    from flask_principal import Need
    from invenio_records_permissions.generators import Generator
    from invenio_records_resources.records import Record
    from invenio_requests.customizations import EventType, RequestType
    from invenio_requests.records.api import Request


class MissingTopicError(ValueError):
    """Raised when the topic is missing in the request generator arguments."""

    pass


def _get_workflow_code_from_workflow(workflow: Workflow) -> str:
    """Return the workflow code from the workflow."""
    workflow_code = next(
        iter(
            k
            for k, v in current_oarepo_workflows.record_workflows.items()
            if v is workflow
        )
    )
    return workflow_code


class RequestPolicyWorkflowCreators(FromRecordWorkflow):
    """Base class that generates creators from a workflow request."""

    def _kwargs_parser(self, **kwargs: Any) -> dict[str, Any]:
        """Transform the kwargs for subsequent methods."""
        return kwargs

    def _requester_generator(self, **kwargs: Any) -> Generator:
        """Return the requesters as a single requester generator."""
        raise NotImplementedError()

    def _get_workflow_request(
        self, request_type_id: str, **kwargs: Any
    ) -> tuple[Workflow, WorkflowRequest]:
        """Return the workflow request from the context."""
        if "record" not in kwargs:
            raise MissingTopicError(
                "Topic not found in request permissions generator arguments, can't get workflow."
            )
        record = kwargs["record"]
        workflow = current_oarepo_workflows.get_workflow(record)
        workflow_requests = workflow.requests()
        try:
            workflow_request = workflow_requests[request_type_id]
        except KeyError as e:
            workflow_code = _get_workflow_code_from_workflow(workflow)
            raise RequestTypeNotInWorkflow(
                request_type=request_type_id,
                workflow_code=workflow_code,
            ) from e
        return workflow, workflow_request

    # return empty needs on MissingTopicError
    # match None in query filter
    # excludes empty needs
    def needs(self, **context: Any) -> list[Need]:  # type: ignore
        """Return the needs generated by the workflow permission."""
        try:
            context = self._kwargs_parser(**context)
            generator = self._requester_generator(**context)
            return generator.needs(**context)
        except (MissingWorkflowError, InvalidWorkflowError, MissingTopicError):
            return []

    def excludes(self, **context: Any) -> list[Need]:
        """Return the needs excluded by the workflow permission."""
        try:
            context = self._kwargs_parser(**context)
            generator = self._requester_generator(**context)
            return generator.excludes(**context)
        except (MissingWorkflowError, InvalidWorkflowError, MissingTopicError):
            return []

    # not tested
    def query_filter(
        self, record: Record = None, request_type: RequestType = None, **context: Any
    ) -> Query:
        """Return the query filter generated by the workflow permission."""
        try:
            context = self._kwargs_parser(**context)
            generator = self._requester_generator(
                record=record, request_type=request_type, **context
            )
            return generator.query_filter(
                record=record, request_type=request_type, **context
            )
        except (MissingWorkflowError, InvalidWorkflowError, MissingTopicError):
            return Query("match_none")


class RequestCreatorsFromWorkflow(RequestPolicyWorkflowCreators):
    """Generates creators from a workflow request to be used in the request 'create' permission."""

    def __init__(self) -> None:
        """Initialize the generator."""
        super().__init__(action="create")

    @override
    def _requester_generator(self, **kwargs: Any) -> Generator:
        request_type: RequestType = kwargs["request_type"]
        workflow, workflow_request = self._get_workflow_request(
            request_type.type_id, **kwargs
        )
        return workflow_request.requester_generator


class EventCreatorsFromWorkflow(RequestPolicyWorkflowCreators):
    """Generates creators from a workflow request to be used in the event 'create' permission."""

    def __init__(self) -> None:
        """Initialize the generator."""
        super().__init__(action="create")

    @override
    def _kwargs_parser(self, **kwargs: Any) -> dict[str, Any]:
        request: Request = kwargs["request"]
        kwargs.setdefault("request_type", request.type)
        try:
            kwargs["record"] = request.topic.resolve()
        except Exception as e:  # noqa              TODO: better exception catching here
            raise MissingTopicError(
                "Topic not found in request event permissions generator arguments, can't get workflow."
            ) from e
        return kwargs

    @override
    def _requester_generator(self, **kwargs: Any) -> Generator:
        request_type: RequestType = kwargs["request_type"]
        event_type: EventType = kwargs["event_type"]
        workflow, workflow_request = self._get_workflow_request(
            request_type.type_id,
            **kwargs,
        )
        try:
            workflow_event = workflow_request.allowed_events[event_type.type_id]
        except KeyError as e:
            workflow_code = _get_workflow_code_from_workflow(workflow)
            raise EventTypeNotInWorkflow(
                request_type=request_type.type_id,
                workflow_code=workflow_code,
                event_type=event_type.type_id,
            ) from e
        return workflow_event.submitter_generator
