from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Security, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import settings
from app.database.crud.ticket import TicketCRUD, TicketMessageCRUD
from app.database.models import Ticket, TicketMessage, TicketStatus

from ..dependencies import get_db_session, require_api_token
from ..schemas.tickets import (
    TicketMediaResponse,
    TicketMessageResponse,
    TicketPriorityUpdateRequest,
    TicketReplyBlockRequest,
    TicketReplyRequest,
    TicketReplyResponse,
    TicketResponse,
    TicketStatusUpdateRequest,
)


router = APIRouter()
logger = structlog.get_logger(__name__)


def _serialize_message(message: TicketMessage) -> TicketMessageResponse:
    return TicketMessageResponse(
        id=message.id,
        user_id=message.user_id,
        message_text=message.message_text,
        is_from_admin=message.is_from_admin,
        has_media=message.has_media,
        media_type=message.media_type,
        media_file_id=message.media_file_id,
        media_caption=message.media_caption,
        created_at=message.created_at,
    )


def _serialize_ticket(ticket: Ticket, include_messages: bool = False) -> TicketResponse:
    messages = []
    if include_messages:
        messages = sorted(ticket.messages, key=lambda m: m.created_at)

    return TicketResponse(
        id=ticket.id,
        user_id=ticket.user_id,
        title=ticket.title,
        status=ticket.status,
        priority=ticket.priority,
        created_at=ticket.created_at,
        updated_at=ticket.updated_at,
        closed_at=ticket.closed_at,
        user_reply_block_permanent=ticket.user_reply_block_permanent,
        user_reply_block_until=ticket.user_reply_block_until,
        messages=[_serialize_message(message) for message in messages],
    )


@router.get('', response_model=list[TicketResponse])
async def list_tickets(
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
    limit: int = Query(50, ge=1, le=200),
    offset: int = Query(0, ge=0),
    status_filter: TicketStatus | None = Query(default=None, alias='status'),
    priority: str | None = Query(default=None),
    user_id: int | None = Query(default=None),
) -> list[TicketResponse]:
    status_value = status_filter.value if status_filter else None

    if user_id:
        tickets = await TicketCRUD.get_user_tickets(
            db,
            user_id=user_id,
            status=status_value,
            limit=limit,
            offset=offset,
        )
    else:
        tickets = await TicketCRUD.get_all_tickets(
            db,
            status=status_value,
            priority=priority,
            limit=limit,
            offset=offset,
        )

    return [_serialize_ticket(ticket) for ticket in tickets]


@router.get('/{ticket_id}', response_model=TicketResponse)
async def get_ticket(
    ticket_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketResponse:
    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    if not ticket:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')
    return _serialize_ticket(ticket, include_messages=True)


@router.post('/{ticket_id}/status', response_model=TicketResponse)
async def update_ticket_status(
    ticket_id: int,
    payload: TicketStatusUpdateRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketResponse:
    try:
        status_value = TicketStatus(payload.status).value
    except ValueError as error:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Invalid ticket status') from error

    closed_at = datetime.now(UTC) if status_value == TicketStatus.CLOSED.value else None
    success = await TicketCRUD.update_ticket_status(db, ticket_id, status_value, closed_at)
    if not success:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')

    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    return _serialize_ticket(ticket, include_messages=True)


@router.post('/{ticket_id}/priority', response_model=TicketResponse)
async def update_ticket_priority(
    ticket_id: int,
    payload: TicketPriorityUpdateRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketResponse:
    allowed_priorities = {'low', 'normal', 'high', 'urgent'}
    if payload.priority not in allowed_priorities:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Invalid priority')

    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    if not ticket:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')

    ticket.priority = payload.priority
    ticket.updated_at = datetime.now(UTC)
    await db.commit()

    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    return _serialize_ticket(ticket, include_messages=True)


@router.post('/{ticket_id}/reply-block', response_model=TicketResponse)
async def update_reply_block(
    ticket_id: int,
    payload: TicketReplyBlockRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketResponse:
    until = payload.until
    if not payload.permanent and until and until <= datetime.now(UTC):
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Block expiration must be in the future')

    success = await TicketCRUD.set_user_reply_block(
        db,
        ticket_id,
        permanent=payload.permanent,
        until=until,
    )
    if not success:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')

    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    return _serialize_ticket(ticket, include_messages=True)


@router.delete('/{ticket_id}/reply-block', response_model=TicketResponse)
async def clear_reply_block(
    ticket_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketResponse:
    success = await TicketCRUD.set_user_reply_block(
        db,
        ticket_id,
        permanent=False,
        until=None,
    )
    if not success:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')

    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    return _serialize_ticket(ticket, include_messages=True)


@router.post('/{ticket_id}/reply', response_model=TicketReplyResponse, status_code=status.HTTP_201_CREATED)
async def reply_to_ticket(
    ticket_id: int,
    payload: TicketReplyRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketReplyResponse:
    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=False, load_user=True)
    if not ticket:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')

    message_text = (payload.message_text or '').strip()
    if not message_text and not payload.media_file_id:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, 'Message text or media is required')

    final_message_text = message_text or (payload.media_caption or '').strip() or '[media]'

    message = await TicketMessageCRUD.add_message(
        db,
        ticket_id=ticket_id,
        user_id=ticket.user_id,
        message_text=final_message_text,
        is_from_admin=True,
        media_type=payload.media_type,
        media_file_id=payload.media_file_id,
        media_caption=payload.media_caption,
    )

    bot = Bot(
        token=settings.BOT_TOKEN,
        default=DefaultBotProperties(parse_mode=ParseMode.HTML),
    )
    try:
        from app.handlers.admin.tickets import notify_user_about_ticket_reply

        await notify_user_about_ticket_reply(bot, ticket, final_message_text, db)
    finally:
        await bot.session.close()

    ticket_with_messages = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)

    # Отправляем событие о новом сообщении через API
    try:
        from app.services.event_emitter import event_emitter

        await event_emitter.emit(
            'ticket.message_added',
            {
                'ticket_id': ticket_id,
                'message_id': message.id,
                'user_id': ticket.user_id,
                'is_from_admin': True,
                'message_text': final_message_text[:200],
                'has_media': bool(payload.media_file_id),
                'status': ticket_with_messages.status,
            },
            db=db,
        )
    except Exception as error:
        logger.warning('Failed to emit ticket.message_added event', error=error)

    return TicketReplyResponse(
        ticket=_serialize_ticket(ticket_with_messages, include_messages=True),
        message=_serialize_message(message),
    )


@router.get(
    '/{ticket_id}/messages/{message_id}/media',
    response_model=TicketMediaResponse,
)
async def get_ticket_message_media(
    ticket_id: int,
    message_id: int,
    request: Request,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> TicketMediaResponse:
    ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=True, load_user=False)
    if not ticket:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Ticket not found')

    message = next((m for m in ticket.messages if m.id == message_id), None)
    if not message:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Message not found')

    if not message.has_media or not message.media_file_id or not message.media_type:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Media not found for this message')

    media_url: str | None = None
    bot = Bot(
        token=settings.BOT_TOKEN,
        default=DefaultBotProperties(parse_mode=ParseMode.HTML),
    )
    try:
        file = await bot.get_file(message.media_file_id)
        if file.file_path:
            media_url = str(request.url_for('download_media', file_id=message.media_file_id))
    except Exception as error:
        logger.warning(
            'Failed to resolve media URL for ticket message', ticket_id=ticket_id, message_id=message_id, error=error
        )
    finally:
        await bot.session.close()

    return TicketMediaResponse(
        id=message.id,
        ticket_id=ticket.id,
        media_type=message.media_type,
        media_file_id=message.media_file_id,
        media_caption=message.media_caption,
        media_url=media_url,
    )
