import json
import logging
import requests
from typing import List, Dict, Optional, Any, Union
from sqlalchemy.orm import Session
from app.models.notification import Notification
from app.services.device_token_service import DeviceTokenService
from app.core.config import settings

logger = logging.getLogger('adimsayar')

class ExpoPushNotificationService:
    """Expo Push 알림 서비스 - FCM과는 별도의 서비스로 동작합니다."""
    
    EXPO_PUSH_API = "https://exp.host/--/api/v2/push/send"
    
    @staticmethod
    def send_push_to_user(
        db: Session, 
        user_id: int, 
        title: str, 
        message: str, 
        data: Dict[str, Any] = None,
        notification_type: str = "general",
        save_to_db: bool = True
    ) -> bool:
        """
        Expo Push Service를 통해 사용자에게 푸시 알림을 보내고, 선택적으로 DB에 알림을 저장합니다.
        NotificationService에서 호출될 때는 이미 DB에 저장되었을 수 있으므로 save_to_db 파라미터로 제어합니다.
        """
        try:
            # 사용자의 디바이스 토큰 조회
            device_tokens = DeviceTokenService.get_user_device_tokens(db, user_id)
            
            if not device_tokens:
                logger.info(f"사용자 ID {user_id}에 대한 활성화된 디바이스 토큰이 없습니다.")
                # 디바이스 토큰이 없고 DB 저장이 필요한 경우에만 저장
                if save_to_db:
                    ExpoPushNotificationService._save_notification_to_db(
                        db, user_id, title, message, data, notification_type
                    )
                return True
            
            # Expo 토큰 필터링 (토큰이 "ExponentPushToken["로 시작하는 것만 선택)
            expo_tokens = [
                token.device_token for token in device_tokens 
                if token.device_token.startswith("ExponentPushToken[")
            ]
            
            if not expo_tokens:
                logger.info(f"사용자 ID {user_id}에 대한 Expo 디바이스 토큰이 없습니다.")
                # Expo 토큰이 없고 DB 저장이 필요한 경우에만 저장
                if save_to_db:
                    ExpoPushNotificationService._save_notification_to_db(
                        db, user_id, title, message, data, notification_type
                    )
                return True
            
            # 푸시 알림 전송
            success = ExpoPushNotificationService._send_expo_push(expo_tokens, title, message, data)
            
            # DB에 알림 저장 (필요한 경우에만)
            if save_to_db:
                ExpoPushNotificationService._save_notification_to_db(
                    db, user_id, title, message, data, notification_type
                )
            
            return success
        
        except Exception as e:
            logger.error(f"Expo 푸시 알림 전송 오류: {str(e)}")
            return False
    
    @staticmethod
    def _send_expo_push(
        tokens: List[str], 
        title: str, 
        message: str, 
        data: Dict[str, Any] = None
    ) -> bool:
        """Expo Push Service API를 통해 푸시 알림을 전송합니다."""
        try:
            if not tokens:
                logger.warning("푸시 알림을 보낼 Expo 토큰이 없습니다.")
                return False
            
            # 헤더 설정
            headers = {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'Accept-Encoding': 'gzip, deflate'
            }
            
            # 각 토큰에 대해 메시지 구성
            messages = []
            for token in tokens:
                message_obj = {
                    "to": token,
                    "title": title,
                    "body": message,
                    "sound": "default",
                }
                
                if data:
                    message_obj["data"] = data
                
                messages.append(message_obj)
            
            # 성공한 전송 수를 추적
            success_count = 0
            
            # 요청 보내기 (최대 100개의 메시지를 한 번에 보낼 수 있음)
            for i in range(0, len(messages), 100):
                batch = messages[i:i+100]
                response = requests.post(
                    ExpoPushNotificationService.EXPO_PUSH_API, 
                    headers=headers, 
                    json=batch
                )
                
                if response.status_code == 200:
                    response_data = response.json()
                    if 'data' in response_data:
                        # 각 티켓의 상태 확인
                        for idx, ticket in enumerate(response_data['data']):
                            if ticket.get('status') == 'ok':
                                success_count += 1
                                logger.info(f"Expo 푸시 알림 티켓 발행 성공: {tokens[i+idx][:20]}...")
                            else:
                                logger.warning(f"Expo 푸시 알림 티켓 발행 실패: {tokens[i+idx][:20]}... - {json.dumps(ticket)}")
                else:
                    logger.warning(f"Expo 푸시 알림 API 응답 오류: {response.status_code} - {response.text}")
            
            logger.info(f"Expo 푸시 알림 전송 결과: 성공 {success_count}/{len(tokens)}")
            return success_count > 0

        except Exception as e:
            logger.error(f"Expo 푸시 알림 전송 중 오류 발생: {str(e)}")
            return False
    
    @staticmethod
    def _save_notification_to_db(
        db: Session, 
        user_id: int, 
        title: str, 
        message: str, 
        data: Dict[str, Any] = None,
        notification_type: str = "general"
    ) -> Notification:
        """
        알림을 데이터베이스에 저장합니다.
        """
        try:
            # 직접 Notification 모델을 사용하여 알림 생성 (순환 참조 방지)
            notification = Notification(
                user_id=user_id,
                type=notification_type,
                title=title,
                message=message,
                data=data
            )
            
            # DB에 추가 및 커밋
            db.add(notification)
            db.commit()
            db.refresh(notification)
            
            logger.info(f"사용자 ID {user_id}에 대한 알림이 DB에 저장되었습니다. (ID: {notification.id})")
            return notification
            
        except Exception as e:
            db.rollback()
            logger.error(f"알림 DB 저장 오류: {str(e)}")
            raise