import json
from typing import *

from django.http import HttpRequest
from pydantic import TypeAdapter, ValidationError
from pydantic_core import PydanticCustomError, InitErrorDetails

from django.utils.translation import pgettext
from django.core.exceptions import PermissionDenied
from annotated_types import MinLen
from wcd_settings.setters import (
    PathType, SettingsSetAction, SettingsSetFailure, SettingsSetter,
)
from wcd_settings.registries import SettingsDTO
from wcd_settings import shortcuts

from .base import BaseView, DescriptorResolverViewMixin
from .validation import exceptions_handler, make_validation_error, prefix_validation


__all__ = (
    'SetterView',
    'AppSettingsSet', 'app_settings_set_view',
    'UserSettingsSet', 'user_settings_set_view',
)


class SetAction(SettingsDTO):
    path: Annotated[PathType, MinLen(1)]
    value: Any


SetActions = TypeAdapter(Annotated[List[SetAction], MinLen(1)])


class SetterView(DescriptorResolverViewMixin, BaseView):
    source_actions_dto = SetActions
    setter_action_class = SettingsSetAction

    def make_setter_action(self, key, path, value):
        with prefix_validation([key]):
            return self.setter_action_class(key=key, path=path, value=value)

    def get_settings(self, request: HttpRequest):
        raise NotImplementedError()

    def parse_sett_actions(self, request: HttpRequest, settings: SettingsSetter):
        body = json.loads(request.body)
        sources = self.source_actions_dto.validate_python(
            body.get('actions', []),
            context={'request': request},
        )
        descriptors = {x.key: x for x in self.get_descriptors(settings, request)}
        actions = []

        for value in sources:
            key, path = value.path[0], value.path[1:]

            if key not in descriptors:
                raise make_validation_error(
                    'unknown_key',
                    pgettext('wcd_settings:error', 'Unknown key {key}').format(key=key),
                    loc=[key],
                )

            actions.append(self.make_setter_action(key, path, value.value))

        return actions

    def get_descriptors(self, settings: SettingsSetter, request: HttpRequest):
        return [
            x for x in super().get_descriptors(settings, request)
            if x.is_editable
        ]

    def raise_failures(self, failures: Dict[str, List[SettingsSetFailure]]):
        raise ValidationError.from_exception_data(
            '',
            line_errors=[
                InitErrorDetails(
                    type=PydanticCustomError(failure.reason, failure.message),
                    loc=[key] + failure.path,
                    input=None,
                )
                for key, items in failures.items()
                for failure in items
            ]
        )

    def result(self, request: HttpRequest):
        return {}

    def handle_exception(self, exception: Exception, request: HttpRequest):
        return exceptions_handler(exception, context={'request': request})

    def check_permissions(self):
        pass

    def result(self, request: HttpRequest):
        self.check_permissions()
        settings = self.get_settings(request)
        actions = self.parse_sett_actions(request, settings)
        commit, failures = settings.set(actions, context={'request': request})

        if len(failures) > 0:
            self.raise_failures(failures)

        result = commit()

        return {'updated': len(result)}

    def post(self, request: HttpRequest):
        status = 200

        try:
            result = self.result(request)
        except Exception as e:
            errors, status = self.handle_exception(e, request)
            result = {'errors': errors}

        return self.render_to_response(result, status=status)


class AppSettingsSet(SetterView):
    def check_permissions(self):
        if not self.request.user.is_superuser:
            raise PermissionDenied()

    def get_settings(self, request: HttpRequest):
        return shortcuts.app_settings(request)

app_settings_set_view = AppSettingsSet.as_view()


class UserSettingsSet(SetterView):
    def get_settings(self, request: HttpRequest):
        return shortcuts.user_settings(request)

    def check_permissions(self):
        if not self.request.user.is_authenticated:
            raise PermissionDenied()

    def make_setter_action(self, key, path, value):
        with prefix_validation([key]):
            return self.setter_action_class(
                key=(self.request.user.id, key),
                path=path,
                value=value,
            )

    def raise_failures(self, failures: Dict[str, List[SettingsSetFailure]]):
        raise ValidationError.from_exception_data(
            '',
            line_errors=[
                InitErrorDetails(
                    type=PydanticCustomError(failure.reason, failure.message),
                    loc=[key] + failure.path,
                    input=None,
                )
                for (_, key), items in failures.items()
                for failure in items
            ]
        )

user_settings_set_view = UserSettingsSet.as_view()
