import datetime
import hashlib
import json
import importlib

from typing import Callable
from collections import defaultdict
from sqlalchemy.orm import Session
from operator import attrgetter

from watcher.db import Event
from watcher import enums
from watcher.logic.timezone import now
from watcher.db.base import dbconnect
from .base import lock_task


class TaskSender:
    def __init__(self, task_events):
        self.tasks = self.group_tasks(task_events=task_events)

    def group_tasks(self, task_events) -> dict:
        tasks = defaultdict(list)
        for task_event in task_events:
            tasks[task_event.object_data['name']].append(self._get_task_params(task_event))
        return tasks

    def send_start_people_allocation(self, tasks_params: list, task_function: Callable) -> None:
        tasks_by_group = defaultdict(set)
        for task_param in tasks_params:
            params = task_param['args'][1]
            params.update(task_param['kwargs'])
            schedules_group_id = params['schedules_group_id']
            start_date = params.get('start_date')
            tasks_by_group[schedules_group_id].add(start_date)

        for schedules_group_id, start_dates in tasks_by_group.items():
            if None in start_dates:
                start_date = None
            else:
                start_date = min(start_dates)
            # TODO: есть ощущение: что мы только ставим евенты тк форса нет найти или написать тест
            task_function.delay(
                schedules_group_id=schedules_group_id,
                start_date=start_date,
                force_task_delay=True,
            )

    def send_default(self, tasks_params: list, task_function: Callable) -> None:
        params_hash = {}
        for task_param in tasks_params:
            params_hash[self._hash_key(task_param)] = task_param

        if params_hash:
            for task_param in params_hash.values():
                task_param['kwargs'].update(force_task_delay=True)
                task_function.delay(*task_param['args'][0], **task_param['args'][1], **task_param['kwargs'])

    def send_tasks(self) -> None:
        for task_name, tasks_params in self.tasks.items():
            task_path, task_name = task_name.rsplit('.', 1)
            task_function = attrgetter(task_name)(importlib.import_module(task_path))
            sender = getattr(self, f'send_{task_name}', getattr(self, 'send_default'))
            sender(tasks_params=tasks_params, task_function=task_function)

    @staticmethod
    def _hash_key(task_params: dict) -> str:
        dhash = hashlib.md5()
        encoded_kwargs = json.dumps(task_params, sort_keys=True).encode()
        dhash.update(encoded_kwargs)
        return dhash.hexdigest()

    @staticmethod
    def _get_task_params(event: Event) -> dict:
        return {
            'args': event.object_data['args'],
            'kwargs': event.object_data['kwargs'],
        }


@lock_task(save_metrics=True, send_to_unistat=True)
@dbconnect
def send_scheduled_tasks(session: Session) -> None:
    task_events = session.query(Event).filter(
        Event.source == enums.EventSource.internal,
        Event.type == enums.EventType.task,
        Event.state == enums.EventState.new,
        Event.created_at < now() - datetime.timedelta(minutes=5),
    )

    sender = TaskSender(task_events=task_events)
    sender.send_tasks()

    task_events.update(
        {Event.state: enums.EventState.processed},
        synchronize_session=False,
    )
