# -*- coding: utf-8 -*-
#
from collections import defaultdict

from django.core.cache import cache
from django.http.response import JsonResponse
from django.utils import timezone
from rest_framework.views import APIView

from accounts.const import AutomationTypes, ChangeSecretRecordStatusChoice
from accounts.models import ChangeSecretAutomation, AutomationExecution, ChangeSecretRecord
from assets.models import Node, Asset
from common.const import Status
from common.permissions import IsValidLicense
from common.utils import lazyproperty
from common.utils.timezone import local_zero_hour, local_now
from ops.celery import app
from rbac.permissions import RBACPermission

__all__ = ['ChangeSecretDashboardApi']


class ChangeSecretDashboardApi(APIView):
    http_method_names = ['get']
    rbac_perms = {
        'GET': 'accounts.view_changesecretautomation',
    }
    permission_classes = [RBACPermission, IsValidLicense]
    tp = AutomationTypes.change_secret
    task_name = 'accounts.tasks.automation.execute_account_automation_task'
    ongoing_change_secret_cache_key = "ongoing_change_secret_cache_key"

    @lazyproperty
    def days(self):
        count = self.request.query_params.get('days', 1)
        return int(count)

    @property
    def days_to_datetime(self):
        if self.days == 1:
            return local_zero_hour()
        return local_now() - timezone.timedelta(days=self.days)

    def get_queryset_date_filter(self, qs, query_field='date_updated'):
        return qs.filter(**{f'{query_field}__gte': self.days_to_datetime})

    @lazyproperty
    def date_range_list(self):
        return [
            (local_now() - timezone.timedelta(days=i)).date()
            for i in range(self.days - 1, -1, -1)
        ]

    def filter_by_date_range(self, queryset, field_name):
        date_range_bounds = self.days_to_datetime.date(), (local_now() + timezone.timedelta(days=1)).date()
        return queryset.filter(**{f'{field_name}__range': date_range_bounds})

    def calculate_daily_metrics(self, queryset, date_field):
        filtered_queryset = self.filter_by_date_range(queryset, date_field)
        results = filtered_queryset.values_list(date_field, 'status')

        status_counts = defaultdict(lambda: defaultdict(int))

        for date_finished, status in results:
            dt_local = timezone.localtime(date_finished)
            date_str = str(dt_local.date())
            if status == ChangeSecretRecordStatusChoice.failed:
                status_counts[date_str]['failed'] += 1
            elif status == ChangeSecretRecordStatusChoice.success:
                status_counts[date_str]['success'] += 1

        metrics = defaultdict(list)
        for date in self.date_range_list:
            date_str = str(date)
            for status in ['success', 'failed']:
                metrics[status].append(status_counts[date_str].get(status, 0))

        return metrics

    def get_daily_success_and_failure_metrics(self):
        metrics = self.calculate_daily_metrics(self.change_secret_records_queryset, 'date_finished')
        return metrics.get('success', []), metrics.get('failed', [])

    @lazyproperty
    def change_secrets_queryset(self):
        return ChangeSecretAutomation.objects.all()

    @lazyproperty
    def change_secret_records_queryset(self):
        return ChangeSecretRecord.get_valid_records()

    def get_change_secret_asset_queryset(self):
        qs = self.change_secrets_queryset
        node_ids = qs.values_list('nodes', flat=True).distinct()
        nodes = Node.objects.filter(id__in=node_ids).only('id', 'key')
        node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
        direct_asset_ids = qs.values_list('assets', flat=True).distinct()
        asset_ids = set(list(direct_asset_ids) + list(node_asset_ids))
        return Asset.objects.filter(id__in=asset_ids)

    def get_filtered_counts(self, qs, field=None):
        if field is None:
            return qs.count()
        return self.get_queryset_date_filter(qs, field).count()

    def get_status_counts(self, executions):
        executions = executions.filter(type=self.tp)
        total, failed, success = 0, 0, 0
        for status in executions.values_list('status', flat=True):
            total += 1
            if status in [Status.failed, Status.error]:
                failed += 1
            elif status == Status.success:
                success += 1

        return {
            'total_count_change_secret_executions': total,
            'total_count_success_change_secret_executions': success,
            'total_count_failed_change_secret_executions': failed,
        }

    def get(self, request, *args, **kwargs):
        query_params = self.request.query_params
        data = {}

        _all = query_params.get('all')

        if _all or query_params.get('total_count_change_secrets'):
            data['total_count_change_secrets'] = self.get_filtered_counts(
                self.change_secrets_queryset
            )

        if _all or query_params.get('total_count_periodic_change_secrets'):
            data['total_count_periodic_change_secrets'] = self.get_filtered_counts(
                self.change_secrets_queryset.filter(is_periodic=True)
            )

        if _all or query_params.get('total_count_change_secret_assets'):
            data['total_count_change_secret_assets'] = self.get_change_secret_asset_queryset().count()

        if _all or query_params.get('total_count_change_secret_status'):
            executions = self.get_queryset_date_filter(AutomationExecution.objects.all(), 'date_start')
            data.update(self.get_status_counts(executions))

        if _all or query_params.get('daily_success_and_failure_metrics'):
            success, failed = self.get_daily_success_and_failure_metrics()
            data.update({
                'dates_metrics_date': [date.strftime('%m-%d') for date in self.date_range_list] or ['0'],
                'dates_metrics_total_count_success': success,
                'dates_metrics_total_count_failed': failed,
            })

        if _all or query_params.get('total_count_ongoing_change_secret'):
            ongoing_counts = cache.get(self.ongoing_change_secret_cache_key)
            if ongoing_counts is None:
                execution_ids = []
                inspect = app.control.inspect()
                try:
                    active_tasks = inspect.active()
                except Exception:
                    active_tasks = None
                if active_tasks:
                    for tasks in active_tasks.values():
                        for task in tasks:
                            _id = task.get('id')
                            name = task.get('name')
                            tp = task.get('kwargs', {}).get('tp')
                            if name == self.task_name and tp == self.tp:
                                execution_ids.append(_id)

                snapshots = AutomationExecution.objects.filter(id__in=execution_ids).values_list('snapshot', flat=True)

                asset_ids = {asset for i in snapshots for asset in i.get('assets', [])}
                account_ids = {account for i in snapshots for account in i.get('accounts', [])}

                ongoing_counts = (len(execution_ids), len(asset_ids), len(account_ids))
                data['total_count_ongoing_change_secret'] = ongoing_counts[0]
                data['total_count_ongoing_change_secret_assets'] = ongoing_counts[1]
                data['total_count_ongoing_change_secret_accounts'] = ongoing_counts[2]
                cache.set(self.ongoing_change_secret_cache_key, ongoing_counts, 60)
            else:
                data['total_count_ongoing_change_secret'] = ongoing_counts[0]
                data['total_count_ongoing_change_secret_assets'] = ongoing_counts[1]
                data['total_count_ongoing_change_secret_accounts'] = ongoing_counts[2]

        return JsonResponse(data, status=200)
