import logging

from intranet.trip.src.api.auth.permissions import (
    has_service_read_perm,
    LazyRolesMap,
    LazyManagersMapByTrip,
)
from intranet.trip.src.api.schemas import ServiceDocumentIn, ServiceReserveIn, ServiceUpdateIn
from intranet.trip.src.enums import ServiceStatus, ServiceType
from intranet.trip.src.exceptions import PermissionDenied, WorkflowError
from intranet.trip.src.lib.aeroclub.api import aeroclub
from intranet.trip.src.logic.aeroclub.services import get_rail_bonus_card_id
from intranet.trip.src.logic.base import Action
from intranet.trip.src.models import ServiceActions, User, Service
from intranet.trip.src.unit_of_work import UnitOfWork

logger = logging.getLogger(__name__)


class ServiceAction(Action):

    def __init__(
            self,
            uow: UnitOfWork,
            user: User,
            service: Service,
            roles_map: LazyRolesMap = None,
            managers_map: LazyManagersMapByTrip = None,
    ):
        self.uow = uow
        self.user = user
        self.service = service
        self.roles_map = roles_map or LazyRolesMap(uow, person_ids=[service.person.person_id])
        self.managers_map = managers_map or LazyManagersMapByTrip(uow, trip_ids=[service.trip_id])

    @classmethod
    async def init(
            cls,
            uow: UnitOfWork,
            user: User,
            service_id: int,
            roles_map: LazyRolesMap = None,
            managers_map: LazyManagersMapByTrip = None,
    ):
        service = await cls.get_service(uow, service_id)
        return cls(uow, user, service, roles_map, managers_map)

    @classmethod
    async def get_service(cls, uow: UnitOfWork, service_id: int) -> Service:
        return await uow.services.get_service(service_id)

    def permission_denied_message(self):
        return (
            f'{self.__class__.__name__}: User has not read permission '
            f'for service {self.service.service_id}'
        )


class AddDocumentAction(ServiceAction):

    action_name = 'add_document'

    async def check_permissions(self) -> None:
        if not await has_service_read_perm(
            self.user, self.service, self.roles_map, self.managers_map,
        ):
            raise PermissionDenied(log_message=self.permission_denied_message())

        if self.service.provider_document_id:
            raise WorkflowError('Document already added')

        if self.service.status not in (ServiceStatus.draft, ServiceStatus.in_progress):
            raise WorkflowError('Wrong service status')

    async def execute(self, service_document_in: ServiceDocumentIn):
        await self.uow.services.update(
            service_id=self.service.service_id,
            provider_document_id=service_document_in.provider_document_id,
        )


class ChangeDocumentAction(AddDocumentAction):

    action_name = 'change_document'

    async def check_permissions(self) -> None:
        if not await has_service_read_perm(
            self.user, self.service, self.roles_map, self.managers_map,
        ):
            raise PermissionDenied(log_message=self.permission_denied_message())

        if not self.service.provider_document_id:
            raise WorkflowError('Firstly add document')

        if self.service.status != ServiceStatus.draft:
            raise WorkflowError('Wrong service status')


class RemoveDocumentAction(ChangeDocumentAction):

    action_name = 'remove_document'

    async def execute(self, *args, **kwargs):
        await self.uow.services.update(
            service_id=self.service.service_id,
            provider_document_id=None,
        )


class ReserveAction(ServiceAction):

    action_name = 'reserve'

    async def check_permissions(self) -> None:
        if not await has_service_read_perm(
            self.user, self.service, self.roles_map, self.managers_map,
        ):
            raise PermissionDenied(log_message=self.permission_denied_message())
        if self.service.type != ServiceType.rail:
            raise WorkflowError('Cannot reserve %s service' % self.service.type.name)
        if self.service.seat_number is not None:
            raise WorkflowError('Cannot reserve service. Seat was already selected')
        if self.service.status != ServiceStatus.in_progress or not self.service.is_authorized:
            raise WorkflowError('Cannot reserve service in status %s' % self.service.status.name)

    async def execute(self, service_reserve_in: ServiceReserveIn):
        aeroclub_service = await aeroclub.get_service(
            order_id=self.service.provider_order_id,
            service_id=self.service.provider_service_id,
        )
        if 'Reservation' not in aeroclub_service['available_actions']:
            raise WorkflowError('Cannot reserve service in Aeroclub')

        travellers: dict = aeroclub_service['travellers']
        if len(travellers) == 0:
            raise WorkflowError('Cannot reserve service in Aeroclub. Empty travellers list.')

        seat_number = service_reserve_in.seat_number
        rail_parameters = {
            'seats_from': seat_number,
            'seats_to': seat_number,
            'location_type': 2,  # RailwayStation
            'remote_checkin': True,
            'traveller_parameters': [{
                'place_count': 1,
                'traveller_id': travellers[0]['id'],
            }]
        }

        bonus_card_id = await get_rail_bonus_card_id(self.service.person.provider_profile_id)
        if bonus_card_id:
            traveller_parameters = rail_parameters['traveller_parameters'][0]
            traveller_parameters['bonus_card_id'] = bonus_card_id

        await aeroclub.reserve_service(
            order_id=self.service.provider_order_id,
            service_id=self.service.provider_service_id,
            auto_execution=True,
            rail_parameters=rail_parameters,
        )
        await self.uow.services.update(
            service_id=self.service.service_id,
            status=ServiceStatus.reserved,
            seat_number=seat_number,
        )


class UpdateAction(ServiceAction):

    action_name = 'update'

    async def check_permissions(self) -> None:
        if not await has_service_read_perm(
            self.user, self.service, self.roles_map, self.managers_map,
        ):
            raise PermissionDenied(log_message=self.permission_denied_message())
        if self.service.type != ServiceType.rail:
            raise WorkflowError('Cannot update service with type `%s`' % self.service.type.name)
        if self.service.status != ServiceStatus.draft:
            raise WorkflowError('Cannot update service in status `%s`' % self.service.status.name)

    async def execute(self, service_update_in: ServiceUpdateIn):
        async with self.uow:
            service_id = await self.uow.services.update(
                service_id=self.service.service_id,
                seat_number=service_update_in.seat_number,
            )
        return service_id


class DeleteAction(ServiceAction):
    """
    Удаление услуги.
    Удалить услугу можно, пока не запустились процессы в Аэроклубе.
    Это возможно, пока услуга находится в статусах draft или verification.
    Также допустимо удаление в статусе in_progress, при условии, что person_trip еще не согласован.
    Иначе услугу нужно отменять через чат с агентом.
    """
    action_name = 'delete'

    async def check_permissions(self) -> None:
        if not await has_service_read_perm(
            self.user, self.service, self.roles_map, self.managers_map,
        ):
            raise PermissionDenied(log_message=self.permission_denied_message())

        if self.service.status in (ServiceStatus.cancelled, ServiceStatus.deleted):
            raise WorkflowError('Cannot remove service in status `%s`' % self.service.status.name)
        if self.service.in_process_of_cancelling:
            raise WorkflowError('Cannot remove service in process of cancelling')

    async def execute(self):
        await aeroclub.cancel_service(
            order_id=self.service.provider_order_id,
            service_id=self.service.provider_service_id,
        )
        update_fields = {'in_process_of_cancelling': True}
        if (
            self.service.status in (ServiceStatus.draft, ServiceStatus.verification)
            or (
                not self.service.is_person_trip_approved
                and self.service.status == ServiceStatus.in_progress
            )
        ):
            update_fields['status'] = ServiceStatus.deleted

        async with self.uow:
            await self.uow.services.update(
                service_id=self.service.service_id,
                **update_fields,
            )


async def get_service_actions(
        uow: UnitOfWork,
        user: User,
        service: Service,
        roles_map: LazyRolesMap,
):
    data = {}
    action_classes = [
        AddDocumentAction,
        ChangeDocumentAction,
        RemoveDocumentAction,
        ReserveAction,
        UpdateAction,
        DeleteAction,
    ]

    for action_class in action_classes:
        action = action_class(
            uow=uow,
            user=user,
            service=service,
            roles_map=roles_map,
        )
        data[action.action_name] = await action.is_available()
    return ServiceActions(**data)
