from datetime import timedelta
import logging
from typing import Optional

from pydantic import ValidationError

from intranet.trip.src.api.auth import (
    has_person_partial_perm,
    has_person_perm,
    has_person_trip_read_perm,
    is_holding_coordinator,
    is_need_person_trip_verification,
    LazyRolesMap,
)
from intranet.trip.src.api.schemas import PersonTripCancel, PersonTripUpdate, ServiceCreate
from intranet.trip.src.api.schemas.validators import db_validate_by_ids
from intranet.trip.src.cache import Cache
from intranet.trip.src.config import settings
from intranet.trip.src.db.gateways.base import RecordNotFound
from intranet.trip.src.enums import (
    OverpriceWorkflow,
    PTStatus,
    Provider,
    ServiceStatus,
    ServiceType,
    TripStatus,
)
from intranet.trip.src.exceptions import PermissionDenied, WorkflowError
from intranet.trip.src.lib.aeroclub.api import aeroclub
from intranet.trip.src.lib.aeroclub.models.profile import ACBonusCard
from intranet.trip.src.lib.messenger.api import bot_messenger
from intranet.trip.src.lib.messenger.gateway import MessengerGateway
from intranet.trip.src.logic.aeroclub.services import AeroclubServicesExecutor
from intranet.trip.src.logic.base import Action
from intranet.trip.src.logic.trips import get_holding_id
from intranet.trip.src.models import PersonTrip, PersonTripActions, User
from intranet.trip.src.unit_of_work import UnitOfWork

logger = logging.getLogger(__name__)


async def get_trip_holding_id(uow: UnitOfWork, trip_id: int) -> Optional[int]:
    """
    Get holding_ids from person_trips (person, company) for trip and check they are the same
    """
    holding_ids = await uow.trips.get_holding_ids(trip_id=trip_id)
    return await get_holding_id(holding_ids)


class PersonTripAction(Action):

    def __init__(
            self,
            uow: UnitOfWork,
            user: User,
            person_trip: PersonTrip,
            roles_map: LazyRolesMap = None,
            cache: Cache = None,
    ):
        self.uow = uow
        self.user = user
        self.person_trip = person_trip
        self.trip_author_id = None
        if person_trip is not None:
            self.trip_author_id = person_trip.trip_author_id
        if roles_map:
            self.roles_map = roles_map
        if cache:
            self.cache = cache
        elif self.person_trip is not None:
            self.roles_map = LazyRolesMap(uow, person_ids=[self.person_trip.person_id])
        else:
            self.roles_map = None

    @classmethod
    async def init(
            cls,
            uow: UnitOfWork,
            user: User,
            trip_id: int,
            person_id: int,
            roles_map=None,
            cache=None,
    ):
        person_trip = await cls.get_person_trip(uow, trip_id, person_id)
        self = cls(uow, user, person_trip, roles_map, cache)
        if not person_trip:
            self.trip_author_id = await uow.trips.get_author_id(trip_id)
            self.trip_id = trip_id
            self.person_id = person_id
        return self

    @classmethod
    async def get_person_trip(cls, uow: UnitOfWork, trip_id: int, person_id: int) -> PersonTrip:
        return await uow.person_trips.get_detailed_person_trip(trip_id, person_id)

    @staticmethod
    def check_is_enabled_service_workflow() -> None:
        if not settings.ENABLE_SERVICE_WORKFLOW:
            raise WorkflowError('Service workflow disabled')

    def check_is_offline(self):
        if self.person_trip.is_offline:
            raise WorkflowError('Services are not available in offline trips')

    def check_aeroclub_ids(self):
        if not self.person_trip.aeroclub_journey_id or not self.person_trip.aeroclub_trip_id:
            raise WorkflowError('Missing aeroclub ids')

    def check_person_trip_authorized(self):
        if not self.person_trip.is_authorized:
            raise WorkflowError('Person trip is not authorized in Aeroclub')

    def check_person_date_of_birth(self):
        if self.person_trip.person.date_of_birth is None:
            raise WorkflowError('Birth date is empty')

    async def check_has_person_trip_read_permission(self):
        if not await has_person_trip_read_perm(
            self.user, self.person_trip, self.roles_map,
        ):
            raise PermissionDenied(
                log_message=(
                    f'{self.__class__.__name__}: User has not person trip read permission for '
                    f'person_trip ({self.person_trip.trip_id=}, {self.person_trip.person_id=})'
                ),
            )


class PersonTripCreateUpdateAction(PersonTripAction):

    action_name = 'edit'
    IS_REVIVAL = False

    def __init__(
            self,
            uow: UnitOfWork,
            user: User,
            person_trip: PersonTrip = None,
            roles_map: LazyRolesMap = None,
            cache: Cache = None
    ):
        super().__init__(uow, user, person_trip, roles_map, cache)
        if person_trip:
            self.trip_id = person_trip.trip_id
            self.person_id = person_trip.person_id

    @classmethod
    async def get_person_trip(
            cls,
            uow: UnitOfWork,
            trip_id: int,
            person_id: int,
    ) -> Optional[PersonTrip]:
        try:
            person_trip = await uow.person_trips.get_detailed_person_trip(trip_id, person_id)
        except RecordNotFound:
            return None
        return person_trip

    async def check_permissions(self) -> None:
        if self.person_trip is not None and self.person_trip.status in [
            PTStatus.cancelled,
            PTStatus.closed,
        ]:
            raise WorkflowError('Wrong person trip status')

        author = await self.uow.persons.get_person(self.trip_author_id)
        if has_person_perm(self.user, author):
            return

        if not self.person_trip:
            raise PermissionDenied(log_message=f'{self.__class__.__name__}: Empty person trip')

        if await has_person_trip_read_perm(self.user, self.person_trip, self.roles_map):
            return

        raise PermissionDenied(
            log_message=f'{self.__class__.__name__}: User has not person trip permission',
        )

    async def validate(self, pt_update: PersonTripUpdate) -> None:
        errors = []
        errors.append(await db_validate_by_ids(self.uow.purposes, pt_update, 'purposes'))
        errors.append(await db_validate_by_ids(self.uow.documents, pt_update, 'documents'))
        errors = [e for e in errors if e]
        if errors:
            raise ValidationError(errors, model=PersonTripUpdate)

    def _get_status(self, person_trip: Optional[PersonTrip]):
        if person_trip is None:
            return PTStatus.new
        # редактирование отмененной командировки означает пересоздание командировки
        if person_trip.status == PTStatus.cancelled:
            return PTStatus.new
        return person_trip.status

    async def execute(
            self,
            person_trip_update: PersonTripUpdate,
            partial_update: bool = False,
    ) -> int:
        await self.validate(person_trip_update)

        trip_id = self.trip_id
        person_id = self.person_id

        person = await self.uow.persons.get_person(person_id)
        roles_map = LazyRolesMap(self.uow, person_ids=[person_id])
        if not await has_person_partial_perm(self.user, person, roles_map):
            raise PermissionDenied(
                log_message=f'User has not partial permission for person {person_id}',
            )

        holding_id = await get_trip_holding_id(uow=self.uow, trip_id=trip_id)
        if holding_id and holding_id != person.company.holding_id:
            raise WorkflowError('Bad holding_id for trip')

        is_creation = self.person_trip is None or self.IS_REVIVAL

        # Создание и частичное редактирование одновременно не имеют смысла
        assert not (partial_update and is_creation)

        old_data = {
            'conf_role': self.person_trip.conf_details.role,
            'presentation_topic': self.person_trip.conf_details.presentation_topic,
            'is_hr_approved': self.person_trip.conf_details.is_hr_approved,
            'is_paid_by_host': self.person_trip.conf_details.is_paid_by_host,
        } if self.person_trip and self.person_trip.conf_details else {}

        update_data = person_trip_update.dict(exclude_unset=partial_update)
        if not update_data:
            return trip_id

        purpose_ids = update_data.pop('purposes', None)
        document_ids = update_data.pop('documents', None)
        conf_details = update_data.pop('conf_details', None)
        travel_details = update_data.pop('travel_details', None)
        route = update_data.pop('route', None)

        provider = person.company.provider if is_creation else self.person_trip.provider
        trip = await self.uow.trips.get_detailed_trip(trip_id)
        if is_creation:
            if not update_data['city_from']:
                update_data['city_from'] = trip.city_from
            if not update_data['country_from']:
                update_data['country_from'] = trip.country_from
            update_data['provider_city_from_id'] = trip.provider_city_from_id
            update_data['is_offline'] = person.is_offline_trip
            update_data['provider'] = provider
            if route is None and trip.route:
                route = [{
                    'city': item.city.name.ru,
                    'country': item.country.name.ru,
                    'date': item.date,
                    'provider_city_id': item.provider_city_id,
                    'need_hotel': item.need_hotel,
                    'need_transfer': item.need_transfer,
                    'is_start_city': index == 0,
                    'trip_id': trip_id,
                    'person_id': person_id,
                } for index, item in enumerate(trip.route)]

        # TODO: Тут есть разница прокинули пустой purposes или не прокинули вообще
        # По идее в случае пустого списка мы должны явно purposes обнулить
        # Но сейчас фронт прокидывает пустой список, а мы не хотим обнулять
        # Надо, чтобы фронт это поправил тут https://st.yandex-team.ru/BTRIP-2311
        # А пока будем явно наследовать цели из trip, если список purposes пустой
        if not partial_update and not purpose_ids and trip.purposes:
            purpose_ids = [purpose.purpose_id for purpose in trip.purposes]

        if route and not is_creation:
            self._assert_route_is_not_changed(route)

        if conf_details is not None and trip.conf_details is not None:
            conf_details['is_another_city'] = trip.conf_details.is_another_city

        update_data['status'] = self._get_status(self.person_trip)

        if update_data.get('gap_date_from') is None:
            if route:
                update_data['gap_date_from'] = min(point['date'] for point in route)
            elif not partial_update:
                update_data['gap_date_from'] = trip.date_from
        if update_data.get('gap_date_to') is None:
            if route:
                update_data['gap_date_to'] = max(point['date'] for point in route)
            elif not partial_update:
                update_data['gap_date_to'] = trip.date_to

        date_from = update_data.get('gap_date_from') or self.person_trip.gap_date_from
        date_to = update_data.get('gap_date_to') or self.person_trip.gap_date_to

        if date_from is not None and date_to is not None:
            if travel_details is None:
                travel_details = {
                    'city_id': 1,
                    'is_created_on_provider': False,
                }
            travel_details['taxi_date_from'] = date_from - timedelta(days=1)
            travel_details['taxi_date_to'] = date_to + timedelta(days=1)

        async with self.uow:
            if is_creation:
                if self.IS_REVIVAL:
                    await self.uow.person_trips.delete(trip_id, person_id)
                trip_id = await self.uow.person_trips.create(trip_id, person_id, **update_data)
                if route:
                    await self.uow.person_trips.create_route_points(route)
            else:
                if update_data:
                    await self.uow.person_trips.update(trip_id, person_id, **update_data)
                if route:
                    await self._update_route_dates(route)
            # Тут по идее при PUT нужно обнулять
            if conf_details:
                await self.uow.person_trips.update_conf_details(
                    trip_id=trip_id,
                    person_id=person_id,
                    **conf_details,
                )
            if travel_details:
                await self.uow.person_trips.update_travel_details(
                    trip_id, person_id, **travel_details,
                )
            if purpose_ids is not None:
                await self.uow.person_trips.update_purposes(trip_id, person_id, purpose_ids)
            if document_ids is not None:
                await self.uow.person_trips.update_person_trip_documents(
                    trip_id, person_id, document_ids,
                )

            self._add_related_jobs(is_creation, provider, conf_details, old_data)

        return trip_id

    def _assert_route_is_not_changed(self, route):
        person_trip_route_city_ids = {item.provider_city_id for item in self.person_trip.route}
        new_route_city_ids = {item['provider_city_id'] for item in route}
        assert person_trip_route_city_ids == new_route_city_ids

    async def _update_route_dates(self, route):
        for point in route:
            await self.uow.person_trips.update_route_point(
                trip_id=self.person_trip.trip_id,
                person_id=self.person_trip.person_id,
                provider_city_id=point['provider_city_id'],
                date=point['date'],
                need_hotel=point.get('need_hotel', True),
                need_transfer=point.get('need_transfer', True),
            )

    def _add_related_jobs(
            self,
            is_creation: bool,
            provider: Provider,
            conf_details: dict,
            old_data: dict,
    ) -> None:
        if settings.IS_YA_TEAM:
            self.uow.add_job(
                'create_or_update_staff_trip_task',
                trip_id=self.trip_id,
                unique=False,
            )

        if is_creation:
            if provider == Provider.aeroclub:
                self.uow.add_job(
                    'create_aeroclub_trips_task',
                    trip_id=self.trip_id,
                    person_ids=[self.person_id],
                    unique=False,
                )
            self.uow.add_job(
                'create_trip_chats_task',
                trip_id=self.trip_id,
                person_ids=[self.person_id]
            )

        if conf_details:
            new_conf_role = conf_details.get('role', old_data.get('conf_role'))
            if settings.IS_YA_TEAM and new_conf_role != old_data.get('conf_role'):
                self.uow.add_job(
                    'update_conf_role_in_tracker_issue_task',
                    trip_id=self.trip_id,
                    person_id=self.person_id,
                )

            new_topic = conf_details.get('presentation_topic', old_data.get('presentation_topic'))
            new_is_approved = conf_details.get('is_hr_approved', old_data.get('is_hr_approved'))
            new_is_paid_by_host = conf_details.get(
                'is_paid_by_host', old_data.get('is_paid_by_host'),
            )

            need_to_notify_about_topic = (
                new_topic != old_data.get('presentation_topic')
                or new_is_approved != old_data.get('is_hr_approved')
            )
            if settings.IS_YA_TEAM and need_to_notify_about_topic:
                self.uow.add_job(
                    'update_speaker_details_in_tracker_issue_task',
                    trip_id=self.trip_id,
                    person_id=self.person_id,
                )

            need_to_notify_about_bribe_warning = (
                new_is_paid_by_host
                and not old_data.get('is_paid_by_host')
            )
            if settings.IS_YA_TEAM and need_to_notify_about_bribe_warning:
                self.uow.add_job(
                    'update_bribe_warning_in_tracker_issue_task',
                    trip_id=self.trip_id,
                    person_id=self.person_id,
                )


class PersonTripRestoreAction(PersonTripCreateUpdateAction):
    IS_REVIVAL = True

    async def check_permissions(self) -> None:
        if self.person_trip is None:
            raise WorkflowError('No person trip to restore')

        if self.person_trip.status != PTStatus.cancelled:
            raise WorkflowError('Wrong person trip status')

        await self.check_has_person_trip_read_permission()


class CancelAction(PersonTripAction):

    action_name = 'cancel'

    async def check_permissions(self) -> None:
        await self.check_has_person_trip_read_permission()

        if self.person_trip.status in (PTStatus.cancelled, PTStatus.closed):
            raise WorkflowError('Wrong person trip status')

    async def execute(self, person_trip_cancel: PersonTripCancel):
        trip_id = self.person_trip.trip_id
        person_id = self.person_trip.person_id

        async with self.uow:
            await self.uow.person_trips.update(trip_id, person_id, status=PTStatus.cancelled)

            person_trips = await self.uow.person_trips.get_list(trip_id=trip_id)
            active_person_trips = [pt for pt in person_trips if pt.status != PTStatus.cancelled]
            if not active_person_trips:
                await self.uow.trips.update(trip_id, status=TripStatus.cancelled)

            if settings.IS_YA_TEAM:
                self.uow.add_job(
                    'create_or_update_staff_trip_task',
                    trip_id=trip_id,
                    unique=False,
                )
            if self.person_trip.alive_services:
                self.uow.add_job(
                    'send_message_to_chat',
                    chat_id=self.person_trip.chat_id,
                    text='Необходима отмена командировки',
                )
                if self.person_trip.provider == Provider.aeroclub:
                    await aeroclub.send_message(
                        journey_id=self.person_trip.aeroclub_journey_id,
                        trip_id=self.person_trip.aeroclub_trip_id,
                        message='Необходима отмена командировки',
                        profile_id=self.person_trip.person.provider_profile_id,
                    )

            if self.person_trip.travel_tracker_issue:
                summonees = []
                if self.person_trip.manager_login:
                    summonees = [self.person_trip.manager_login]
                self.uow.add_job(
                    'notify_by_tracker_comment_task',
                    issue=self.person_trip.travel_tracker_issue,
                    template_name='cancel_person_trip.jinja2',
                    context={'cancellation_reason': person_trip_cancel.cancellation_reason},
                    summonees=summonees,
                )
            if self.person_trip.conf_tracker_issue:
                self.uow.add_job(
                    'notify_by_tracker_comment_task',
                    issue=self.person_trip.conf_tracker_issue,
                    template_name='cancel_person_trip.jinja2',
                    context={'cancellation_reason': person_trip_cancel.cancellation_reason},
                    summonees=[settings.CONF_MANAGER_LOGIN],
                )


class CreateServiceAction(PersonTripAction):

    action_name = 'create_service'

    async def check_permissions(self) -> None:
        self.check_is_enabled_service_workflow()
        await self.check_has_person_trip_read_permission()
        self.check_is_offline()
        self.check_aeroclub_ids()
        self.check_person_trip_authorized()

        if self.person_trip.status in (PTStatus.draft, PTStatus.cancelled, PTStatus.closed):
            raise WorkflowError('Wrong person trip status')

    async def get_available_bonus_card(self, service_type: ServiceType) -> ACBonusCard or None:
        """
        Возвращает бонусную карту сотрудника переданного типа,
        сохранённые в Аэроклубе.
        """
        # Отключаем автоматическую привязку бонусной карты пока не начнём получать код поставщика.
        return None

    async def execute(self, service_create: ServiceCreate) -> int:
        data = await aeroclub.add_service_to_trip(
            request_id=service_create.search_id,
            option_id=service_create.option_number,
            key=service_create.key,
            journey_id=self.person_trip.aeroclub_journey_id,
            trip_id=self.person_trip.aeroclub_trip_id,
            detail_index=service_create.detail_index,
        )
        available_bonus_card = await self.get_available_bonus_card(service_create.type)

        service_data = {
            'status': ServiceStatus.draft,
            'type': service_create.type,
            'provider_order_id': data['data'][0]['order_number'],
            'provider_service_id': data['data'][0]['service_number'],
            'is_authorized': False,
        }
        if available_bonus_card:
            service_data['provider_bonus_card_id'] = available_bonus_card.id
        if service_create.type == ServiceType.rail:
            service_data['seat_number'] = service_create.seat_number

        async with self.uow:
            service_id = await self.uow.services.create(
                trip_id=self.person_trip.trip_id,
                person_id=self.person_trip.person_id,
                **service_data,
            )

            if service_create.overprice_workflow is not None:
                overprice_workflow = service_create.overprice_workflow
                self.uow.add_job(
                    'notify_by_tracker_comment_task',
                    issue=self.person_trip.travel_tracker_issue,
                    template_name='overprice_service.jinja2',
                    context={
                        'overprice_workflow': overprice_workflow,
                        'OverpriceWorkflow': OverpriceWorkflow,
                    },
                )
        return service_id


class SendToVerificationAction(PersonTripAction):

    action_name = 'send_to_verification'

    async def check_permissions(self):
        self.check_is_enabled_service_workflow()
        await self.check_has_person_trip_read_permission()

        if not is_need_person_trip_verification(self.user, self.person_trip.person):
            raise WorkflowError('Do not need trip verification')

        self.check_is_offline()
        self.check_aeroclub_ids()
        self.check_person_trip_authorized()
        self.check_person_date_of_birth()

        statuses = {s.status for s in self.person_trip.alive_services}
        if ServiceStatus.draft not in statuses:
            raise WorkflowError('No service to process')

        for service in self.person_trip.alive_services:
            if service.provider_document_id is None and service.status == ServiceStatus.draft:
                raise WorkflowError('Service %d is without document' % service.service_id)

    async def execute(self):
        trip_id = self.person_trip.trip_id
        person_id = self.person_trip.person_id
        async with self.uow:
            await self.uow.person_trips.update(trip_id, person_id, status=PTStatus.verification)

            service_ids = [
                s.service_id
                for s in self.person_trip.services
                if s.status == ServiceStatus.draft
            ]
            await self.uow.services.bulk_update(
                service_ids=service_ids,
                status=ServiceStatus.verification,
            )

            if settings.TRIP_GROUP_EMAIL:
                self.uow.add_job(
                    'notify_by_tracker_comment_task',
                    issue=self.person_trip.travel_tracker_issue,
                    template_name='need_to_check_services.jinja2',
                    context={
                        'person_trip_url': self.person_trip.url,
                    },
                    maillist_summonees=[settings.TRIP_GROUP_EMAIL],
                )


class ExecuteServicesBaseAction(PersonTripAction):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.executor = AeroclubServicesExecutor(uow=self.uow, person_trip=self.person_trip)

    async def check_permissions(self) -> None:
        self.check_is_enabled_service_workflow()
        await self.check_has_person_trip_read_permission()

        if is_need_person_trip_verification(self.user, self.person_trip.person):
            raise WorkflowError('Do not need trip verification')

        self.check_is_offline()
        self.check_aeroclub_ids()
        self.check_person_trip_authorized()
        self.check_person_date_of_birth()

        if not self.person_trip.alive_services:
            raise WorkflowError('Empty service list')

        for service in self.person_trip.alive_services:
            if service.provider_document_id is None and service.status == ServiceStatus.draft:
                raise WorkflowError('Service %d is without document' % service.service_id)

    async def execute(self):
        trip_id = self.person_trip.trip_id
        person_id = self.person_trip.person_id
        async with self.uow:
            await self.uow.person_trips.update(trip_id, person_id, status=PTStatus.executing)

            service_ids = [
                s.service_id
                for s in self.person_trip.services
                if s.status == ServiceStatus.draft
            ]
            await self.uow.services.bulk_update(
                service_ids=service_ids,
                status=ServiceStatus.in_progress,
            )
            if self.person_trip.provider == Provider.aeroclub:
                self.uow.add_job(
                    'execute_person_trip_services',
                    trip_id=trip_id,
                    person_id=person_id,
                )


class ExecuteServicesAction(ExecuteServicesBaseAction):

    action_name = 'execute_services'

    async def check_permissions(self) -> None:
        await super().check_permissions()

        if self.person_trip.status not in (PTStatus.new, PTStatus.verification):
            raise WorkflowError('Wrong person trip status')

        for service in self.person_trip.alive_services:
            # TODO: BTRIP-702 Статус new для обратной совместимости. Надо будет удалить
            available_statuses = (
                ServiceStatus.draft,
                ServiceStatus.new,
                ServiceStatus.verification,
            )
            if service.status not in available_statuses:
                raise WorkflowError('Wrong service status')


class ExecuteExtraServicesAction(ExecuteServicesBaseAction):

    action_name = 'execute_extra_services'

    async def check_permissions(self) -> None:
        await super().check_permissions()

        if self.person_trip.status not in (
            PTStatus.verification,
            PTStatus.executing,
            PTStatus.executed,
        ):
            raise WorkflowError('Wrong person trip status')

        statuses = {s.status for s in self.person_trip.alive_services}
        if ServiceStatus.draft not in statuses and ServiceStatus.verification not in statuses:
            raise WorkflowError('Nothing to execute')


class CreateManagerChatAction(PersonTripAction):

    action_name = 'create_manager_chat'

    async def check_permissions(self):
        await self.check_has_person_trip_read_permission()
        if self.person_trip.manager_chat_id is not None:
            raise WorkflowError('Chat already created')
        if self.person_trip.manager_id is None:
            raise WorkflowError('Unable to create chat')

    async def execute(self):
        messenger_gw = await MessengerGateway.init(cache=self.cache)
        manager_chat_id = await messenger_gw.create_chat(
            uids=[self.person_trip.person.uid, self.person_trip.manager.uid],
            chat_name='Чат с координатором',
        )
        trip_id = self.person_trip.trip_id
        person_id = self.person_trip.person_id
        await bot_messenger.send_message(
            chat_id=manager_chat_id,
            text=f'Привет! Возникли вопросы с командировкой: {self.person_trip.url}',
        )
        await self.uow.person_trips.update(
            trip_id=trip_id,
            person_id=person_id,
            manager_chat_id=manager_chat_id,
        )
        return manager_chat_id


class JoinChatAction(PersonTripAction):

    action_name = 'join_chat'

    async def check_permissions(self):
        if not self.person_trip or not self.person_trip.chat_id:
            raise WorkflowError('Chat was not created')
        if not is_holding_coordinator(self.user, self.person_trip.person.company.holding_id):
            raise PermissionDenied(
                log_message=(
                    f'{self.__class__.__name__}: User is not holding '
                    f'{self.person_trip.person.company.holding_id} coordinator',
                ),
            )

    async def execute(self):
        messenger_gw = await MessengerGateway.init(cache=self.cache)
        await messenger_gw.add_member_to_chat(
            uid=str(self.user.uid),
            chat_id=self.person_trip.chat_id,
        )
        return self.person_trip.chat_id


class ApproveAction(PersonTripAction):

    action_name = 'approve'

    async def check_permissions(self) -> None:
        if settings.IS_YA_TEAM:
            raise WorkflowError()

        if self.person_trip.is_approved:
            raise WorkflowError('Person trip is already approved')

        if self.person_trip.status == PTStatus.cancelled:
            raise WorkflowError('Wrong person trip status')

        if not (
            is_holding_coordinator(self.user, self.person_trip.person.company.holding_id)
            or await self.roles_map.is_approver(self.user.person_id, self.person_trip.person_id)
        ):
            raise PermissionDenied(log_message='Not enough permissions to approve person trip')

    async def execute(self):
        await self.uow.person_trips.update(
            trip_id=self.person_trip.trip_id,
            person_id=self.person_trip.person_id,
            is_approved=True,
        )
        logger.info(
            'Person %s approved person trip with trip_id=%s, person_id=%s',
            self.user.person_id,
            self.person_trip.trip_id,
            self.person_trip.person_id,
        )


async def get_person_trip_actions(
        uow: UnitOfWork,
        user: User,
        person_trip: PersonTrip,
        roles_map: LazyRolesMap,
):
    action_classes = [
        PersonTripCreateUpdateAction,
        CancelAction,
        CreateServiceAction,
        SendToVerificationAction,
        ExecuteServicesAction,
        ExecuteExtraServicesAction,
        CreateManagerChatAction,
        JoinChatAction,
        ApproveAction,
    ]
    data = {}
    for action_class in action_classes:
        action = action_class(
            uow=uow,
            user=user,
            person_trip=person_trip,
            roles_map=roles_map,
        )
        data[action.action_name] = await action.is_available()

    data['read'] = await has_person_trip_read_perm(
        user, person_trip, roles_map,
    )
    data['search_service'] = (
        data['read']
        and (person_trip.conf_details is None or person_trip.conf_details.is_another_city)
        and not person_trip.is_offline
        and person_trip.status not in (PTStatus.cancelled, PTStatus.closed)
    )
    return PersonTripActions(**data)
