import logging
from datetime import datetime, timedelta
from sqlalchemy import select, func, desc
from sqlalchemy.exc import IntegrityError
from .models import MessageLog, MessageTotals, MessageCounter, SentPhones
from .engine import async_session


async def get_all_messages() -> list[MessageLog]:
    """Получить все записи из таблицы message_log."""
    async with async_session() as session:
        result = await session.execute(select(MessageLog))
        return result.scalars().all()


async def add_message(name: str, link: str, is_sent: bool = False) -> MessageLog:
    """Добавить новую запись в таблицу message_log."""
    async with async_session() as session:
        async with session.begin():
            message = MessageLog(name=name, link=link, is_sent=is_sent)
            session.add(message)
        logging.info(f"Добавлено сообщение: name={name}, link={link}, is_sent={is_sent}")
        return message


async def add_sms_log(ad_name: str, link: str, phones: list, is_sent: bool = False) -> MessageLog:
    """
    Добавить запись о отправке SMS для объявления.
    
    Args:
        ad_name: Название объявления
        link: Ссылка на объявление  
        phones: Список телефонов
        is_sent: Успешно ли отправлено SMS
    """
    phone_text = ", ".join(phones) if phones else "Нет телефонов"
    message_name = f"{ad_name} | Телефоны: {phone_text}"
    
    async with async_session() as session:
        async with session.begin():
            message = MessageLog(name=message_name, link=link, is_sent=is_sent)
            session.add(message)
        
        logging.info(f"SMS лог: name={ad_name}, phones={len(phones)}, is_sent={is_sent}")
        return message


# ================== ФУНКЦИИ ДЛЯ ОТСЛЕЖИВАНИЯ ОТПРАВЛЕННЫХ НОМЕРОВ ==================

def _is_within_resend_cooldown(sent_at: datetime | None, resend_after_days: int) -> bool:
    """
    True = повторную SMS слать нельзя.
    resend_after_days=0 — никогда не повторять (если была успешная отправка).
    """
    if resend_after_days <= 0:
        return True
    if sent_at is None:
        return False
    return sent_at >= datetime.utcnow() - timedelta(days=resend_after_days)


async def is_phone_sent(phone_number: str, resend_after_days: int = 0) -> bool:
    """
    Проверить, нельзя ли сейчас отправлять SMS на номер (с учётом cooldown).
    
    Args:
        phone_number: Номер телефона в международном формате
        resend_after_days: 0 = никогда повторно; N = блок если успех < N дней назад
        
    Returns:
        bool: True если SMS отправлять сейчас нельзя
    """
    async with async_session() as session:
        result = await session.execute(
            select(SentPhones)
            .where(SentPhones.phone_number == phone_number)
            .where(SentPhones.is_successful == True)
        )
        sent_phone = result.scalar_one_or_none()
        if sent_phone is None:
            return False
        return _is_within_resend_cooldown(sent_phone.sent_at, resend_after_days)


async def get_sent_phones() -> list[str]:
    """
    Получить список всех номеров, на которые уже отправлялись SMS.
    
    Returns:
        list[str]: Список номеров телефонов
    """
    async with async_session() as session:
        result = await session.execute(
            select(SentPhones.phone_number)
            .where(SentPhones.is_successful == True)
        )
        return [row[0] for row in result.fetchall()]


async def try_reserve_phone_for_sms(
    phone_number: str,
    ad_id: str = None,
    ad_name: str = None,
    resend_after_days: int = 0,
) -> bool:
    """
    Атомарно резервирует номер в sent_phones до вызова шлюза SMS.

    resend_after_days:
      0  — не отправлять повторно, если запись уже есть (успешная);
      N>0 — разрешить повтор, если прошло >= N дней с последней успешной отправки
            (или если прошлой успешной отправки не было).
    """
    resend_after_days = max(0, int(resend_after_days or 0))

    async with async_session() as session:
        async with session.begin():
            result = await session.execute(
                select(SentPhones).where(SentPhones.phone_number == phone_number)
            )
            existing = result.scalar_one_or_none()

            if existing is None:
                session.add(
                    SentPhones(
                        phone_number=phone_number,
                        ad_id=ad_id,
                        ad_name=ad_name,
                        is_successful=False,
                        sent_at=datetime.utcnow(),
                    )
                )
                logging.info("Номер %s зарезервирован под отправку SMS", phone_number)
                return True

            # Уже есть успешная отправка — проверяем cooldown
            if existing.is_successful:
                if _is_within_resend_cooldown(existing.sent_at, resend_after_days):
                    if resend_after_days <= 0:
                        logging.info(
                            "Номер %s уже в sent_phones — повтор запрещён (SMS_RESEND_AFTER_DAYS=0)",
                            phone_number,
                        )
                    else:
                        logging.info(
                            "Номер %s уже получал SMS %s — пропуск (cooldown %s дн.)",
                            phone_number,
                            existing.sent_at,
                            resend_after_days,
                        )
                    return False

                # Cooldown истёк — разрешаем повтор: обновляем ту же уникальную запись
                logging.info(
                    "Номер %s: cooldown %s дн. истёк (последняя успешная: %s) — разрешаем повтор",
                    phone_number,
                    resend_after_days,
                    existing.sent_at,
                )
            else:
                # Предыдущая попытка неуспешна / зависший резерв — можно пробовать снова
                logging.info(
                    "Номер %s был зарезервирован без успеха — повторная попытка",
                    phone_number,
                )

            existing.is_successful = False
            existing.sent_at = datetime.utcnow()
            if ad_id:
                existing.ad_id = ad_id
            if ad_name:
                existing.ad_name = ad_name

    logging.info("Номер %s зарезервирован под отправку SMS", phone_number)
    return True


async def mark_phone_sent(phone_number: str, ad_id: str = None, ad_name: str = None, is_successful: bool = True) -> SentPhones:
    """
    Отметить номер как отправленный.
    
    Args:
        phone_number: Номер телефона в международном формате
        ad_id: ID объявления
        ad_name: Название объявления
        is_successful: Успешно ли отправлено
        
    Returns:
        SentPhones: Созданная запись
    """
    async with async_session() as session:
        async with session.begin():
            # Проверяем, есть ли уже запись для этого номера
            existing = await session.execute(
                select(SentPhones)
                .where(SentPhones.phone_number == phone_number)
            )
            sent_phone = existing.scalar_one_or_none()
            
            if sent_phone:
                if sent_phone.is_successful and not is_successful:
                    logging.warning(
                        "Не понижаю is_successful для %s (уже зафиксирована успешная отправка)",
                        phone_number,
                    )
                else:
                    sent_phone.is_successful = is_successful
                sent_phone.sent_at = datetime.utcnow()
                if ad_id:
                    sent_phone.ad_id = ad_id
                if ad_name:
                    sent_phone.ad_name = ad_name
            else:
                # Создаем новую запись
                sent_phone = SentPhones(
                    phone_number=phone_number,
                    ad_id=ad_id,
                    ad_name=ad_name,
                    is_successful=is_successful
                )
                session.add(sent_phone)
        
        logging.info(f"Номер {phone_number} отмечен как {'отправленный' if is_successful else 'неотправленный'}")
        return sent_phone


async def filter_new_phones(phones: list[str], resend_after_days: int = 0) -> list[str]:
    """
    Отфильтровать номера, на которые сейчас можно отправлять SMS (с учётом cooldown).
    
    Args:
        phones: Список номеров телефонов
        resend_after_days: 0 = никогда повторно; N = можно, если успех был >= N дней назад
        
    Returns:
        list[str]: Список номеров, доступных для отправки
    """
    if not phones:
        return []

    resend_after_days = max(0, int(resend_after_days or 0))
    
    async with async_session() as session:
        result = await session.execute(
            select(SentPhones)
            .where(SentPhones.phone_number.in_(phones))
            .where(SentPhones.is_successful == True)
        )
        blocked = set()
        for row in result.scalars().all():
            if _is_within_resend_cooldown(row.sent_at, resend_after_days):
                blocked.add(row.phone_number)

        new_phones = [phone for phone in phones if phone not in blocked]

        logging.info(
            "Из %s номеров %s доступны для отправки (SMS_RESEND_AFTER_DAYS=%s)",
            len(phones),
            len(new_phones),
            resend_after_days,
        )
        if phones and not new_phones:
            logging.warning(
                "Все номера отфильтрованы cooldown/антидубликатом: %s",
                list(phones),
            )
        return new_phones


async def get_sent_phones_count() -> int:
    """
    Получить количество уникальных номеров, на которые отправлялись SMS.
    
    Returns:
        int: Количество уникальных номеров
    """
    async with async_session() as session:
        result = await session.scalar(
            select(func.count(func.distinct(SentPhones.phone_number)))
            .where(SentPhones.is_successful == True)
        )
        return result or 0


def _today_start_naive() -> datetime:
    """Начало «сегодня» в TZ бота, переведённое в UTC-naive (как MessageLog.created_at)."""
    try:
        from datetime import timezone

        from app.schedule_utils import now_in_bot_tz

        local = now_in_bot_tz()
        start_local = local.replace(hour=0, minute=0, second=0, microsecond=0)
        if start_local.tzinfo is not None:
            return start_local.astimezone(timezone.utc).replace(tzinfo=None)
        return start_local
    except Exception:
        today = datetime.utcnow().date()
        return datetime.combine(today, datetime.min.time())


async def count_today_sent_sms() -> int:
    """Количество успешных SMS за сегодня (для дневного лимита)."""
    today_start = _today_start_naive()
    async with async_session() as session:
        return (
            await session.scalar(
                select(func.count(MessageLog.id))
                .where(MessageLog.is_sent == True)
                .where(MessageLog.created_at >= today_start)
            )
            or 0
        )


async def get_sms_statistics() -> dict:
    """
    Получить статистику SMS за сегодня и общую.

    Returns:
        dict: Статистика отправки SMS
    """
    async with async_session() as session:
        # Статистика за сегодня
        today_start = _today_start_naive()

        # Подсчитываем SMS за сегодня
        today_sent = await session.scalar(
            select(func.count(MessageLog.id))
            .where(MessageLog.is_sent == True)
            .where(MessageLog.created_at >= today_start)
        ) or 0

        today_failed = await session.scalar(
            select(func.count(MessageLog.id))
            .where(MessageLog.is_sent == False)
            .where(MessageLog.created_at >= today_start)
        ) or 0
        
        # Общая статистика
        total_sent = await session.scalar(
            select(func.count(MessageLog.id))
            .where(MessageLog.is_sent == True)
        ) or 0
        
        total_failed = await session.scalar(
            select(func.count(MessageLog.id))
            .where(MessageLog.is_sent == False)
        ) or 0
        
        # Статистика из MessageTotals (если используется)
        totals_result = await session.execute(select(MessageTotals).limit(1))
        totals = totals_result.scalars().first()
        
        # Текущие локальные счетчики
        counter_result = await session.execute(select(MessageCounter).limit(1))
        counter = counter_result.scalars().first()
        
        # Статистика уникальных номеров
        unique_phones_count = await get_sent_phones_count()
        
        return {
            'today': {
                'sent': today_sent,
                'failed': today_failed,
                'total': today_sent + today_failed
            },
            'all_time': {
                'sent': total_sent,
                'failed': total_failed,
                'total': total_sent + total_failed
            },
            'global_totals': {
                'sent': totals.sent_total if totals else 0,
                'failed': totals.failed_total if totals else 0
            },
            'current_session': {
                'sent': counter.successful if counter else 0,
                'failed': counter.failed if counter else 0
            },
            'unique_phones': unique_phones_count
        }


async def get_last_successful_sms_time() -> datetime | None:
    """Возвращает время последней успешно отправленной SMS (sent_at из sent_phones)."""
    async with async_session() as session:
        result = await session.execute(
            select(SentPhones.sent_at)
            .where(SentPhones.is_successful == True)
            .order_by(desc(SentPhones.sent_at))
            .limit(1)
        )
        row = result.scalar_one_or_none()
        return row


async def get_recent_sms_logs(limit: int = 10) -> list[MessageLog]:
    """
    Получить последние записи SMS логов.
    
    Args:
        limit: Количество записей для возврата
        
    Returns:
        list[MessageLog]: Последние SMS логи
    """
    async with async_session() as session:
        result = await session.execute(
            select(MessageLog)
            .order_by(desc(MessageLog.created_at))
            .limit(limit)
        )
        return result.scalars().all()
