import json
import logging
import requests
from typing import List, Dict, Optional, Any, Union, Tuple
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
from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession
from app.models.device_token import DeviceToken
from app.core.expo_push import send_push_notification as send_expo_push
from app.core.firebase_config import send_firebase_message, send_firebase_multicast, initialize_firebase
import traceback

logger = logging.getLogger('adimsayar')

class PushNotificationService:
    """
    통합 푸시 알림 서비스
    Firebase Cloud Messaging과 Expo Push Notifications를 모두 지원합니다.
    """
    
    @staticmethod
    def _get_access_token():
        """Firebase 서비스 계정으로 액세스 토큰을 생성합니다."""
        try:
            credentials = service_account.Credentials.from_service_account_info(
                {
                    "type": "service_account",
                    "project_id": settings.FIREBASE_PROJECT_ID,
                    "private_key": settings.FIREBASE_PRIVATE_KEY,
                    "client_email": settings.FIREBASE_CLIENT_EMAIL,
                    "token_uri": "https://oauth2.googleapis.com/token",
                },
                scopes=['https://www.googleapis.com/auth/firebase.messaging']
            )
            return credentials.token
        except Exception as e:
            logger.error(f"Error getting Firebase access token: {str(e)}")
            logger.error(traceback.format_exc())
            return None

    @staticmethod
    async 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:
        """
        사용자에게 푸시 알림을 전송합니다. FCM과 Expo 모두 지원합니다.
        
        Args:
            db: 데이터베이스 세션
            user_id: 사용자 ID
            title: 알림 제목
            message: 알림 내용
            data: 추가 데이터
            notification_type: 알림 유형
            save_to_db: 알림을 데이터베이스에 저장할지 여부
            
        Returns:
            성공 여부
        """
        try:
            # Firebase 초기화 확인
            initialize_firebase()
            
            # 알림을 DB에 저장
            notification_id = None
            if save_to_db:
                notification = Notification(
                    user_id=user_id,
                    type=notification_type,
                    title=title,
                    message=message,
                    data=data
                )
                db.add(notification)
                db.commit()
                db.refresh(notification)
                logger.info(f"Notification saved to DB with ID: {notification.id}")
                notification_id = notification.id
                
            # 사용자의 활성 디바이스 토큰 조회
            device_tokens = db.query(DeviceToken).filter(
                DeviceToken.user_id == user_id,
                DeviceToken.is_active == True
            ).all()
            
            if not device_tokens:
                logger.info(f"No active device tokens found for user {user_id}")
                return True  # 토큰이 없어도 성공으로 간주 (DB 저장은 완료됨)
            
            # Firebase FCM 토큰과 Expo 토큰을 분리
            fcm_tokens = []
            expo_tokens = []
            
            for token in device_tokens:
                # 유효하지 않은 토큰 필터링
                if not token.device_token or len(token.device_token) < 10:
                    logger.warning(f"Invalid token found for user {user_id}: {token.device_token}")
                    continue
                
                if token.device_token.startswith('ExponentPushToken['):
                    expo_tokens.append(token.device_token)
                else:
                    fcm_tokens.append(token.device_token)
            
            # 결과 추적
            success_count = 0
            total_tokens = len(fcm_tokens) + len(expo_tokens)
            
            if total_tokens == 0:
                logger.warning(f"No valid tokens found for user {user_id} after filtering")
                return True  # 유효한 토큰이 없어도 성공으로 간주
            
            # 데이터에 기본 필드 추가 (badge, sound 등)
            enhanced_data = data.copy() if data else {}
            
            # notification_id 추가 (저장된 경우)
            if notification_id:
                enhanced_data["notification_id"] = notification_id
            
            # 필수 필드가 없는 경우 추가
            if "badge" not in enhanced_data:
                enhanced_data["badge"] = 1
            if "sound" not in enhanced_data:
                enhanced_data["sound"] = "default"
            if "type" not in enhanced_data:
                enhanced_data["type"] = notification_type
            
            # 모든 값을 문자열로 변환 (FCM 요구사항)
            string_data = {}
            for key, value in enhanced_data.items():
                string_data[str(key)] = str(value)
                
            # Firebase FCM으로 알림 전송
            if fcm_tokens:
                logger.info(f"Sending Firebase push to {len(fcm_tokens)} tokens for user {user_id}")
                try:
                    success, failure = send_firebase_multicast(
                        tokens=fcm_tokens,
                        title=title,
                        body=message,
                        data=string_data,
                        notification_type=notification_type
                    )
                    success_count += success
                    logger.info(f"Firebase push result: {success} success, {failure} failure")
                except Exception as fcm_error:
                    logger.error(f"Error sending Firebase push: {str(fcm_error)}")
                    logger.error(traceback.format_exc())
            
            # Expo로 알림 전송
            if expo_tokens:
                logger.info(f"Sending Expo push to {len(expo_tokens)} tokens for user {user_id}")
                try:
                    result = await send_expo_push(
                        tokens=expo_tokens,
                        title=title,
                        body=message,
                        data=string_data
                    )
                    if result:
                        # Expo 결과 확인
                        for ticket in result.get('data', []):
                            if ticket.get('status') == 'ok':
                                success_count += 1
                except Exception as e:
                    logger.error(f"Error sending Expo push: {str(e)}")
                    logger.error(traceback.format_exc())
            
            success_rate = success_count / total_tokens if total_tokens > 0 else 0
            logger.info(f"Push notification summary for user {user_id}: {success_count}/{total_tokens} successful ({success_rate:.0%})")
            
            # 하나라도 성공했거나 토큰이 없는 경우 성공 간주
            return success_count > 0 or not (fcm_tokens or expo_tokens)
            
        except Exception as e:
            logger.error(f"Error in send_push_to_user: {str(e)}")
            logger.error(traceback.format_exc())
            if save_to_db:
                db.rollback()
            return False
    
    @staticmethod
    async def send_push_to_tokens(
        tokens: List[str],
        title: str,
        message: str,
        data: Dict[str, Any] = None,
        notification_type: str = "general"
    ) -> bool:
        """
        지정된 토큰 목록에 푸시 알림을 전송합니다.
        
        Args:
            tokens: 디바이스 토큰 목록
            title: 알림 제목
            message: 알림 내용
            data: 추가 데이터
            notification_type: 알림 유형
            
        Returns:
            성공 여부
        """
        if not tokens:
            return True
        
        try:
            # Firebase FCM 토큰과 Expo 토큰을 분리
            fcm_tokens = []
            expo_tokens = []
            
            for token in tokens:
                if token.startswith('ExponentPushToken['):
                    expo_tokens.append(token)
                else:
                    fcm_tokens.append(token)
            
            # 결과 추적
            success_count = 0
            
            # Firebase FCM으로 알림 전송
            if fcm_tokens:
                logger.info(f"Sending Firebase push to {len(fcm_tokens)} tokens")
                success, failure = send_firebase_multicast(
                    tokens=fcm_tokens,
                    title=title,
                    body=message,
                    data=data,
                    notification_type=notification_type
                )
                success_count += success
                logger.info(f"Firebase push result: {success} success, {failure} failure")
            
            # Expo로 알림 전송
            if expo_tokens:
                logger.info(f"Sending Expo push to {len(expo_tokens)} tokens")
                try:
                    result = await send_expo_push(
                        tokens=expo_tokens,
                        title=title,
                        body=message,
                        data=data or {}
                    )
                    if result:
                        # Expo 결과 확인
                        for ticket in result.get('data', []):
                            if ticket.get('status') == 'ok':
                                success_count += 1
                except Exception as e:
                    logger.error(f"Error sending Expo push: {str(e)}")
            
            return success_count > 0 or not (fcm_tokens or expo_tokens)
            
        except Exception as e:
            logger.error(f"Error in send_push_to_tokens: {str(e)}")
            return False

    @staticmethod
    def _send_firebase_push(
        tokens: List[str], 
        title: str, 
        message: str, 
        data: Dict[str, Any] = None
    ) -> bool:
        """FCM HTTP v1 API를 사용하여 푸시 알림을 전송합니다."""
        try:
            if not tokens:
                logger.warning("푸시 알림을 보낼 토큰이 없습니다.")
                return False

            # 액세스 토큰 획득
            access_token = PushNotificationService._get_access_token()
            
            # API 엔드포인트
            project_id = settings.FIREBASE_PROJECT_ID
            url = f"https://fcm.googleapis.com/v1/projects/{project_id}/messages:send"
            
            # 헤더 설정
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json',
            }

            # 성공한 전송 수를 추적
            success_count = 0

            # 각 토큰에 대해 개별적으로 메시지 전송
            for token in tokens:
                payload = {
                    "message": {
                        "token": token,
                        "notification": {
                            "title": title,
                            "message": message
                        },
                        "data": data or {},
                        "android": {
                            "priority": "high",
                            "notification": {
                                "sound": "default"
                            }
                        },
                        "apns": {
                            "payload": {
                                "aps": {
                                    "sound": "default"
                                }
                            }
                        }
                    }
                }

                response = requests.post(url, headers=headers, json=payload)
                
                if response.status_code == 200:
                    success_count += 1
                    logger.info(f"푸시 알림 전송 성공: {token[:10]}...")
                else:
                    logger.warning(f"푸시 알림 전송 실패: {token[:10]}... - {response.text}")

            logger.info(f"푸시 알림 전송 결과: 성공 {success_count}/{len(tokens)}")
            return success_count > 0

        except Exception as e:
            logger.error(f"Firebase 푸시 알림 전송 중 오류 발생: {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:
            # 기존 NotificationService.create_notification 활용
            from app.services.notification_service import NotificationService
            
            notification = NotificationService.create_notification(
                db=db,
                user_id=user_id,
                notif_type=notification_type,
                title=title,
                message=message,
                data=data
            )
            
            # 커밋
            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 

    @staticmethod
    async def send_campaign_to_all_users(
        db: Session,
        title: str,
        message: str,
        data: Dict[str, Any] = None,
        notification_type: str = "campaign",
        batch_size: int = 500
    ) -> Tuple[int, int]:
        """
        모든 사용자에게 캠페인 푸시 알림을 전송합니다.
        
        Args:
            db: 데이터베이스 세션
            title: 알림 제목
            message: 알림 내용
            data: 추가 데이터
            notification_type: 알림 유형
            batch_size: 한 번에 처리할 토큰 수 (최대 500)
            
        Returns:
            (성공한 사용자 수, 전체 사용자 수) 튜플
        """
        try:
            # 모든 활성 디바이스 토큰 가져오기
            device_tokens = db.query(DeviceToken).filter(
                DeviceToken.is_active == True
            ).all()
            
            if not device_tokens:
                logger.info("No active device tokens found for sending campaign")
                return 0, 0
            
            # 사용자 ID별로 토큰 그룹화 (같은 사용자가 여러 디바이스를 가질 수 있음)
            user_tokens = {}
            for token in device_tokens:
                if token.user_id not in user_tokens:
                    user_tokens[token.user_id] = []
                user_tokens[token.user_id].append(token.device_token)
            
            logger.info(f"Sending campaign to {len(user_tokens)} users with {len(device_tokens)} total tokens")
            
            # Firebase FCM 토큰과 Expo 토큰을 분리
            fcm_tokens = []
            expo_tokens = []
            
            for token in device_tokens:
                if token.device_token.startswith('ExponentPushToken['):
                    expo_tokens.append(token.device_token)
                else:
                    fcm_tokens.append(token.device_token)
            
            # 결과 추적
            success_count = 0
            total_users = len(user_tokens)
            
            # 1. 먼저 각 사용자에 대해 알림을 DB에 저장
            for user_id in user_tokens:
                try:
                    notification = Notification(
                        user_id=user_id,
                        type=notification_type,
                        title=title,
                        message=message,
                        data=data
                    )
                    db.add(notification)
                except Exception as e:
                    logger.error(f"Error saving notification for user {user_id}: {str(e)}")
            
            # DB 트랜잭션 커밋
            db.commit()
            logger.info(f"Saved campaign notifications to DB for {total_users} users")
            
            # 2. Firebase FCM으로 알림 전송 (배치 처리)
            if fcm_tokens:
                total_fcm_tokens = len(fcm_tokens)
                logger.info(f"Sending Firebase push to {total_fcm_tokens} FCM tokens")
                
                # 배치 처리 (최대 500개씩)
                for i in range(0, total_fcm_tokens, batch_size):
                    batch = fcm_tokens[i:i+batch_size]
                    success, failure = send_firebase_multicast(
                        tokens=batch,
                        title=title,
                        body=message,
                        data=data,
                        notification_type=notification_type
                    )
                    success_count += success
                    logger.info(f"Firebase push batch result: {success}/{len(batch)} success")
            
            # 3. Expo로 알림 전송 (배치 처리)
            if expo_tokens:
                total_expo_tokens = len(expo_tokens)
                logger.info(f"Sending Expo push to {total_expo_tokens} Expo tokens")
                
                # 배치 처리 (최대 100개씩)
                expo_batch_size = 100  # Expo 권장 배치 크기
                for i in range(0, total_expo_tokens, expo_batch_size):
                    expo_batch = expo_tokens[i:i+expo_batch_size]
                    try:
                        result = await send_expo_push(
                            tokens=expo_batch,
                            title=title,
                            body=message,
                            data=data or {}
                        )
                        if result and 'data' in result:
                            # Expo 결과 확인
                            batch_success = sum(1 for ticket in result['data'] if ticket.get('status') == 'ok')
                            success_count += batch_success
                            logger.info(f"Expo push batch result: {batch_success}/{len(expo_batch)} success")
                    except Exception as e:
                        logger.error(f"Error sending Expo push batch: {str(e)}")
            
            logger.info(f"Campaign push notification results: {success_count} successful deliveries to {total_users} users")
            return success_count, total_users
            
        except Exception as e:
            logger.error(f"Error in send_campaign_to_all_users: {str(e)}")
            db.rollback()
            return 0, 0 

    @staticmethod
    async def verify_firebase_setup():
        """
        Firebase 설정이 올바르게 구성되었는지 확인합니다.
        
        Returns:
            (성공 여부, 오류 메시지)
        """
        try:
            # 1. Firebase 초기화 확인
            init_success = initialize_firebase()
            if not init_success:
                return False, "Firebase initialization failed"
            
            # 2. Access Token 획득 테스트
            token = PushNotificationService._get_access_token()
            if not token:
                return False, "Failed to obtain Firebase access token"
            
            # 3. 서비스 계정 파일 존재 확인
            import os
            firebase_paths = [
                'app/firebase-service-account.json',
                '/app/app/firebase-service-account.json',
                '/var/www/adimsayar.easerver.net/htdocs/backend/app/firebase-service-account.json',
                'firebase-service-account.json'
            ]
            
            found_path = None
            for path in firebase_paths:
                if os.path.exists(path):
                    found_path = path
                    break
            
            if not found_path:
                if all([settings.FIREBASE_PROJECT_ID, settings.FIREBASE_CLIENT_EMAIL, settings.FIREBASE_PRIVATE_KEY]):
                    return True, "Using Firebase credentials from environment variables"
                else:
                    return False, "No Firebase service account file found and incomplete environment variables"
            
            return True, f"Firebase setup verified. Using service account from {found_path}"
        
        except Exception as e:
            logger.error(f"Error verifying Firebase setup: {str(e)}")
            logger.error(traceback.format_exc())
            return False, f"Error: {str(e)}" 