import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
from app.channels.base import NotificationChannel, DeliveryResult
from app.core.config import get_settings
from app.services.telegram_sender import choose_telegram_sender

settings = get_settings()


class TelegramAdapter(NotificationChannel):
    """Telegram Bot API adapter with retry logic for rate limits"""
    
    @property
    def channel_name(self) -> str:
        return "telegram"
    
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10)
    )
    async def send(self, target: str, content: str, **kwargs) -> DeliveryResult:
        """
        Send message via Telegram Bot API или Client API (MTProto)
        
        Args:
            target: chat_id (customer.tg_chat_id) или username (@username)
            content: message text
            kwargs: 
                - tenant_id (required)
                - parse_mode (default: Markdown)
                - disable_web_page_preview
                - use_mtproto (bool): принудительно использовать MTProto
        
        Тип отправки определяется из настроек тенанта (TenantSettings.tg_sender):
        - "system" или "brand" - использует Bot API
        - "mtproto" - использует MTProto (аккаунт из TenantSettings.mtproto_account_id)
        - "disabled" - отправка отключена
        
        Автоматически выбирает правильный MTProto аккаунт на основе номера телефона клиента.
        Если номер клиента совпадает с номером MTProto аккаунта, используется этот аккаунт.
        """
        tenant_id = kwargs.get("tenant_id")
        if not tenant_id:
            return DeliveryResult(
                success=False,
                error="tenant_id is required for Telegram sending"
            )
        
        use_mtproto = kwargs.get("use_mtproto", False)
        
        # Проверяем настройки тенанта - какой тип отправки использовать
        from app.db.session import SessionLocal
        from app.db.models import TenantSettings
        db_settings = SessionLocal()
        try:
            tenant_settings = db_settings.query(TenantSettings).filter(
                TenantSettings.tenant_id == tenant_id
            ).first()
            
            # Если в настройках указано использовать MTProto
            if tenant_settings and tenant_settings.tg_sender == "mtproto":
                use_mtproto = True
        finally:
            db_settings.close()
        
        # Если явно запрошен MTProto, в настройках MTProto, или target это username, пробуем MTProto сначала
        if use_mtproto or target.startswith('@'):
            from app.services.telegram_mtproto import (
                send_message_via_mtproto, 
                is_mtproto_available,
                get_default_mtproto_account,
                get_mtproto_account_by_phone
            )
            from app.db.session import SessionLocal
            
            # Получаем аккаунт для тенанта из настроек
            db = SessionLocal()
            try:
                from app.db.models import TenantSettings
                
                # Получаем настройки тенанта для определения аккаунта
                tenant_settings = db.query(TenantSettings).filter(
                    TenantSettings.tenant_id == tenant_id
                ).first()
                
                account_id = None
                # Если в настройках указан конкретный MTProto аккаунт, используем его
                if tenant_settings and tenant_settings.mtproto_account_id:
                    account_id = tenant_settings.mtproto_account_id
                else:
                    # Иначе используем аккаунт по умолчанию
                    account_id = await get_default_mtproto_account(tenant_id, db)
                
                if account_id and await is_mtproto_available(account_id=account_id, db=db):
                    success, error = await send_message_via_mtproto(
                        recipient=target,
                        message=content,
                        parse_mode=kwargs.get("parse_mode", "Markdown"),
                        account_id=account_id,
                        db=db
                    )
                elif await is_mtproto_available():  # Fallback на legacy из env
                    success, error = await send_message_via_mtproto(
                        recipient=target,
                        message=content,
                        parse_mode=kwargs.get("parse_mode", "Markdown")
                    )
                else:
                    success, error = False, "MTProto не настроен"
                
                if success:
                    return DeliveryResult(
                        success=True,
                        message_id="mtproto",  # MTProto не возвращает message_id напрямую
                        response_meta={"method": "mtproto"}
                    )
                # Если MTProto не сработал, пробуем Bot API как fallback (если это не username)
                if target.startswith('@'):
                    # Если username, но MTProto не доступен, возвращаем ошибку
                    return DeliveryResult(
                        success=False,
                        error=error or "Отправка по username (@username) требует настройки Telegram Client API (MTProto). Проверьте настройки."
                    )
                # Продолжаем с Bot API ниже для не-username
            finally:
                db.close()
        
        # Выбираем подходящий бот (системный или бренд-бот)
        bot_token = await choose_telegram_sender(tenant_id)
        if not bot_token:
            # Если нет бота, пробуем MTProto как последний шанс
            from app.services.telegram_mtproto import (
                send_message_via_mtproto, 
                is_mtproto_available,
                get_default_mtproto_account
            )
            from app.db.session import SessionLocal
            from app.db.models import TenantSettings
            
            db_fallback = SessionLocal()
            try:
                # Проверяем настройки для выбора аккаунта
                tenant_settings = db_fallback.query(TenantSettings).filter(
                    TenantSettings.tenant_id == tenant_id
                ).first()
                
                account_id = None
                if tenant_settings and tenant_settings.mtproto_account_id:
                    account_id = tenant_settings.mtproto_account_id
                else:
                    account_id = await get_default_mtproto_account(tenant_id, db_fallback)
                
                if account_id and await is_mtproto_available(account_id=account_id, db=db_fallback):
                    success, error = await send_message_via_mtproto(
                        recipient=target,
                        message=content,
                        parse_mode=kwargs.get("parse_mode", "Markdown"),
                        account_id=account_id,
                        db=db_fallback
                    )
                elif await is_mtproto_available():
                    success, error = await send_message_via_mtproto(
                        recipient=target,
                        message=content,
                        parse_mode=kwargs.get("parse_mode", "Markdown")
                    )
                else:
                    success, error = False, None
                
                if success:
                    return DeliveryResult(
                        success=True,
                        message_id="mtproto",
                        response_meta={"method": "mtproto"}
                    )
            finally:
                db_fallback.close()
                
            return DeliveryResult(
                success=False,
                error="No Telegram bot configured for this tenant"
            )
        
        url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
        payload = {
            "chat_id": target,
            "text": content,
            "parse_mode": kwargs.get("parse_mode", "Markdown"),
            "disable_web_page_preview": kwargs.get("disable_web_page_preview", False),
        }
        
        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.post(url, json=payload)
                
                if response.status_code == 429:
                    # Rate limit - will be retried by tenacity
                    retry_after = int(response.json().get("parameters", {}).get("retry_after", 5))
                    raise httpx.HTTPStatusError(
                        f"Rate limited, retry after {retry_after}s",
                        request=response.request,
                        response=response
                    )
                
                response.raise_for_status()
                data = response.json()
                
                if data.get("ok"):
                    return DeliveryResult(
                        success=True,
                        message_id=str(data["result"]["message_id"]),
                        response_meta=data["result"]
                    )
                else:
                    # Обработка специальных ошибок
                    error_code = data.get("error_code")
                    error_description = data.get("description", "Unknown error")
                    
                    # Если пользователь заблокировал бота или чат не найден, пробуем MTProto как fallback
                    if error_code in [403, 400] and ("blocked" in error_description.lower() or "chat not found" in error_description.lower()):
                        # Пробуем MTProto если доступен
                        from app.services.telegram_mtproto import (
                            send_message_via_mtproto, 
                            is_mtproto_available,
                            get_default_mtproto_account
                        )
                        from app.db.session import SessionLocal
                        from app.db.models import TenantSettings
                        
                        db_fallback = SessionLocal()
                        try:
                            # Проверяем настройки для выбора аккаунта
                            tenant_settings = db_fallback.query(TenantSettings).filter(
                                TenantSettings.tenant_id == tenant_id
                            ).first()
                            
                            account_id = None
                            if tenant_settings and tenant_settings.mtproto_account_id:
                                account_id = tenant_settings.mtproto_account_id
                            else:
                                account_id = await get_default_mtproto_account(tenant_id, db_fallback)
                            
                            if account_id and await is_mtproto_available(account_id=account_id, db=db_fallback):
                                success, error = await send_message_via_mtproto(
                                    recipient=target,
                                    message=content,
                                    parse_mode=kwargs.get("parse_mode", "Markdown"),
                                    account_id=account_id,
                                    db=db_fallback
                                )
                            elif await is_mtproto_available():
                                success, error = await send_message_via_mtproto(
                                    recipient=target,
                                    message=content,
                                    parse_mode=kwargs.get("parse_mode", "Markdown")
                                )
                            else:
                                success, error = False, None
                        finally:
                            db_fallback.close()
                            
                        if success:
                            return DeliveryResult(
                                success=True,
                                message_id="mtproto",
                                response_meta={"method": "mtproto_fallback"}
                            )
                        
                        # Устанавливаем opt_out для пользователя
                        await self._mark_user_opt_out(tenant_id, target)
                        
                        return DeliveryResult(
                            success=False,
                            error=f"User blocked bot or chat not found: {error_description}",
                            opt_out=True
                        )
                    
                    return DeliveryResult(
                        success=False,
                        error=error_description
                    )
        
        except httpx.HTTPError as e:
            # При HTTP ошибке пробуем MTProto как fallback
            from app.services.telegram_mtproto import (
                send_message_via_mtproto, 
                is_mtproto_available,
                get_default_mtproto_account
            )
            from app.db.session import SessionLocal
            from app.db.models import TenantSettings
            
            db_fallback = SessionLocal()
            try:
                # Проверяем настройки для выбора аккаунта
                tenant_settings = db_fallback.query(TenantSettings).filter(
                    TenantSettings.tenant_id == tenant_id
                ).first()
                
                account_id = None
                if tenant_settings and tenant_settings.mtproto_account_id:
                    account_id = tenant_settings.mtproto_account_id
                else:
                    account_id = await get_default_mtproto_account(tenant_id, db_fallback)
                
                if account_id and await is_mtproto_available(account_id=account_id, db=db_fallback):
                    success, error = await send_message_via_mtproto(
                        recipient=target,
                        message=content,
                        parse_mode=kwargs.get("parse_mode", "Markdown"),
                        account_id=account_id,
                        db=db_fallback
                    )
                elif await is_mtproto_available():
                    success, error = await send_message_via_mtproto(
                        recipient=target,
                        message=content,
                        parse_mode=kwargs.get("parse_mode", "Markdown")
                    )
                else:
                    success, error = False, None
            finally:
                db_fallback.close()
                
            if success:
                if success:
                    return DeliveryResult(
                        success=True,
                        message_id="mtproto",
                        response_meta={"method": "mtproto_fallback"}
                    )
            
            return DeliveryResult(
                success=False,
                error=f"HTTP error: {str(e)}"
            )
        except Exception as e:
            return DeliveryResult(
                success=False,
                error=f"Unexpected error: {str(e)}"
            )
    
    async def _mark_user_opt_out(self, tenant_id: int, chat_id: str):
        """Пометить пользователя как opt_out"""
        try:
            from app.db.session import SessionLocal
            from app.db.models import Customer
            
            db = SessionLocal()
            try:
                # Находим пользователя по tg_chat_id
                customer = db.query(Customer).filter(
                    Customer.tenant_id == tenant_id,
                    Customer.tg_chat_id == chat_id
                ).first()
                
                if customer:
                    customer.opt_out = True
                    db.commit()
            finally:
                db.close()
        except Exception:
            # Игнорируем ошибки при установке opt_out
            pass












