from typing import List, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc, and_, or_, update, func
from sqlalchemy.orm import selectinload
from datetime import datetime

from app.database.models import Ticket, TicketMessage, TicketStatus, User


class TicketCRUD:
    """CRUD операции для работы с тикетами"""
    
    @staticmethod
    async def create_ticket(
        db: AsyncSession,
        user_id: int,
        title: str,
        message_text: str,
        priority: str = "normal",
        *,
        media_type: Optional[str] = None,
        media_file_id: Optional[str] = None,
        media_caption: Optional[str] = None,
    ) -> Ticket:
        """Создать новый тикет с первым сообщением"""
        ticket = Ticket(
            user_id=user_id,
            title=title,
            status=TicketStatus.OPEN.value,
            priority=priority
        )
        db.add(ticket)
        await db.flush()  # Получаем ID тикета
        
        # Создаем первое сообщение
        message = TicketMessage(
            ticket_id=ticket.id,
            user_id=user_id,
            message_text=message_text,
            is_from_admin=False,
            has_media=bool(media_type and media_file_id),
            media_type=media_type,
            media_file_id=media_file_id,
            media_caption=media_caption,
        )
        db.add(message)
        
        await db.commit()
        await db.refresh(ticket)
        return ticket
    
    @staticmethod
    async def get_ticket_by_id(
        db: AsyncSession,
        ticket_id: int,
        load_messages: bool = True,
        load_user: bool = False
    ) -> Optional[Ticket]:
        """Получить тикет по ID"""
        query = select(Ticket).where(Ticket.id == ticket_id)
        
        if load_user:
            query = query.options(selectinload(Ticket.user))
        
        if load_messages:
            query = query.options(selectinload(Ticket.messages))
        
        result = await db.execute(query)
        return result.scalar_one_or_none()
    
    @staticmethod
    async def get_user_tickets(
        db: AsyncSession,
        user_id: int,
        status: Optional[str] = None,
        limit: int = 20,
        offset: int = 0
    ) -> List[Ticket]:
        """Получить тикеты пользователя"""
        query = select(Ticket).where(Ticket.user_id == user_id)
        
        if status:
            query = query.where(Ticket.status == status)
        
        query = query.order_by(desc(Ticket.updated_at)).offset(offset).limit(limit)
        
        result = await db.execute(query)
        return result.scalars().all()

    @staticmethod
    async def user_has_active_ticket(
        db: AsyncSession,
        user_id: int
    ) -> bool:
        """Проверить, есть ли у пользователя активный (не закрытый) тикет"""
        query = (
            select(Ticket.id)
            .where(
                Ticket.user_id == user_id,
                Ticket.status.in_([TicketStatus.OPEN.value, TicketStatus.ANSWERED.value])
            )
            .limit(1)
        )
        result = await db.execute(query)
        return result.scalar_one_or_none() is not None

    @staticmethod
    async def is_user_globally_blocked(
        db: AsyncSession,
        user_id: int
    ) -> Optional[datetime]:
        """Проверить, заблокирован ли пользователь для создания/ответов по любому тикету.
        Возвращает дату окончания блокировки, если активна, или None.
        """
        query = select(Ticket).where(
            Ticket.user_id == user_id,
            or_(Ticket.user_reply_block_permanent == True, Ticket.user_reply_block_until.isnot(None))
        ).order_by(desc(Ticket.updated_at)).limit(10)
        result = await db.execute(query)
        tickets = result.scalars().all()
        if not tickets:
            return None
        from datetime import datetime
        # Если есть вечная блокировка в любом тикете — блок активен без срока
        for t in tickets:
            if t.user_reply_block_permanent:
                return datetime.max
        # Иначе ищем максимальный срок блокировки, если он в будущем
        future_until = [t.user_reply_block_until for t in tickets if t.user_reply_block_until]
        if not future_until:
            return None
        max_until = max(future_until)
        return max_until if max_until > datetime.utcnow() else None
    
    @staticmethod
    async def get_all_tickets(
        db: AsyncSession,
        status: Optional[str] = None,
        priority: Optional[str] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[Ticket]:
        """Получить все тикеты (для админов)"""
        query = select(Ticket).options(selectinload(Ticket.user))
        
        conditions = []
        if status:
            conditions.append(Ticket.status == status)
        if priority:
            conditions.append(Ticket.priority == priority)
        
        if conditions:
            query = query.where(and_(*conditions))
        
        query = query.order_by(desc(Ticket.updated_at)).offset(offset).limit(limit)
        
        result = await db.execute(query)
        return result.scalars().all()

    @staticmethod
    async def get_tickets_by_statuses(
        db: AsyncSession,
        statuses: List[str],
        limit: int = 50,
        offset: int = 0
    ) -> List[Ticket]:
        query = select(Ticket).options(selectinload(Ticket.user))
        if statuses:
            query = query.where(Ticket.status.in_(statuses))
        query = query.order_by(desc(Ticket.updated_at)).offset(offset).limit(limit)
        result = await db.execute(query)
        return result.scalars().all()

    @staticmethod
    async def count_tickets(
        db: AsyncSession,
        status: Optional[str] = None
    ) -> int:
        query = select(func.count()).select_from(Ticket)
        if status:
            query = query.where(Ticket.status == status)
        result = await db.execute(query)
        return int(result.scalar() or 0)

    @staticmethod
    async def count_tickets_by_statuses(
        db: AsyncSession,
        statuses: List[str]
    ) -> int:
        query = select(func.count()).select_from(Ticket)
        if statuses:
            query = query.where(Ticket.status.in_(statuses))
        result = await db.execute(query)
        return int(result.scalar() or 0)
    
    @staticmethod
    async def update_ticket_status(
        db: AsyncSession,
        ticket_id: int,
        status: str,
        closed_at: Optional[datetime] = None
    ) -> bool:
        """Обновить статус тикета"""
        ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=False)
        if not ticket:
            return False
        
        ticket.status = status
        ticket.updated_at = datetime.utcnow()
        
        if status == TicketStatus.CLOSED.value and closed_at:
            ticket.closed_at = closed_at
        
        await db.commit()
        return True

    @staticmethod
    async def set_user_reply_block(
        db: AsyncSession,
        ticket_id: int,
        permanent: bool,
        until: Optional[datetime]
    ) -> bool:
        ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=False)
        if not ticket:
            return False
        ticket.user_reply_block_permanent = bool(permanent)
        ticket.user_reply_block_until = until
        ticket.updated_at = datetime.utcnow()
        await db.commit()
        return True
    
    @staticmethod
    async def close_ticket(
        db: AsyncSession,
        ticket_id: int
    ) -> bool:
        """Закрыть тикет"""
        return await TicketCRUD.update_ticket_status(
            db, ticket_id, TicketStatus.CLOSED.value, datetime.utcnow()
        )
    
    @staticmethod
    async def get_open_tickets_count(db: AsyncSession) -> int:
        """Получить количество открытых тикетов"""
        query = select(Ticket).where(Ticket.status.in_([
            TicketStatus.OPEN.value,
            TicketStatus.ANSWERED.value
        ]))
        result = await db.execute(query)
        return len(result.scalars().all())


class TicketMessageCRUD:
    """CRUD операции для работы с сообщениями тикетов"""
    
    @staticmethod
    async def add_message(
        db: AsyncSession,
        ticket_id: int,
        user_id: int,
        message_text: str,
        is_from_admin: bool = False,
        media_type: Optional[str] = None,
        media_file_id: Optional[str] = None,
        media_caption: Optional[str] = None
    ) -> TicketMessage:
        """Добавить сообщение в тикет"""
        message = TicketMessage(
            ticket_id=ticket_id,
            user_id=user_id,
            message_text=message_text,
            is_from_admin=is_from_admin,
            has_media=bool(media_type and media_file_id),
            media_type=media_type,
            media_file_id=media_file_id,
            media_caption=media_caption
        )
        
        db.add(message)
        
        # Обновляем статус тикета
        ticket = await TicketCRUD.get_ticket_by_id(db, ticket_id, load_messages=False)
        if ticket:
            # Если тикет закрыт, запрещаем изменение статуса при сообщении пользователя
            if not is_from_admin and ticket.status == TicketStatus.CLOSED.value:
                return message
            if is_from_admin:
                # Админ ответил - тикет отвечен
                ticket.status = TicketStatus.ANSWERED.value
            else:
                # Пользователь ответил - тикет открыт
                ticket.status = TicketStatus.OPEN.value
            
            ticket.updated_at = datetime.utcnow()
        
        await db.commit()
        await db.refresh(message)
        return message
    
    @staticmethod
    async def get_ticket_messages(
        db: AsyncSession,
        ticket_id: int,
        limit: int = 50,
        offset: int = 0
    ) -> List[TicketMessage]:
        """Получить сообщения тикета"""
        query = (
            select(TicketMessage)
            .where(TicketMessage.ticket_id == ticket_id)
            .order_by(TicketMessage.created_at)
            .offset(offset)
            .limit(limit)
        )
        
        result = await db.execute(query)
        return result.scalars().all()
    
    @staticmethod
    async def get_last_message(
        db: AsyncSession,
        ticket_id: int
    ) -> Optional[TicketMessage]:
        """Получить последнее сообщение в тикете"""
        query = (
            select(TicketMessage)
            .where(TicketMessage.ticket_id == ticket_id)
            .order_by(desc(TicketMessage.created_at))
            .limit(1)
        )
        
        result = await db.execute(query)
        return result.scalar_one_or_none()
