text: from posthog.models import Event, Team, Action, ActionStep, Element, User
  from rest_framework import request, serializers, viewsets, authentication # type: ignore
  from rest_framework.response import Response
  from rest_framework.decorators import action # type: ignore
  from rest_framework.exceptions import AuthenticationFailed
  from django.db.models import Q, F, Count, Prefetch
  from django.forms.models import model_to_dict
  from django.utils.decorators import method_decorator
  from typing import Any, List, Dict
  import pandas as pd # type: ignore
  import numpy as np # type: ignore
  import datetime
  from dateutil.relativedelta import relativedelta
  
  
  class ActionStepSerializer(serializers.HyperlinkedModelSerializer):
      class Meta:
          model = ActionStep
          fields = ['id', 'event', 'tag_name', 'text', 'href', 'selector', 'url', 'name']
  
  
  class ActionSerializer(serializers.HyperlinkedModelSerializer):
      steps = serializers.SerializerMethodField()
      count = serializers.SerializerMethodField()
  
      class Meta:
          model = Action
          fields = ['id', 'name', 'steps', 'created_at', 'deleted', 'count']
  
      def get_steps(self, action: Action):
          steps = action.steps.all().order_by('id')
          return ActionStepSerializer(steps, many=True).data
  
      def get_count(self, action: Action) -> int:
          if self.context['request'].GET.get('include_count', False):
              return Event.objects.filter_by_action(action, count=True)
          return None
  
  class TemporaryTokenAuthentication(authentication.BaseAuthentication):
      def authenticate(self, request: request.Request):
          # if the Origin is different, the only authentication method should be temporary_token
          # This happens when someone is trying to create actions from the editor on their own website
          if request.headers.get('Origin') and request.headers['Origin'] not in request.build_absolute_uri('/'):
              if not request.GET.get('temporary_token'):
                  raise AuthenticationFailed(detail="""No temporary_token set.
                      That means your either trying to access this API from a different site,
                      or it means your proxy isn\'t sending the correct headers.
                      See https://github.com/PostHog/posthog/wiki/Running-behind-a-proxy for more information.
                      """)
          if request.GET.get('temporary_token'):
              user = User.objects.filter(temporary_token=request.GET.get('temporary_token'))
              if not user.exists():
                  raise AuthenticationFailed(detail='User doesnt exist')
              return (user.first(), None)
          return None
  
  class ActionViewSet(viewsets.ModelViewSet):
      queryset = Action.objects.all()
      serializer_class = ActionSerializer
      authentication_classes = [TemporaryTokenAuthentication, authentication.SessionAuthentication, authentication.BasicAuthentication]
  
      def get_queryset(self):
          queryset = super().get_queryset()
          if self.action == 'list':
              queryset = queryset.filter(deleted=False)
  
          if self.request.GET.get('actions'):
              queryset = queryset.filter(pk__in=self.request.GET['actions'].split(','))
          queryset = queryset.prefetch_related(Prefetch('steps', queryset=ActionStep.objects.order_by('id')))
          return queryset\
              .filter(team=self.request.user.team_set.get())\
              .order_by('-id')
  
      def create(self, request: request.Request, *args: Any, **kwargs: Any) -> Response:
          action, created = Action.objects.get_or_create(
              name=request.data['name'],
              team=request.user.team_set.get(),
              deleted=False,
              defaults={
                  'created_by': request.user
              }
          )
          if not created:
              return Response(data={'detail': 'action-exists', 'id': action.pk}, status=400)
  
          if request.data.get('steps'):
              for step in request.data['steps']:
                  ActionStep.objects.create(
                      action=action,
                      **{key: value for key, value in step.items() if key not in ('isNew', 'selection')}
                  )
          return Response(ActionSerializer(action, context={'request': request}).data)
  
      def update(self, request: request.Request, *args: Any, **kwargs: Any) -> Response:
          action = Action.objects.get(pk=kwargs['pk'], team=request.user.team_set.get())
  
          # If there's no steps property at all we just ignore it
          # If there is a step property but it's an empty array [], we'll delete all the steps
          if 'steps' in request.data:
              steps = request.data.pop('steps')
              # remove steps not in the request
              step_ids = [step['id'] for step in steps if step.get('id')]
              action.steps.exclude(pk__in=step_ids).delete()
  
              for step in steps:
                  if step.get('id'):
                      db_step = ActionStep.objects.get(pk=step['id'])
                      step_serializer = ActionStepSerializer(db_step)
                      step_serializer.update(db_step, step)
                  else:
                      ActionStep.objects.create(
                          action=action,
                          **{key: value for key, value in step.items() if key not in ('isNew', 'selection')}
                      )
  
          serializer = ActionSerializer(action, context={'request': request})
          serializer.update(action, request.data)
          return Response(ActionSerializer(action, context={'request': request}).data)
  
      def list(self, request: request.Request, *args: Any, **kwargs: Any) -> Response:
          actions_list = []
          actions = self.get_queryset()
          actions_list = ActionSerializer(actions, many=True, context={'request': request}).data
          if request.GET.get('include_count', False):
              actions_list.sort(key=lambda action: action.get('count', action['id']), reverse=True)
          return Response({'results': actions_list})
  
      def _group_events_to_date(self, date_from, aggregates, steps, ):
          aggregates = pd.DataFrame([{'date': a.day, 'count': a.id} for a in aggregates])
          aggregates['date'] = aggregates['date'].dt.date
          # create all dates
          time_index = pd.date_range(date_from, periods=steps + 1, freq='D')
          grouped = pd.DataFrame(aggregates.groupby('date').mean(), index=time_index)
  
          # fill gaps
          grouped = grouped.fillna(0)
          return grouped
  
      def _where_query(self, request: request.Request, date_from: datetime.date):
          ret = []
  
          for key, value in request.GET.items():
              if key not in ('days', 'actions', 'display', 'breakdown'):
                  ret.append(['(posthog_event.properties -> %s) = %s', [key, '"{}"'.format(value)]])
          if date_from:
              ret.append(['posthog_event.timestamp >= %s', [date_from]])
          return ret
  
      def _breakdown(self, action: Action, breakdown_by: str, where: List) -> Dict:
          events = Event.objects.filter_by_action(action, where=where)
          events = Event.objects.filter(pk__in=[event.id for event in events])
  
          key = "properties__{}".format(breakdown_by)
          events = events\
              .values(key)\
              .annotate(count=Count('id'))\
              .order_by('-count')
  
          return [{'name': item[key] if item[key] else 'undefined', 'count': item['count']} for item in events]
  
      @action(methods=['GET'], detail=False)
      def trends(self, request: request.Request, *args: Any, **kwargs: Any) -> Response:
          actions = self.get_queryset().filter(deleted=False)
          actions_list = []
          steps = int(request.GET.get('days', 7))
          date_from = datetime.date.today() - relativedelta(days=steps)
          date_to = datetime.date.today()
          for action in actions:
              append = {
                  'action': {
                      'id': action.pk,
                      'name': action.name
                  },
                  'label': action.name,
                  'count': 0,
                  'breakdown': []
              }
              where = self._where_query(request, date_from)
              aggregates = Event.objects.filter_by_action(action, count_by='day', where=where)
              if len(aggregates) > 0:
                  dates_filled = self._group_events_to_date(date_from=date_from, aggregates=aggregates, steps=steps)
                  values = [value[0] for key, value in dates_filled.iterrows()]
                  append['labels'] = [key.strftime('%-d %B') for key, value in dates_filled.iterrows()]
                  append['data'] = values
                  append['count'] = sum(values)
              if request.GET.get('breakdown'):
                  append['breakdown'] = self._breakdown(action, breakdown_by=request.GET['breakdown'], where=where)
              actions_list.append(append)
          return Response(actions_list)
  