import itertools
import logging
import time
from collections import defaultdict
from typing import Dict, Iterable, Any, Tuple

import celery
from django.conf import settings
from django.db.models import F
from django.utils import timezone
from requests import Response

from idm.celery_app import app
from idm.core.models import System, CommandTimestamp
from idm.framework.task import BaseTask
from idm.users.constants.user import USER_TYPES
from idm.core.utils import create_or_update_model
from idm.utils import events
from idm.utils import http
from idm.utils.lock import lock

log = logging.getLogger(__name__)

UPLOAD_ROLES_URL_PATH = '/v1/upload_roles'
MANAGE_SLUG_URL_PATH = '/v1/manage_slug'


class ExportRoles(BaseTask):
    """
    Запускает выгрузку ролей определенных систем в динамическую YT таблицу.
    """
    monitor_success = False

    def init(self):
        system_ids = set(i['system_id'] for i in events.get_events(events.EventType.YT_EXPORT_REQUIRED))
        system_id_to_tvm = (
            System.objects
            .operational()
            .filter(id__in=system_ids)
            .exclude(tvm_id='', tvm_tirole_ids__len=0)
            .values_list('slug', 'tvm_id', 'tvm_tirole_ids')
        )

        for slug, main_tvm_id, tvm_tirole_ids in system_id_to_tvm:
            if tvm_ids := {int(tvm_id) for tvm_id in itertools.chain([main_tvm_id], tvm_tirole_ids or []) if tvm_id}:
                ExportRoles.delay(step='export_roles', system_slug=slug, tvm_ids=tuple(tvm_ids))

    def generate_blob(self, roles: Iterable[Dict]) -> Dict:
        timestamp = int(time.time())
        blob_data = {
            'revision': timestamp,
            'born_date': timestamp,
            'tvm': defaultdict(lambda: defaultdict(list)),
            'user': defaultdict(lambda: defaultdict(list)),
        }

        skipped_roles = []

        for role in roles:
            blob_part = None
            if role['user_type'] == USER_TYPES.TVM_APP and role['user_tvm']:
                blob_part = blob_data['tvm'][role['user_tvm']]
            elif role['user_type'] == USER_TYPES.USER and role['user_uid']:
                blob_part = blob_data['user'][role['user_uid']]

            if blob_part is None:
                skipped_roles.append(role['id'])
                continue

            blob_part[role['node_slug']].append(role['data'] or {})

        if skipped_roles:
            self.log.warning('Roles with undefined user: %s', skipped_roles)

        for item_type in ('tvm', 'user'):
            if not blob_data[item_type]:
                blob_data.pop(item_type)

        return blob_data

    def export_roles(self, system_slug: str, tvm_ids: Tuple[int]):
        with lock(f'export_roles_{system_slug}') as acquired:
            if not acquired:
                return

            task_start = timezone.now()

            send_roles = self._send_roles(system_slug=system_slug)
            send_tvm = self._send_tvm_ids(system_slug=system_slug, tvm_ids=tvm_ids)

            if send_tvm and send_roles and settings.ENABLE_COMMAND_MONITORING:
                create_or_update_model(
                    model=CommandTimestamp,
                    obj_filter={'command': f'{self.task_name}.{system_slug}'},
                    defaults={
                        'last_success_start': task_start,
                        'last_success_finish': timezone.now(),
                    }
                )

    def _send_roles(self, system_slug: str) -> bool:
        system = System.objects.get(slug=system_slug)
        seen_event_ids = [i['_id'] for i in events.get_events(events.EventType.YT_EXPORT_REQUIRED, system.id)]
        roles = (
            system.roles
            .active()
            .of_user()
            .values(
                'id',
                user_type=F('user__type'),
                user_uid=F('user__uid'),
                user_tvm=F('user__username'),
                node_slug=F('node__slug_path'),
                data=F('fields_data'),
            )
            .order_by('id')
        )

        response = self._post_data(
            path=UPLOAD_ROLES_URL_PATH,
            data={'system_slug': system_slug, 'roles': self.generate_blob(roles)},
        )

        if response.status_code == 200:
            events.remove_events(seen_event_ids)
            return True

        log.error(f'Got error while sending tvm_ids to tirole: {response.content!r}')
        return False

    def _send_tvm_ids(self, system_slug: str, tvm_ids: Tuple[int]) -> bool:
        response = self._post_data(path=MANAGE_SLUG_URL_PATH, data={'system_slug': system_slug, 'tvmid': tvm_ids})

        if response.status_code == 200:
            return True

        log.error(f'Got error while sending tvm_ids to tirole: {response.content!r}')
        return False

    def _post_data(self, path: str, data: Dict[str, Any]) -> Response:
        response = http.post(
            http.validate_url(settings.TIROLE_HOST).with_path(path),
            json=data,
            tvm_id=settings.TIROLE_TVM_ID,
        )
        return response


ExportRoles: celery.Task = app.register_task(ExportRoles())
