import logging

from aioredis_lock import RedisLock
from ylog.context import log_context

from intranet.trip.src.config import settings
from intranet.trip.src.enums import PTStatus, Provider, ServiceStatus, ServiceType
from intranet.trip.src.lib.aeroclub.api import aeroclub
from intranet.trip.src.lib.aeroclub.enums import (
    AuthorizationStatus,
    ServiceState,
)
from intranet.trip.src.lib.aeroclub.exceptions import (
    AeroclubBadDocument,
    AeroclubClientError,
    AeroclubWorkflowError,
)
from intranet.trip.src.models import Service
from intranet.trip.src.unit_of_work import UnitOfWork

logger = logging.getLogger(__name__)

BROKEN_STATUSES = (
    ServiceState.servicing,
    ServiceState.exchanged,
    ServiceState.expired,
    ServiceState.no_places,
    ServiceState.rejected,
    ServiceState.failed,
)


async def get_rail_bonus_card_id(provider_profile_id: int) -> int or None:
    """Если у профиля в АК есть бонусная карта типа Railway, отдаст её id"""
    profile_data = await aeroclub.get_profile(provider_profile_id)
    bonus_cards = profile_data.get('bonus_cards', [])
    for card in bonus_cards:
        if card['kind_type'] == 'Railway':
            return card['id']


class ServiceWrapper:
    """
    Сущность, объединяющая услугу в БД и соответствующую ей услугу в АК
    """
    def __init__(self, service: Service = None, aeroclub_service: dict = None):
        assert service or aeroclub_service
        self._service = service
        self.ac = aeroclub_service

    @property
    def in_db(self):
        return self._service is not None

    @property
    def in_ac(self):
        return self.ac is not None

    def __getattr__(self, item):
        return getattr(self._service, item)

    @property
    def is_processing(self):
        """
        Услуга, c которой нужно что-то делать в АК
        """
        return (
            self.in_db
            and self.in_ac
            and self.status not in (ServiceStatus.draft, ServiceStatus.deleted)
            and not self.in_process_of_cancelling
        )

    def get_update_fields(self) -> dict:
        """
        Формирует данные, которые надо отредактировать в базе,
        чтобы услуга в базе соотвествовала услуге в АК
        """

        ac_state = self.ac['service_state'] if self.in_ac else None
        is_broken = self.is_broken if self.in_db else False

        if ac_state is None:
            status = ServiceStatus.deleted
        elif ac_state == ServiceState.unknown and not self.in_db:
            status = ServiceStatus.in_progress
        elif ac_state == ServiceState.unknown and self.status == ServiceStatus.deleted:
            status = ServiceStatus.deleted
        elif ac_state == ServiceState.unknown and self.status == ServiceStatus.draft:
            status = ServiceStatus.draft
        elif ac_state == ServiceState.unknown and self.status != ServiceStatus.deleted:
            status = ServiceStatus.in_progress
        elif ac_state == ServiceState.execution:
            status = ServiceStatus.executed
        elif ac_state == ServiceState.refunding:
            status = ServiceStatus.cancelled
        elif ac_state == ServiceState.reservation:
            status = ServiceStatus.reserved
        elif ac_state in BROKEN_STATUSES:
            if (
                ac_state == ServiceState.servicing
                and self.in_db
                and self.status == ServiceStatus.reserved
            ):
                status = ServiceStatus.reserved
            elif ac_state in (ServiceState.no_places, ServiceState.expired, ServiceState.exchanged):
                status = ServiceStatus.cancelled
            elif (
                ac_state == ServiceState.servicing
                and self.in_db
                and self.in_process_of_cancelling
            ):
                status = self.status
            else:
                status = ServiceStatus.in_progress
            is_broken = True
        else:
            status = ServiceStatus.in_progress

        if not self.in_db:
            return {
                'status': status,
                'is_broken': is_broken,
            }

        fields = {}
        if (
            ac_state is None
            and self.in_process_of_cancelling
        ):
            fields['in_process_of_cancelling'] = False
        if status != self.status:
            fields['status'] = status
        if is_broken != self.is_broken:
            fields['is_broken'] = is_broken
        return fields


class BaseAeroclubServiceMixin:
    async def _notify_about_service_execution(self, service: ServiceWrapper):
        await self.uow.run_job(
            'notify_about_service_execution_task',
            trip_id=self.person_trip.trip_id,
            person_id=self.person_trip.person_id,
            provider_order_id=service.ac['order_number'],
            provider_service_id=service.ac['number'],
        )

    async def _notify_by_tracker_comment(self, template_name, context, summonees=None):
        await self.uow.run_job(
            'notify_by_tracker_comment_task',
            issue=self.person_trip.travel_tracker_issue,
            template_name=template_name,
            context=context,
            summonees=summonees,
        )

    async def _notify_about_service_broken(self):
        await self._notify_by_tracker_comment(
            template_name='service_broken.jinja2',
            context={
                'person_trip_url': self.person_trip.url,
                'chat_url': self.person_trip.chat_url,
            },
        )

    async def _notify_about_service_unavailable(self, service: ServiceWrapper):
        context = {
            'person_trip_url': self.person_trip.url,
        }
        if (
            service.ac['service_state'] == ServiceState.rejected
            and service.type == ServiceType.hotel
        ):
            template_name = 'service_rejected.jinja2'
            context['hotel_name'] = service.ac['name']['ru']
        else:
            template_name = 'service_unavailable.jinja2'

        await self._notify_by_tracker_comment(
            template_name=template_name,
            context=context,
            summonees=[self.person_trip.person.login],
        )

    async def update_service_in_db(self, service, ac_state, update_fields):
        if (
            ac_state == ServiceState.execution
            and service.status != ServiceStatus.executed
        ):
            await self._notify_about_service_execution(service)

        # Услуга переодит в один из BROKEN_STATUSES
        if not service.is_broken:
            if ac_state in (ServiceState.servicing, ServiceState.failed):
                await self._notify_about_service_broken()
            elif ac_state in BROKEN_STATUSES:
                await self._notify_about_service_unavailable(service)

        if update_fields:
            await self.uow.services.update(service.service_id, **update_fields)


class AeroclubServicesExecutor(BaseAeroclubServiceMixin):
    """
    Класс, который отвечает за оформление услуг в рамках одной персональной командировки Аэроклуба.
    Может быть запущен на любом этапе оформления.

    Порядок оформления услуг:
      - добавление путешественников в услуги
      - резервирование авиа-услуг
      - авторизация всех услуг (обычная авторизация или авторизация дополненных услуг)
      - попытка бронирования ранее выбранного места для жд-услуг
      - оформление услуг (жд- и авиа-услуги по идее должны оформляться атвоматически)
    """
    def __init__(self, uow: UnitOfWork, person_trip):
        self.uow = uow
        self.person_trip = person_trip
        assert self.person_trip.provider == Provider.aeroclub
        self.aeroclub_trip = None
        self.services = None
        self.processing_services = None

    def _build_services(self):
        services = self.person_trip.services or []
        aeroclub_services = self.aeroclub_trip['services']

        self.services = []
        self.processing_services = []

        db_mapping = {(s.provider_order_id, s.provider_service_id): s for s in services}
        ac_mapping = {(s['order_number'], s['number']): s for s in aeroclub_services}

        for key in set(db_mapping) | set(ac_mapping):
            service = db_mapping.get(key)
            aeroclub_service = ac_mapping.get(key)
            service_wrapper = ServiceWrapper(service, aeroclub_service)
            self.services.append(service_wrapper)
            if service_wrapper.is_processing:
                self.processing_services.append(service_wrapper)

    def _get_services_by_type_and_ac_action(self, service_type: ServiceType, ac_action: str):
        return [
            s for s in self.processing_services
            if s.type == service_type and ac_action in s.ac['available_actions']
        ]

    @classmethod
    async def init(cls, uow, trip_id: int, person_id: int):
        person_trip = await uow.person_trips.get_detailed_person_trip(trip_id, person_id)
        return cls(uow, person_trip)

    async def load_aeroclub_trip(self):
        self.aeroclub_trip = await aeroclub.get_trip(
            journey_id=self.person_trip.aeroclub_journey_id,
            trip_id=self.person_trip.aeroclub_trip_id,
        )
        self._build_services()

    def _get_log_context(self, service: ServiceWrapper = None):
        context = {
            'trip_id': self.person_trip.trip_id,
            'person_id': self.person_trip.person_id,
        }
        if service:
            context.update({
                'service_id': service.service_id,
                'provider_order_id': service.provider_order_id,
                'provider_service_id': service.provider_service_id,
            })
        return context

    def _error(self, message, service=None, raise_error=True):
        context = self._get_log_context(service)
        with log_context(**context):
            logger.exception('[FAILED] %s. Context: %s', message, context)
        if raise_error:
            raise AeroclubWorkflowError(message)

    def _success(self, message, service: ServiceWrapper = None):
        context = self._get_log_context(service)
        with log_context(**context):
            logger.info('[OK] %s. Context: %s', message, context)

    async def _add_traveller_to_service(self, service: ServiceWrapper):
        try:
            traveller_workload = {
                'profile_id': self.person_trip.person.provider_profile_id,
                'document_id': service.provider_document_id,
            }
            if service.provider_bonus_card_id:
                traveller_workload['bonus_card_id'] = service.provider_bonus_card_id

            await aeroclub.add_person_to_service(
                order_id=service.provider_order_id,
                service_id=service.provider_service_id,
                data=[traveller_workload],
            )
        except AeroclubBadDocument:
            old_document_id = service.provider_document_id
            await self.uow.services.update(service.service_id, provider_document_id=None)
            self._error(
                'Bad document id %d, set provider_document_id to None' % old_document_id,
                service=service,
            )
        except Exception:
            self._error('Failed adding travellers to service', service=service)

    async def add_travellers(self):
        services_to_add_traveller = [s for s in self.processing_services if not s.ac['travellers']]
        if not services_to_add_traveller:
            self._success('Not need to add travellers')
            return

        for service in services_to_add_traveller:
            await self._add_traveller_to_service(service)
        await self.load_aeroclub_trip()

    async def reserve_avia_services(self):
        await self.load_aeroclub_trip()

        services_to_reserve = self._get_services_by_type_and_ac_action(
            service_type=ServiceType.avia,
            ac_action='Reservation',
        )
        if not services_to_reserve:
            self._success('Not need to reserve avia services')
            return

        for service in services_to_reserve:
            await self._reserve_service(service)

    async def _get_trip_authorization_status(self) -> str:
        try:
            data = await aeroclub.get_services_authorization_state(
                journey_id=self.person_trip.aeroclub_journey_id,
                trip_id=self.person_trip.aeroclub_trip_id,
            )
        except Exception:
            self._error('Failed to get trip authorization state')
        else:
            return data['data']['authorization_status']

    async def _get_service_authorization_assertion(self, service: ServiceWrapper) -> bool:
        try:
            data = await aeroclub.get_additional_service_authorization_state(
                order_id=service.provider_order_id,
                service_id=service.provider_service_id,
            )
        except Exception:
            self._error('Failed to get additional service authorization state')
        else:
            return data['data']['authorization_assertion']

    async def _send_authorization_request(self) -> str:
        try:
            await aeroclub.send_services_authorization_request(
                journey_id=self.person_trip.aeroclub_journey_id,
                trip_id=self.person_trip.aeroclub_trip_id,
            )
        except Exception:
            self._error('Failed to send services authorization request')
        else:
            return AuthorizationStatus.in_progress

    async def _send_additional_service_authorization_request(self, service: ServiceWrapper) -> str:
        try:
            await aeroclub.send_additional_service_authorization_request(
                order_id=service.provider_order_id,
                service_id=service.provider_service_id,
            )
        except Exception:
            self._error('Failed to send additional service authorization request')
        else:
            return AuthorizationStatus.in_progress

    async def _complete_authorization(self):
        await self.load_aeroclub_trip()
        token = self.aeroclub_trip['authorization_token']
        if not token:
            self._error('There is no authorization token')
        try:
            await aeroclub.authorize_trip(
                token=token,
                profile_id=self.person_trip.person.provider_profile_id,
            )
        except Exception:
            self._error('Failed to authorize aeroclub services')

    async def _notify_need_to_reserve_rail(self):
        services_to_notify = [
            service for service in self.processing_services
            if service.type == ServiceType.rail and service.status == ServiceStatus.in_progress
        ]
        if not services_to_notify:
            return

        context = {
            'person_trip_url': self.person_trip.url,
            'seat_was_selected': any(
                service.seat_number is not None
                for service in services_to_notify
            ),
        }
        await self.uow.run_job(
            'send_message_to_chat',
            chat_id=self.person_trip.chat_id,
            template_name='need_to_reserve_rail.jinja2',
            context=context,
        )
        await self.uow.run_job(
            'notify_by_tracker_comment_task',
            issue=self.person_trip.travel_tracker_issue,
            template_name='need_to_reserve_rail.jinja2',
            context=context,
            summonees=[self.person_trip.person.login],
        )

    async def _authorize_services(self):
        status = await self._get_trip_authorization_status()
        if status == AuthorizationStatus.authorized:
            self._success('Services were already authorized')
            return

        for service in self.processing_services:
            await self._add_service_custom_properties(service, is_additional=False)

        if status == AuthorizationStatus.pending:
            status = await self._send_authorization_request()
        if status == AuthorizationStatus.in_progress:
            await self._complete_authorization()

        service_ids = [
            s.service_id for s in self.services
            if s.in_db and s.status == ServiceStatus.in_progress
        ]
        await self.uow.services.bulk_update(
            service_ids=service_ids,
            is_authorized=True,
        )
        self._success('Services were successfully authorized')

    async def _add_service_custom_properties(self, service: ServiceWrapper, is_additional: bool):
        try:
            await aeroclub.get_service_custom_properties(
                journey_id=self.person_trip.aeroclub_journey_id,
                trip_id=self.person_trip.aeroclub_trip_id,
                order_id=service.provider_order_id,
                service_id=service.provider_service_id,
                is_additional=is_additional,
            )
            await aeroclub.add_service_custom_properties(
                journey_id=self.person_trip.aeroclub_journey_id,
                trip_id=self.person_trip.aeroclub_trip_id,
                order_id=service.provider_order_id,
                service_id=service.provider_service_id,
                traveller_id=service.ac['travellers'][0]['id'],
                is_additional=is_additional,
            )
        except Exception:
            self._error('Failed to add custom properties to service', service=service)

    async def _authorize_additional_services(self):
        services_to_authorize = [s for s in self.processing_services if not s.is_authorized]
        if not services_to_authorize:
            self._success('There are no additional services to authorize')
            return

        for service in services_to_authorize:
            await self._add_service_custom_properties(service, is_additional=True)
            assertion = await self._get_service_authorization_assertion(service)
            if assertion is False:
                self._success('Additional service was already authorized')
                continue
            await self._send_additional_service_authorization_request(service)
            await self._complete_authorization()
            await self.uow.services.update(service_id=service.service_id, is_authorized=True)
        self._success('Additional services were successfully authorized')

    async def authorize_services(self):
        """
        Есть два вида авторизации услуг:
          - обычная авторизация
          - авторизация дополненной услуги

        Чтобы авторизовать услуги, нужно:
          - получить статус авторизации -> authorization_status == 'Pending'
          - отправить запрос на авторизацию -> authorization_status == 'InProgress'
          - авторизовать -> authorization_status == 'Authorized'

        Обычная авторизация работает только при первой авторизации услуг в командировке.
        Авторизация происходит по принципу все или ничего.

        В случае добавленных услуг, их необходимо авторизовывать поштучно.
        """
        if not settings.SKIP_TRIP_APPROVE and not self.person_trip.is_approved:
            raise AeroclubWorkflowError('Person trip is not approved')

        # Авторизуем первый раз, если у нас нет ни одной авторизованной услуги
        is_first_authorization = all(
            not s.is_authorized
            for s in self.services
            if s.in_db and s.in_ac
        )

        # TODO: подумать еще раз, не получится ли тут ситуации, когда мы авторизовываем второй раз,
        # когда первая авторизация еще не прошла
        if is_first_authorization:
            await self._authorize_services()
        else:
            await self._authorize_additional_services()

    async def _get_rail_reservation_parameters(self, service: ServiceWrapper):
        rail_parameters = {
            'seats_from': service.seat_number,
            'seats_to': service.seat_number,
            'location_type': 2,  # RailwayStation
            'remote_checkin': True,
            'traveller_parameters': [{
                'place_count': 1,
                'traveller_id': service.ac['travellers'][0]['id'],
            }]
        }
        bonus_card_id = await get_rail_bonus_card_id(
            self.person_trip.person.provider_profile_id,
        )
        if bonus_card_id:
            traveller_parameters = rail_parameters['traveller_parameters'][0]
            traveller_parameters['bonus_card_id'] = bonus_card_id
        return rail_parameters

    async def _reserve_service(self, service: ServiceWrapper):
        try:
            rail_parameters = None
            if service.type == ServiceType.rail:
                rail_parameters = await self._get_rail_reservation_parameters(service)
            await aeroclub.reserve_service(
                order_id=service.provider_order_id,
                service_id=service.provider_service_id,
                auto_execution=True,
                rail_parameters=rail_parameters,
            )
            await self.uow.services.update(service.service_id, status=ServiceStatus.reserved)
        except Exception:
            self._error('Failed to reserve service', service=service)
        else:
            self._success('Service was reserved', service=service)

    async def _is_selected_seat_free(self, service: ServiceWrapper):
        free_seats_data = await aeroclub.get_free_seats(
            order_id=service.provider_order_id,
            service_id=service.provider_service_id,
        )
        free_seats = [
            seat['number']
            for seat in free_seats_data['data']['carriage_details'][0]['places']
        ]
        return service.seat_number in free_seats

    async def reserve_rail_services(self):
        await self.load_aeroclub_trip()

        services_to_reserve = self._get_services_by_type_and_ac_action(
            service_type=ServiceType.rail,
            ac_action='Reservation',
        )
        if not services_to_reserve:
            self._success('Not need to reserve rail services')
            return

        for service in services_to_reserve:
            if service.seat_number is None:
                continue

            try:
                is_seat_free = await self._is_selected_seat_free(service)
            except (IndexError, KeyError):
                self._error('Cannot get free seats', service=service, raise_error=False)
                await self._notify_about_service_unavailable(service)
                await self.uow.services.update(
                    service_id=service.service_id,
                    seat_number=None,
                    is_broken=True,
                )
                continue

            if is_seat_free:
                await self._reserve_service(service)
            else:
                await self._notify_need_to_reserve_rail()
                await self.uow.services.update(service.service_id, seat_number=None)

    async def execute_services(self):
        """
        Кажется тут нужно только отели явно экзэкьютить,
        остальное автоматически произойдет после бронирования.
        :return:
        """
        await self.load_aeroclub_trip()

        services_to_execute = [
            s for s in self.processing_services if 'Execution' in s.ac['available_actions']
        ]
        if not services_to_execute:
            self._success('Not need to execute services')
            return

        for service in services_to_execute:
            try:
                await aeroclub.execute_service(
                    order_id=service.provider_order_id,
                    service_id=service.provider_service_id,
                )
            except Exception:
                self._error('Failed to execute service', service=service)
            else:
                self._success('Hotel was successfully executed', service=service)

    def check_person_trip(self):
        if self.person_trip.status not in [PTStatus.executing, PTStatus.executed]:
            raise AeroclubWorkflowError('Wrong person trip status')

    async def update_in_db(self, dry_run: bool = False):
        await self.load_aeroclub_trip()

        state = {}
        statuses = set()

        for service in self.services:
            ac_state = service.ac['service_state'] if service.in_ac else None
            update_fields = service.get_update_fields()

            if service.in_db:
                statuses.add(update_fields.get('status') or service.status)

                if not dry_run:
                    await self.update_service_in_db(service, ac_state, update_fields)

                if 'status' in update_fields:
                    self._success(
                        message='Service %d status updated from %s to %s' % (
                            service.service_id,
                            service.status,
                            update_fields['status'],
                        ),
                        service=service,
                    )
                state[service.service_id] = ac_state

            # Если услуги нет в базе, то ее надо создать
            else:
                service_id = None
                if not dry_run:
                    service_id = await self.uow.services.create(
                        trip_id=self.person_trip.trip_id,
                        person_id=self.person_trip.person_id,
                        type=service.ac['type'].lower(),
                        provider_order_id=service.ac['order_number'],
                        provider_service_id=service.ac['number'],
                        is_from_provider=True,
                        **update_fields,
                    )
                    if ac_state == ServiceState.execution:
                        await self._notify_about_service_execution(service)

                self._success(
                    message='Service %s was created from aeroclub in status %s' % (
                        service_id,
                        update_fields['status'],
                    ),
                )
                state[service_id] = ac_state
                statuses.add(update_fields['status'])

        # Если больше не осталось услуг в статусе in_progress
        # при этом есть услуги в статусе executed
        is_all_executed = (
            ServiceStatus.in_progress not in statuses
            and ServiceStatus.reserved not in statuses
            and ServiceStatus.executed in statuses
        )
        if is_all_executed and self.person_trip.status != PTStatus.executed and not dry_run:
            await self.uow.person_trips.update(
                trip_id=self.person_trip.trip_id,
                person_id=self.person_trip.person_id,
                status=PTStatus.executed,
            )
            await self.uow.run_job(
                'notify_by_tracker_comment_task',
                issue=self.person_trip.travel_tracker_issue,
                template_name='trip_services_executed.jinja2',
                context={
                    'person_trip_url': self.person_trip.url,
                },
            )
        return state

    async def check_and_run(self) -> bool:
        try:
            self.check_person_trip()
        except AeroclubWorkflowError as exc:
            self._error(message=str(exc), service=None, raise_error=False)
            return False
        return await self.run()

    async def run(self) -> bool:
        key = f'lock:{self.person_trip.aeroclub_journey_id}_{self.person_trip.aeroclub_trip_id}'
        async with RedisLock(
            pool_or_conn=self.uow._redis,
            key=key,
            wait_timeout=settings.REDIS_LOCK_WAIT_TIMEOUT,
            timeout=settings.REDIS_LOCK_TIMEOUT,
        ):
            await self.load_aeroclub_trip()
            if self.aeroclub_trip['authorization_status'] != AuthorizationStatus.authorized:
                self._error('Person trip is not authorized in Aeroclub')

            is_ok = True
            try:
                await self.add_travellers()
                await self.authorize_services()
                await self.reserve_avia_services()  # TODO BTRIP-2605
                await self.reserve_rail_services()
                await self.execute_services()
            except AeroclubWorkflowError:
                is_ok = False

            state = await self.update_in_db()
            self._success(
                'Aeroclub services executor successfully finished. Final state: %s' % state
            )
            return is_ok


class AeroclubServiceProcessor(BaseAeroclubServiceMixin):
    """
    Класс, отвечающий за процесс обновления данных услуги из АК

    Алгоритм действий:
      - получаем услугу из БД по аэроклубным id
      - загружаем данные услуги из АК
      - сравниваем и проверяем необходимость обновления состояния услуги
        и признаков is_broken, in_process_of_cancelling
      - если услуга есть в БД - обновляем данные, если услуги нет - создаем ее
      - шлем необходимые отбивки
      - проверяем, все ли услуги в командировке оформлены и обновляем ее состояние при необходимости

    Во все логи этого класса добавлены такие поля контекста:
      - context.context.trip_id
      - context.context.person_id
      - context.context.service_id
      - context.context.provider_order_id
      - context.context.provider_service_id
    """

    def __init__(self, uow: UnitOfWork, service: Service):
        self.uow = uow
        self.service = service
        self.service_wrapper = None
        self.person_trip = None

    @classmethod
    async def init(cls, uow, provider_order_id, provider_service_id):
        service = await uow.services.get_service_by_aeroclub_id(
            provider_order_id=provider_order_id,
            provider_service_id=provider_service_id,
        )
        return cls(uow, service)

    async def reload_service_from_ac(self):
        try:
            ac_service = await aeroclub.get_service(
                order_id=self.service.provider_order_id,
                service_id=self.service.provider_service_id,
            )
        except AeroclubClientError as e:
            if e.status_code != 404:
                raise
            ac_service = None
        self.service_wrapper = ServiceWrapper(self.service, ac_service)

    async def reload_service_from_db(self):
        self.service = await self.uow.services.get_detailed_service(self.service.service_id)
        self.service_wrapper = ServiceWrapper(self.service, self.service_wrapper.ac)

    async def reload_person_trip(self):
        self.person_trip = await self.uow.person_trips.get_detailed_person_trip(
            trip_id=self.service.trip_id,
            person_id=self.service.person_id,
        )

    async def update_person_trip_if_needed(self):
        statuses = {service.status for service in self.person_trip.services}
        all_executed = (
            ServiceStatus.in_progress not in statuses
            and ServiceStatus.reserved not in statuses
            and ServiceStatus.executed in statuses
        )
        new_status = PTStatus.executed if all_executed else PTStatus.executing
        if self.person_trip.status != new_status:
            await self.uow.person_trips.update(
                trip_id=self.person_trip.trip_id,
                person_id=self.person_trip.person_id,
                status=new_status,
            )
            logger.info(
                'Updated person trip (trip_id=%s, person_id=%s) from status %s to status %s',
                self.person_trip.trip_id,
                self.person_trip.person_id,
                self.person_trip.status,
                new_status,
            )

    def get_log_context(self):
        return {
            'trip_id': self.person_trip.trip_id,
            'person_id': self.person_trip.person_id,
            'service_id': self.service.service_id,
            'provider_order_id': self.service.provider_order_id,
            'provider_service_id': self.service.provider_service_id,
        }

    async def run(self):
        await self.reload_person_trip()

        with log_context(**self.get_log_context()):
            logger.info('Start AeroclubServiceProcessor for service %s', self.service.service_id)
            await self.reload_service_from_ac()

            ac_state = (
                self.service_wrapper.ac['service_state'] if self.service_wrapper.in_ac else None
            )
            update_fields = self.service_wrapper.get_update_fields()
            if self.service_wrapper.in_db:
                await self.update_service_in_db(self.service_wrapper, ac_state, update_fields)
                logger.info(
                    'Service %s updated from status %s to status %s',
                    self.service.service_id,
                    self.service.status,
                    update_fields.get('status', self.service.status),
                )
            else:
                service_id = await self.uow.services.create(
                    trip_id=self.person_trip.trip_id,
                    person_id=self.person_trip.person_id,
                    type=self.service.ac['type'].lower(),
                    provider_order_id=self.service.ac['order_number'],
                    provider_service_id=self.service.ac['number'],
                    is_from_provider=True,
                    **update_fields,
                )
                logger.info('Created service %s', service_id)
                if ac_state == ServiceState.execution:
                    await self._notify_about_service_execution(self.service_wrapper)

            await self.reload_person_trip()
            await self.update_person_trip_if_needed()
            logger.info('Finish AeroclubServiceProcessor for service %s', self.service.service_id)
