# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Views for the server application: lookups."""

import logging
from collections.abc import Sequence

from rest_framework import status
from rest_framework.parsers import JSONParser
from rest_framework.request import Request
from rest_framework.response import Response

from debusine.db.context import context
from debusine.db.models.lookups import LookupResult
from debusine.server.collections.lookup import lookup_multiple, lookup_single
from debusine.server.exceptions import DebusineAPIException
from debusine.server.serializers import (
    LookupMultipleSerializer,
    LookupResponseSerializer,
    LookupSingleSerializer,
)
from debusine.server.views.base import BaseAPIView
from debusine.server.views.rest import IsUserTokenAuthenticated
from debusine.tasks.models import LookupMultiple

logger = logging.getLogger(__name__)


class LookupSingleView(BaseAPIView):
    """View used to look up a single collection item."""

    permission_classes = [IsUserTokenAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request: Request) -> Response:
        """
        Resolve a lookup for a single collection item.

        The request data must be a :ref:`lookup-single`.
        """
        lookup_deserialized = LookupSingleSerializer(data=request.data)
        lookup_deserialized.is_valid(raise_exception=True)

        workspace = lookup_deserialized.validated_data["workspace"]
        self.enforce(workspace.can_display)

        if workflow_root := lookup_deserialized.validated_data.get(
            "workflow_root"
        ):
            self.enforce(workflow_root.can_display)

        try:
            result = lookup_single(
                lookup_deserialized.validated_data["lookup"],
                default_category=lookup_deserialized.validated_data.get(
                    "default_category"
                ),
                workspace=workspace,
                pc=context.pc,
                workflow_root=workflow_root,
                expect_type=lookup_deserialized.validated_data["expect_type"],
            )
        except KeyError as e:
            raise DebusineAPIException(
                title="No matches",
                detail=str(e),
                status_code=status.HTTP_404_NOT_FOUND,
            )
        except LookupError as e:
            raise DebusineAPIException(
                title="Lookup error",
                detail=str(e),
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        return Response(
            LookupResponseSerializer(result).data, status=status.HTTP_200_OK
        )


class LookupMultipleView(BaseAPIView):
    """View used to look up multiple collection items."""

    permission_classes = [IsUserTokenAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request: Request) -> Response:
        """
        Resolve a lookup for a single collection item.

        The request data must be a :ref:`lookup-multiple`.
        """
        lookup_deserialized = LookupMultipleSerializer(data=request.data)
        lookup_deserialized.is_valid(raise_exception=True)

        workspace = lookup_deserialized.validated_data["workspace"]
        self.enforce(workspace.can_display)

        if workflow_root := lookup_deserialized.validated_data.get(
            "workflow_root"
        ):
            self.enforce(workflow_root.can_display)

        try:
            lookup = LookupMultiple.model_validate(
                lookup_deserialized.validated_data["lookup"]
            )
        except ValueError as e:
            raise DebusineAPIException(
                title="Cannot deserialize lookup",
                validation_errors={"lookup": str(e)},
            )

        try:
            result: Sequence[LookupResult] = lookup_multiple(
                lookup,
                default_category=lookup_deserialized.validated_data.get(
                    "default_category"
                ),
                workspace=workspace,
                pc=context.pc,
                workflow_root=workflow_root,
                expect_type=lookup_deserialized.validated_data["expect_type"],
            )
        except KeyError as e:
            raise DebusineAPIException(
                title="One of the lookups returned no matches",
                detail=str(e),
                status_code=status.HTTP_404_NOT_FOUND,
            )
        except LookupError as e:
            raise DebusineAPIException(
                title="Lookup error",
                detail=str(e),
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        # The type stubs don't mandle many=True:
        # https://github.com/typeddjango/djangorestframework-stubs/issues/260
        return Response(
            LookupResponseSerializer(
                result,  # type: ignore[arg-type]
                many=True,
            ).data,
            status=status.HTTP_200_OK,
        )
