from collections import defaultdict
from datetime import timedelta
from uuid import uuid4

import logging
import pytz
import requests

from django.conf import settings
from django.db import transaction
from django.db.models import Exists, OuterRef
from django.db.models.query import QuerySet
from django.forms import model_to_dict
from django.utils import timezone

from intranet.femida.src.celery_app import app, get_retry_countdown
from intranet.femida.src.notifications.publication_subscriptions import (
    PublicationSubscriptionNotification,
)
from intranet.femida.src.startrek.utils import get_issue
from intranet.femida.src.stats.utils import get_beginning_of_moscow_day
from intranet.femida.src.utils.lock import locked_task
from intranet.femida.src.utils.tvm2_client import get_service_ticket
from intranet.femida.src.utils.urls import dict_to_url_query_string
from intranet.femida.src.vacancies.helpers import get_suitable_vacancies
from intranet.femida.src.vacancies.models import Vacancy, PublicationSubscription, SubmissionForm
from intranet.femida.src.vacancies.startrek.issues import (
    create_job_issue,
    create_vacancy_edit_issue,
    start_vacancy_approvement,
)

logger = logging.getLogger(__name__)


PublicationSubscriptionVacancy = PublicationSubscription.shown_vacancies.through


@app.autoretry_task(max_retries=5)
def create_job_issue_task(vacancy_id):
    """
    Если не вышло создать задачу синхронно, будем пытаться создать ее отложенно несколько раз.
    """
    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    if vacancy.startrek_key:
        logger.warning(
            'JOB issue was already created for vacancy %s: %s',
            vacancy_id,
            vacancy.startrek_key,
        )
        return
    create_job_issue(vacancy)


@app.task(max_retries=5)
def start_vacancy_approvement_task(vacancy_id, author, approvers, uid=None):
    vacancy = (
        Vacancy.unsafe
        .select_related('department')
        .get(id=vacancy_id)
    )
    uid = uid or uuid4().hex
    try:
        start_vacancy_approvement(vacancy, author, approvers, uid)
    except Exception as exc:
        retries = start_vacancy_approvement_task.request.retries
        start_vacancy_approvement_task.retry(
            countdown=get_retry_countdown(retries),
            exc=exc,
            args=[],
            kwargs=dict(
                vacancy_id=vacancy_id,
                author=author,
                approvers=approvers,
                uid=uid,
            ),
        )


@app.autoretry_task(max_retries=3)
def vacancy_close_by_issue_task(vacancy_id):
    from intranet.femida.src.vacancies.controllers import vacancy_close_by_issue

    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    issue = get_issue(vacancy.startrek_key)
    vacancy_close_by_issue(vacancy, issue)


@app.autoretry_task(max_retries=3)
def vacancy_change_type_by_issue_task(vacancy_id):
    from intranet.femida.src.vacancies.controllers import vacancy_change_type_by_issue

    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    issue = get_issue(vacancy.startrek_key)
    vacancy_change_type_by_issue(vacancy, issue)


@app.autoretry_task(max_retries=3)
def vacancy_approve_by_issue_task(vacancy_id):
    from intranet.femida.src.vacancies.controllers import vacancy_approve_by_issue

    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    issue = get_issue(vacancy.startrek_key)
    vacancy_approve_by_issue(vacancy, issue)


@app.autoretry_task(max_retries=3)
def vacancy_bp_created_task(vacancy_id, new_bp_id):
    from intranet.femida.src.vacancies.controllers import change_issue_bp_by_vacancy_new_bp

    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    change_issue_bp_by_vacancy_new_bp(vacancy, new_bp_id)


@app.autoretry_task(max_retries=3)
def vacancy_approve_bp_by_issue_task(vacancy_id):
    from intranet.femida.src.vacancies.controllers import vacancy_approve_bp_by_issue

    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    vacancy_approve_bp_by_issue(vacancy)


@app.task(max_retries=5)
def create_vacancy_edit_issue_task(vacancy_id, submission_form_id, uid=None):
    vacancy = Vacancy.unsafe.get(id=vacancy_id)
    submission_form = SubmissionForm.objects.get(id=submission_form_id)
    uid = uid or uuid4().hex
    try:
        create_vacancy_edit_issue(vacancy, submission_form, uid)
    except Exception as exc:
        retries = create_vacancy_edit_issue_task.request.retries
        create_vacancy_edit_issue_task.retry(
            countdown=get_retry_countdown(retries),
            exc=exc,
            args=[],
            kwargs=dict(
                vacancy_id=vacancy_id,
                submission_form_id=submission_form_id,
                uid=uid,
            ),
        )


@app.task
@locked_task
def push_updated_vacancies_to_oebs(from_datetime=None, to_datetime=None):
    data = _get_vacancies_data(from_datetime, to_datetime)
    _push_to_oebs(data)


def _get_vacancies_data(from_datetime=None, to_datetime=None):
    if from_datetime is None:
        from_datetime = timezone.now() - timedelta(days=1)

    vacancies = (
        Vacancy.unsafe
        .prefetch_related('vacancy_history')
        .filter(vacancy_history__changed_at__gte=from_datetime)
        .distinct()
    )

    if to_datetime:
        vacancies = vacancies.filter(vacancy_history__changed_at__lte=to_datetime)

    data = {
        'vacancies': [],
    }

    for vacancy in vacancies:
        history_data_by_bp = defaultdict(list)
        tz = pytz.timezone('Europe/Moscow')
        for vacancy_history in vacancy.vacancy_history.all():
            history_data_by_bp[vacancy_history.budget_position_id].append({
                'date': vacancy_history.changed_at.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S.%f'),
                'status': vacancy_history.status,
                'resolution': vacancy_history.resolution,
                'full_name': vacancy_history.full_name,
            })
        for bp_id, history_data in history_data_by_bp.items():
            data['vacancies'].append({
                'vacancyID': vacancy.id,
                'bpNumber': bp_id,
                'statuses': history_data,
            })
    return data


def _push_to_oebs(data):
    oebs_vacancies_url = '{}/rest/vacancystatuses'.format(settings.OEBS_API_URL)
    headers = {
        'X-Ya-Service-Ticket': get_service_ticket(settings.TVM_OEBS_API_CLIENT_ID),
    }
    try:
        res = requests.post(
            url=oebs_vacancies_url,
            json=data,
            headers=headers,
            verify=settings.YANDEX_INTERNAL_CERT_PATH,
        )
        res.raise_for_status()
        logger.info('Successfully pushed vacancies to OEBS')
    except requests.RequestException:
        logger.exception('Failed to push vacancies to OEBS')


@app.task
@locked_task
def send_publication_subscriptions_digest():
    exclude = (
        'id',
        'sha1',
        'created_by',
        'shown_vacancies',
    )

    subscriptions = (
        PublicationSubscription.objects
        .select_related(
            'created_by',
            'department',
        )
        .prefetch_related(
            'skills',
            'cities',
            'abc_services',
            'professions__professional_sphere',
        )
    )
    with transaction.atomic():
        shown_vacancies = []
        for subscription in subscriptions:
            subscription_fields = model_to_dict(subscription, exclude=exclude)
            subscription_fields = {
                k: v
                for k, v in subscription_fields.items()
                if not (v is None or v == '')
            }
            vacancies = _get_new_vacancies(subscription)
            vacancies = get_suitable_vacancies(vacancies, **subscription_fields)
            if vacancies:
                shown_vacancies.extend(
                    PublicationSubscriptionVacancy(
                        publicationsubscription=subscription,
                        vacancy=vacancy,
                    )
                    for vacancy in vacancies
                )
                PublicationSubscriptionNotification(
                    instance=subscription,
                    query_params=dict_to_url_query_string(subscription_fields),
                    vacancies=vacancies,
                ).send()

        PublicationSubscriptionVacancy.objects.bulk_create(shown_vacancies)


def _get_new_vacancies(subscription: PublicationSubscription) -> QuerySet:
    yesterday = get_beginning_of_moscow_day(timezone.now()) - timedelta(1)
    shown_qs = Exists(
        PublicationSubscriptionVacancy.objects
        .filter(
            publicationsubscription=subscription,
            vacancy=OuterRef('id'),
        )
        .values('id')
    )
    queryset = (
        Vacancy.unsafe
        .annotate(shown=shown_qs)
        .filter(
            is_published=True,
            modified__gte=max(yesterday, subscription.created),
            shown=False,
        )
    )
    return queryset
