import asyncio
from datetime import date
import logging

from fastapi import APIRouter, Depends, HTTPException, Request, Response
from pydantic import ValidationError

from intranet.trip.src.api.auth import (
    is_yandex_coordinator,
    has_trip_read_perm,
    has_person_trip_read_perm,
    LazyRolesMap,
)
from intranet.trip.src.api.converters.aeroclub import AeroclubServiceConverter
from intranet.trip.src.api.decorators import exclude_route
from intranet.trip.src.api.depends import Pagination, get_cache, get_unit_of_work
from intranet.trip.src.api.schemas import (
    ChatId,
    ManagerChatId,
    PaginatedResponse,
    PersonTrip,
    PersonTripCancel,
    PersonTripPartialUpdate,
    PersonTripServices,
    PersonTripUpdate,
    Trip,
    TripCreate,
    TripFilter,
    TripId,
    TripStaffFilter,
    TripUpdate,
)
from intranet.trip.src.cache import Cache
from intranet.trip.src.config import settings
from intranet.trip.src.enums import RelationType
from intranet.trip.src.exceptions import PermissionDenied, WorkflowError
from intranet.trip.src.lib.aeroclub.api import aeroclub
from intranet.trip.src.lib.staff.gateway import StaffGateway
from intranet.trip.src.logic.person_trips import (
    ApproveAction,
    CancelAction,
    CreateManagerChatAction,
    ExecuteExtraServicesAction,
    ExecuteServicesAction,
    JoinChatAction,
    PersonTripCreateUpdateAction,
    PersonTripRestoreAction,
    SendToVerificationAction,
    get_person_trip_actions,
)
from intranet.trip.src.logic.services import get_service_actions
from intranet.trip.src.logic.trips import TripCreateUpdateAction
from intranet.trip.src.unit_of_work import UnitOfWork

logger = logging.getLogger(__name__)
router = APIRouter()


@router.post('/', status_code=201, response_model=TripId)
async def trip_create(
    request: Request,
    trip: TripCreate,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await TripCreateUpdateAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=None,
    )
    try:
        trip_id = await action.check_and_execute(trip=trip)
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=e.errors())
    except WorkflowError as e:
        raise HTTPException(status_code=400, detail=str(e))

    return {'trip_id': trip_id}


@router.get('/', response_model=PaginatedResponse[Trip])
async def trip_list(
    request: Request,
    uow: UnitOfWork = Depends(get_unit_of_work(read_only=True)),
    pagination=Depends(Pagination),
    date_from__lte: date = None,
    date_from__gte: date = None,
    aeroclub_city_to_id: int = None,
    provider_city_to_id: str = None,
    tracker_issue: str = None,
    manager: int = None,
    person: int = None,
    relation_type: RelationType = None,
):
    user = request.state.user
    if aeroclub_city_to_id:
        provider_city_to_id = str(aeroclub_city_to_id)

    person_id = None
    manager_id = None
    can_filter_by_person = user.is_coordinator or not user.is_limited_access
    if can_filter_by_person and (person or manager):
        if person:
            person_id = person
        if manager:
            manager_id = manager
    elif not can_filter_by_person:
        person_id = user.person_id

    holding_id = user.company.holding_id
    if is_yandex_coordinator(user):
        holding_id = None

    trip_filter = TripFilter(
        limit=pagination.limit,
        offset=pagination.offset,
        date_from__lte=date_from__lte,
        date_from__gte=date_from__gte,
        provider_city_to_id=provider_city_to_id,
        tracker_issue=tracker_issue,
        manager_id=manager_id,
        person_id=person_id,
        relation_type=relation_type,
        holding_id=holding_id,
    )
    trips, count = await uow.trips.get_list_and_count(trip_filter, user)

    return pagination.get_paginated_response(
        data=trips,
        count=count,
    )


@router.get(
    '/from_staff',
    response_model=PaginatedResponse[Trip],
    include_in_schema=settings.IS_YA_TEAM,
)
@exclude_route(is_exclude=not settings.IS_YA_TEAM)
async def trip_list_from_staff(
    request: Request,
    pagination=Depends(Pagination),
    event_type: str = None,
    date_from: date = None,
    date_to: date = None,
    participants: str = None,
):
    trip_filter = TripStaffFilter(
        limit=pagination.limit,
        page=pagination.page,
        event_type=event_type,
        date_from=date_from,
        date_to=date_to,
        participants=participants,
    )
    gateway = await StaffGateway.init(user_ticket=request.state.user.user_ticket)
    trips, count = await gateway.get_list_and_count(trip_filter)

    return pagination.get_paginated_response(
        data=trips,
        count=count,
    )


@router.get('/{trip_id}', response_model=Trip)
async def trip_detail(
    request: Request,
    trip_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work(read_only=True)),
):
    user = request.state.user

    trip = await uow.trips.get_detailed_trip(trip_id=trip_id)
    person_trips = trip.person_trips or []
    roles_map = LazyRolesMap(uow, person_ids=[pt.person.person_id for pt in person_trips])
    if not await has_trip_read_perm(user, trip, roles_map):
        raise HTTPException(status_code=403, detail='not your trip')

    person_ids = [pt.person_id for pt in person_trips]
    # Юзер с ограниченным доступом имеющий доступ к этой командировке
    # или если это его командировка и он имеет доступ только к своей персональной командировке
    # или имеет доступ ко всем персональным командировкам
    if user.is_limited_access and not user.is_coordinator:
        if user.person_id in person_ids:
            person_ids = [user.person_id]
            trip.person_trips = [pt for pt in person_trips if pt.person_id == user.person_id]
            person_trips = trip.person_trips

    lazy_roles_map = LazyRolesMap(uow, person_ids=person_ids)
    for person_trip in person_trips:
        person_trip.actions = await get_person_trip_actions(
            uow=uow,
            user=user,
            person_trip=person_trip,
            roles_map=lazy_roles_map,
        )
    return trip


@router.put('/{trip_id}', response_model=TripId)
async def trip_update(
    request: Request,
    trip_id: int,
    trip: TripUpdate,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await TripCreateUpdateAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
    )
    try:
        trip_id = await action.check_and_execute(trip=trip)
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=e.errors())

    return {'trip_id': trip_id}


@router.get('/{trip_id}/persons/{person_id}', response_model=PersonTrip)
async def person_trip_detail(
    request: Request,
    trip_id: int,
    person_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work(read_only=True)),
):
    person_trip = await uow.person_trips.get_detailed_person_trip(trip_id, person_id)
    lazy_roles_map = LazyRolesMap(uow, person_ids=[person_trip.person.person_id])
    if not await has_person_trip_read_perm(
        user=request.state.user,
        person_trip=person_trip,
        roles_map=lazy_roles_map,
    ):
        raise PermissionDenied(
            log_message=(
                'User has not person trip read permission '
                f'for person_trip ({person_trip.trip_id=}, {person_trip.person_id=})'
            ),
        )
    person_trip.actions = await get_person_trip_actions(
        uow=uow,
        user=request.state.user,
        person_trip=person_trip,
        roles_map=lazy_roles_map,
    )
    # Using copy person_trip because pydantic does not support cyclic import
    # https://github.com/samuelcolvin/pydantic/issues/2279
    person_trip_copy = person_trip.copy(
        exclude={
            'services', 'purposes', 'travel_details', 'documents', 'conf_details',
        }
    )
    for service in person_trip.services or []:
        # TODO: так плохо делать
        service.person = person_trip.person
        service.person_trip = person_trip_copy
        service.actions = await get_service_actions(
            uow=uow,
            user=request.state.user,
            service=service,
            roles_map=lazy_roles_map,
        )
    return person_trip


@router.get('/{trip_id}/persons/{person_id}/services', response_model=PersonTripServices)
async def person_trip_services(
    request: Request,
    trip_id: int,
    person_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work(read_only=True)),
):
    person_trip = await uow.person_trips.get_detailed_person_trip(trip_id, person_id)
    lazy_roles_map = LazyRolesMap(uow, person_ids=[person_trip.person.person_id])
    if not await has_person_trip_read_perm(
        user=request.state.user,
        person_trip=person_trip,
        roles_map=lazy_roles_map,
    ):
        raise PermissionDenied(
            log_message=(
                'User has not person trip read permission '
                f'for person_trip ({person_trip.trip_id=}, {person_trip.person_id=})'
            ),
        )
    services = person_trip.services or []
    coros = [
        aeroclub.get_service(
            order_id=service.provider_order_id,
            service_id=service.provider_service_id,
        )
        for service in services
    ]
    aeroclub_services = await asyncio.gather(*coros)

    result = {
        'services': [],
        'avia_services': [],
        'rail_services': [],
        'hotel_services': [],
        'errors': [],
    }
    converter = AeroclubServiceConverter()
    # TODO: обрабатывать 404
    for service, aeroclub_service in zip(services, aeroclub_services):
        service.person = person_trip.person
        service.actions = await get_service_actions(
            uow=uow,
            user=request.state.user,
            service=service,
            roles_map=lazy_roles_map,
        )
        result['services'].append(service)
        data = converter.convert(service, aeroclub_service)
        result[f'{service.type}_services'].append(data)

    return result


async def _create_update_person_trip(request, uow, trip_id, person_id, person_trip_update, partial):
    action = await PersonTripCreateUpdateAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    if not await action.is_available():
        action = await PersonTripRestoreAction.init(
            uow=uow,
            user=request.state.user,
            trip_id=trip_id,
            person_id=person_id,
        )
    try:
        trip_id = await action.check_and_execute(person_trip_update, partial_update=partial)
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=e.errors())
    except WorkflowError as e:
        raise HTTPException(status_code=400, detail=str(e))

    return {'trip_id': trip_id}


@router.put('/{trip_id}/persons/{person_id}', response_model=TripId)
async def person_trip_update(
    request: Request,
    trip_id: int,
    person_id: int,
    person_trip: PersonTripUpdate,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    return await _create_update_person_trip(
        request=request,
        uow=uow,
        trip_id=trip_id,
        person_id=person_id,
        person_trip_update=person_trip,
        partial=False,
    )


@router.patch('/{trip_id}/persons/{person_id}', response_model=TripId)
async def person_trip_partial_update(
    request: Request,
    trip_id: int,
    person_id: int,
    person_trip: PersonTripPartialUpdate,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    return await _create_update_person_trip(
        request=request,
        uow=uow,
        trip_id=trip_id,
        person_id=person_id,
        person_trip_update=person_trip,
        partial=True,
    )


@router.post(
    path='/{trip_id}/persons/{person_id}/send_to_verification',
    status_code=204,
    response_class=Response,
)
async def person_trip_send_to_verification(
    trip_id: int,
    person_id: int,
    request: Request,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await SendToVerificationAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    await action.check_and_execute()


@router.post(
    path='/{trip_id}/persons/{person_id}/execute_services',
    status_code=204,
    response_class=Response,
)
async def person_trip_execute_services(
    trip_id: int,
    person_id: int,
    request: Request,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await ExecuteServicesAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    await action.check_and_execute()


@router.post(
    path='/{trip_id}/persons/{person_id}/execute_extra_services',
    status_code=204,
    response_class=Response,
)
async def person_trip_execute_extra_services(
    trip_id: int,
    person_id: int,
    request: Request,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await ExecuteExtraServicesAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    await action.check_and_execute()


@router.post('/{trip_id}/persons/{person_id}/cancel', status_code=204, response_class=Response)
async def person_trip_cancel(
    request: Request,
    trip_id: int,
    person_id: int,
    person_trip_cancel_in: PersonTripCancel,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await CancelAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    await action.check_and_execute(person_trip_cancel_in)


@router.post('/{trip_id}/persons/{person_id}/approve', status_code=204, response_class=Response)
async def person_trip_approve(
    request: Request,
    trip_id: int,
    person_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await ApproveAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    await action.check_and_execute()


@router.put('/{trip_id}/documents/{document_id}', status_code=204, response_class=Response)
async def person_trip_document_add(
    request: Request,
    trip_id: int,
    document_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    author_person_id = request.state.user.person_id

    async with uow:
        await uow.trips.add_document(trip_id, document_id, author_person_id)
        if settings.IS_YA_TEAM:
            uow.add_job(
                'create_or_update_staff_trip_task',
                trip_id=trip_id,
                unique=False,
            )


@router.delete('/{trip_id}/documents/{document_id}', status_code=204, response_class=Response)
async def person_trip_document_remove(
    request: Request,
    trip_id: int,
    document_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    author_person_id = request.state.user.person_id
    await uow.trips.remove_document(trip_id, document_id, author_person_id)


@router.post('/{trip_id}/persons/{person_id}/create_manager_chat', response_model=ManagerChatId)
async def create_manager_chat(
    request: Request,
    trip_id: int,
    person_id: int,
    uow: UnitOfWork = Depends(get_unit_of_work()),
):
    action = await CreateManagerChatAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
    )
    manager_chat_id = await action.check_and_execute()
    return {
        'manager_chat_id': manager_chat_id,
    }


@router.post(
    path='/{trip_id}/persons/{person_id}/join_chat',
    response_model=ChatId,
)
async def person_trip_join_chat(
    trip_id: int,
    person_id: int,
    request: Request,
    uow: UnitOfWork = Depends(get_unit_of_work()),
    cache: Cache = Depends(get_cache),
):
    action = await JoinChatAction.init(
        uow=uow,
        user=request.state.user,
        trip_id=trip_id,
        person_id=person_id,
        cache=cache,
    )
    chat_id = await action.check_and_execute()
    return {
        'chat_id': chat_id,
    }
