import logging

from intranet.trip.lib.arq_utils.decorators import job, retry_on_exception

from intranet.trip.src.unit_of_work import UnitOfWork
from intranet.trip.src.cache import Cache
from intranet.trip.src.config import settings
from intranet.trip.src.enums import Provider
from intranet.trip.src.logic.aeroclub.services import AeroclubServicesExecutor
from intranet.trip.src.lib.aeroclub.api import aeroclub, aeroclub_fs_write
from intranet.trip.src.lib.aeroclub.notifications.handlers import handler_registry
from intranet.trip.src.lib.messenger.notifications import notify_by_messenger
from intranet.trip.src.lib.messenger.sync import MessengerSync
from intranet.trip.src.lib.staff.sync import StaffTripPush
from intranet.trip.src.lib.hub.sync import sync_hub_profiles
from intranet.trip.src.lib.tracker.notifications import notify_by_tracker_comment
from intranet.trip.src.logic.aeroclub.trips import AeroclubTripController
from intranet.trip.src.logic.tracker import (
    update_conf_role_in_tracker_issue,
    update_speaker_details_in_tracker_issue,
    update_bribe_warning_in_tracker_issue,
    send_attachments_to_tracker,
    notify_about_service_execution,
    notify_offline_person_trip_created,
)
from intranet.trip.src.logic.triggers.executor import TriggerExecutor


logger = logging.getLogger(__name__)


@job
async def execute_person_trip_services(ctx, trip_id: int, person_id: int) -> None:
    logger.info(
        'Start execute_person_trip_services, trip_id: %s, person_id: %s',
        trip_id, person_id,
    )
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        ctl = await AeroclubServicesExecutor.init(
            uow=uow,
            trip_id=trip_id,
            person_id=person_id,
        )
        await ctl.check_and_run()


@job
async def create_aeroclub_trips_task(ctx, trip_id: int, person_ids: list[int]):
    if not settings.ENABLE_AEROCLUB_SYNC:
        logger.info('Skip create_aeroclub_trips_task because AEROCLUB_SYNC doesn\'t enable')
        return

    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        ctl = AeroclubTripController(uow=uow)
        await ctl.create_aeroclub_trips(trip_id, person_ids)


@job
async def create_profiles_to_ihub_and_aeroclub_trips_task(ctx, trip_id: int, person_ids: list[int]):
    if not settings.ENABLE_AEROCLUB_SYNC:
        logger.info('Skip create_profiles_to_ihub_and_aeroclub_trips_task '
                    'because AEROCLUB_SYNC doesn\'t enable')
        return

    logger.info('Start create_profiles_to_ihub_and_aeroclub_trips_task task')
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        await sync_hub_profiles(uow=uow, person_ids=person_ids)
        await uow.run_job(
            'create_aeroclub_trips_task',
            trip_id=trip_id,
            person_ids=person_ids,
            unique=False,
        )
    logger.info('Finish create_profiles_to_ihub_and_aeroclub_trips_task.')


@job
async def create_trip_chats_task(ctx, trip_id: int, person_ids: list[int]):
    if not (settings.IS_YA_TEAM or settings.ENABLE_CHATS):
        # TODO: Пока нет интеграции с внешним Мессенджером, чат вообще не создаем
        # https://st.yandex-team.ru/BTRIP-2252
        return

    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        cache = Cache(ctx['redis'])
        chat_sync = await MessengerSync.init(uow, cache)
        await chat_sync.create_chats(trip_id, person_ids)


@job
async def create_or_update_staff_trip_task(ctx, trip_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        staff_push = await StaffTripPush.init(uow)
        await staff_push.create_or_update_staff_trip(trip_id)


@job
@retry_on_exception()
async def notify_offline_person_trip_created_task(ctx, trip_id: int, person_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        await notify_offline_person_trip_created(uow, trip_id, person_id)


@job
async def update_trip_custom_properties(ctx, trip_id: int, person_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        ctl = await AeroclubTripController.init(uow=uow)
        await ctl.add_custom_properties_for_person_trip(trip_id, person_id)


@job
@retry_on_exception()
async def send_message_to_chat(
    ctx,
    chat_id: str,
    text: str = None,
    template_name: str = None,
    context: dict = None,
) -> None:
    if not (settings.IS_YA_TEAM or settings.ENABLE_CHATS):
        # TODO: Пока нет интеграции с внешним Мессенджером, чат вообще не создаем
        # https://st.yandex-team.ru/BTRIP-2252
        return

    await notify_by_messenger(
        chat_id=chat_id,
        text=text,
        template_name=template_name,
        context=context,
    )


@job
@retry_on_exception()
async def notify_by_tracker_comment_task(
    ctx,
    issue: str,
    template_name: str,
    context: dict,
    summonees: list[str] = None,
    maillist_summonees: list[str] = None,
    attachment_ids: list[int] = None,
) -> None:
    if not settings.IS_YA_TEAM:
        # TODO: Пока нет нормальной абстракции над нотификациями,
        # будем просто скипать отправку сообщений в случае b2b-инсталляции
        # https://st.yandex-team.ru/BTRIP-2285
        return

    await notify_by_tracker_comment(
        issue=issue,
        template_name=template_name,
        context=context,
        summonees=summonees,
        maillist_summonees=maillist_summonees,
        attachment_ids=attachment_ids,
    )


@job
@retry_on_exception(retry_defer_callable=lambda job_try: 10 * 2 ** (job_try - 1))  # 10, 20, 40 sec
async def notify_about_service_execution_task(
    ctx,
    trip_id: int,
    person_id: int,
    provider_order_id: int,
    provider_service_id: int,
) -> None:
    if not settings.IS_YA_TEAM:
        # TODO: Пока нет нормальной абстракции над нотификациями,
        # будем просто скипать отправку сообщений в случае b2b-инсталляции
        # https://st.yandex-team.ru/BTRIP-2285
        return

    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        await notify_about_service_execution(
            uow=uow,
            trip_id=trip_id,
            person_id=person_id,
            provider_order_id=provider_order_id,
            provider_service_id=provider_service_id,
            force=ctx['is_last_try'],
        )


@job
@retry_on_exception()
async def send_attachments_to_tracker_task(
    ctx,
    trip_id: int,
    person_id: int,
    attachments: list[dict],
) -> None:
    if not settings.IS_YA_TEAM:
        # TODO: Пока нет нормальной абстракции над нотификациями,
        # будем просто скипать отправку сообщений в случае b2b-инсталляции
        # https://st.yandex-team.ru/BTRIP-2285
        return

    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        issue = await uow.person_trips.get_travel_tracker_issue(
            trip_id=trip_id,
            person_id=person_id,
        )
        if issue is None:
            raise Exception("Missing tracker_issue")
        text = 'Агент Аэроклуба прислал следующие файлы'
        await send_attachments_to_tracker(issue, text, attachments)


@job
@retry_on_exception()
async def update_conf_role_in_tracker_issue_task(ctx, trip_id: int, person_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        await update_conf_role_in_tracker_issue(uow=uow, trip_id=trip_id, person_id=person_id)


@job
@retry_on_exception()
async def update_speaker_details_in_tracker_issue_task(ctx, trip_id: int, person_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        await update_speaker_details_in_tracker_issue(uow=uow, trip_id=trip_id, person_id=person_id)


@job
@retry_on_exception()
async def update_bribe_warning_in_tracker_issue_task(ctx, trip_id: int, person_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        await update_bribe_warning_in_tracker_issue(uow=uow, trip_id=trip_id, person_id=person_id)


@job
@retry_on_exception()
async def send_message_to_aeroclub_task(ctx, chat_id: str, text: str, profile_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        person_trip = await uow.person_trips.get_person_trip_by_chat_id(chat_id=chat_id)

    # TODO: по хорошему такую проверку надо делать не в самой таске, а перед ее вызовом.
    # Но тогда придется рефакторить класс BaseMessageProcessor, что хочется делать уже тогда,
    # когда доберемся до отправки сообщений в Авиацентр
    if person_trip.provider != Provider.aeroclub:
        logger.info(
            'Provider of person trip is not Aeroclub. Skip sending message. chat_id: %s',
            chat_id,
        )
        return

    if (
        not person_trip.aeroclub_journey_id
        or not person_trip.aeroclub_trip_id
        or not person_trip.person.provider_profile_id
    ):
        raise ValueError(f'Missing aeroclub ids, for chat_id: {chat_id}')

    await aeroclub.send_message(
        journey_id=person_trip.aeroclub_journey_id,
        trip_id=person_trip.aeroclub_trip_id,
        message=text,
        profile_id=profile_id,
    )
    logger.info(
        'Send message to aeroclub: trip_id=%s, person_id=%s',
        person_trip.trip_id, person_trip.person_id,
    )


@job
@retry_on_exception()
async def send_file_to_aeroclub_task(
        ctx,
        chat_id: str,
        file_name: str,
        file_data: bytes,
        profile_id: int,
):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        person_trip = await uow.person_trips.get_person_trip_by_chat_id(chat_id=chat_id)

    if person_trip.provider != Provider.aeroclub:
        logger.info(
            'Provider of person trip is not Aeroclub. Skip sending file. chat_id: %s',
            chat_id,
        )
        return

    if (
        not person_trip.aeroclub_journey_id
        or not person_trip.aeroclub_trip_id
        or not person_trip.person.provider_profile_id
    ):
        raise ValueError(f'Missing aeroclub ids, for chat_id: {chat_id}')

    await aeroclub_fs_write.send_file(
        journey_id=person_trip.aeroclub_journey_id,
        trip_id=person_trip.aeroclub_trip_id,
        content_type='application/octet-stream',
        file_name=file_name,
        file_data=file_data,
        profile_id=profile_id,
    )
    logger.info(
        'Send message to aeroclub: trip_id=%s, person_id=%s',
        person_trip.trip_id, person_trip.person_id,
    )


@job
async def run_person_trip_triggers(ctx, trip_id: int, person_id: int):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        person_trip = await uow.person_trips.get_detailed_person_trip(trip_id, person_id)
        executor = TriggerExecutor(uow, person_trip)
        await executor.run()


@job
@retry_on_exception()
async def process_aeroclub_event_task(ctx, event: dict):
    async with ctx['db'].acquire() as conn:
        uow = UnitOfWork(conn=conn, redis=ctx['redis'])
        handler_class = handler_registry.get(event['code'])
        if handler_class is None:
            logger.info(
                'Event %s for journey_id=%s, trip_id=%s - corresponding handler was not found',
                event['code'],
                event['aeroclub_journey_id'],
                event['aeroclub_trip_id'],
            )
            return
        logger.info(
            'Processing event %s for journey_id=%s, trip_id=%s with handler %s',
            event['code'],
            event['aeroclub_journey_id'],
            event['aeroclub_trip_id'],
            handler_class.__name__,
        )
        handler = handler_class(uow)
        await handler.run_handler(event)
