from sqlalchemy.orm import Session
from typing import List, Optional, Dict
from app.models.admin_setting import AdminSetting
from app.schemas.admin_setting import AdminSettingCreate, AdminSettingUpdate
import logging
from app.core.config import settings as app_settings
from fastapi import HTTPException, status
from typing import Any
from app.models.order import Order
from app.models.product import Product
logger = logging.getLogger('adimsayar')

# Default settings
DEFAULT_SETTINGS = {
    "STEP_BASE_RATE": "0.01",  # 기본 걸음-포인트 변환 비율 (1 걸음 = 0.01 포인트)
    "STEP_THRESHOLD": "7000",  # 추가 보상 기준 걸음 수
    "STEP_MULTIPLIER": "2.0",  # 기준 이상 걸음 시 승수
    "ACHIEVEMENT_STEP_GOAL": str(app_settings.ACHIEVEMENT_STEP_GOAL),  # 목표 걸음 수
    "ACHIEVEMENT_BONUS": str(app_settings.ACHIEVEMENT_BONUS),  # 목표 달성 보너스
    "REFERRAL_BONUS": str(app_settings.REFERRAL_SIGNUP_BONUS),  # 추천 보너스
    "REFERRAL_DIRECT_BONUS_PERCENTAGE": str(app_settings.REFERRAL_DIRECT_BONUS_PERCENTAGE),  # 직접 추천 보너스 %
    "REFERRAL_CHAIN_DECAY": str(app_settings.REFERRAL_CHAIN_DECAY),  # 추천 체인 감소율
    "REFERRAL_MAX_LEVEL": str(app_settings.REFERRAL_MAX_LEVEL),  # 최대 추천 체인 레벨
}

def get_all_settings(db: Session, country_code: Optional[str] = None) -> List[AdminSetting]:
    """
    모든 관리자 설정을 조회합니다. country_code가 제공되면 해당 국가 설정만 반환합니다.
    """
    query = db.query(AdminSetting)
    if country_code:
        query = query.filter(AdminSetting.country_code == country_code)
    return query.all()

def get_setting(db: Session, setting_name: str, country_code: Optional[str] = None) -> Optional[AdminSetting]:
    """
    특정 설정을 조회합니다. country_code가 제공되면 해당 국가 설정을 우선 조회하고,
    없는 경우 country_code가 None인 기본 설정을 반환합니다.
    """
    # 국가별 설정 우선 조회
    if country_code:
        setting = db.query(AdminSetting).filter(
            AdminSetting.setting_name == setting_name,
            AdminSetting.country_code == country_code
        ).first()
        if setting:
            return setting
    
    # 기본 설정 조회
    return db.query(AdminSetting).filter(
        AdminSetting.setting_name == setting_name,
        AdminSetting.country_code == None
    ).first()

def get_setting_value(db: Session, setting_name: str, country_code: Optional[str] = None) -> str:
    """
    설정 값을 문자열로 반환합니다. 설정이 없는 경우 기본값을 반환합니다.
    """
    setting = get_setting(db, setting_name, country_code)
    if setting and setting.setting_value is not None:
        return setting.setting_value
    
    # 기본값 반환
    return DEFAULT_SETTINGS.get(setting_name, "")

def get_setting_float(db: Session, setting_name: str, country_code: Optional[str] = None) -> float:
    """
    설정 값을 실수로 반환합니다. 변환 실패 시 0.0을 반환합니다.
    """
    value = get_setting_value(db, setting_name, country_code)
    try:
        return float(value)
    except (ValueError, TypeError):
        logger.error(f"Failed to convert setting {setting_name} with value '{value}' to float")
        # 기본값 가져오기 시도
        try:
            return float(DEFAULT_SETTINGS.get(setting_name, "0.0"))
        except (ValueError, TypeError):
            return 0.0

def get_setting_int(db: Session, setting_name: str, country_code: Optional[str] = None) -> int:
    """
    설정 값을 정수로 반환합니다. 변환 실패 시 0을 반환합니다.
    """
    value = get_setting_value(db, setting_name, country_code)
    try:
        return int(float(value))  # 실수 문자열도 처리
    except (ValueError, TypeError):
        logger.error(f"Failed to convert setting {setting_name} with value '{value}' to int")
        # 기본값 가져오기 시도
        try:
            return int(float(DEFAULT_SETTINGS.get(setting_name, "0")))
        except (ValueError, TypeError):
            return 0

def update_setting(
    db: Session, 
    setting_name: str, 
    update_data: AdminSettingUpdate,
    country_code: Optional[str] = None
) -> AdminSetting:
    """
    설정을 업데이트하거나 없는 경우 새로 생성합니다.
    """
    # 기존 설정 조회
    setting = db.query(AdminSetting).filter(
        AdminSetting.setting_name == setting_name,
        AdminSetting.country_code == country_code
    ).first()
    
    if setting:
        # 기존 설정 업데이트
        setting.setting_value = update_data.setting_value
        if update_data.country_code is not None:
            setting.country_code = update_data.country_code
    else:
        # 새 설정 생성
        setting = AdminSetting(
            setting_name=setting_name,
            setting_value=update_data.setting_value,
            country_code=country_code if country_code is not None else update_data.country_code
        )
        db.add(setting)
    
    db.commit()
    db.refresh(setting)
    return setting

def ensure_default_settings(db: Session) -> None:
    """
    애플리케이션 시작 시 기본 설정이 존재하는지 확인하고, 없는 경우 생성합니다.
    """
    for name, value in DEFAULT_SETTINGS.items():
        setting = db.query(AdminSetting).filter(
            AdminSetting.setting_name == name,
            AdminSetting.country_code == None
        ).first()
        
        if not setting:
            setting = AdminSetting(
                setting_name=name,
                setting_value=value,
                country_code=None
            )
            db.add(setting)
    
    db.commit()

def authenticate_admin(db: Session, email: str, password: str) -> Optional[Dict]:
    """
    Admin authentication function.
    Authenticates user credentials and verifies that the user has admin privileges.
    Returns admin user data with tokens if authentication is successful, None otherwise.
    """
    try:
        from app.services.user_service import UserService
        from app.core.security import create_access_token, create_refresh_token
        from app.services.notification_service import NotificationService
        from app.core.time import now
        from app.core.security import pwd_context
        from app.models.user import User
        
        logger.info(f"Attempting admin authentication for user: {email}")
        user = db.query(User).filter(User.email == email).first()
        
        if not user:
            logger.warning(f"User not found with email: {email}")
            return None
            
        if not pwd_context.verify(password, user.password):
            logger.warning(f"Invalid password for admin: {email}")
            return None
            
        if user.status == 0:
            logger.warning(f"Inactive admin attempted to login: {email}")
            return None
            
        # Check if user is an admin
        if not user.is_admin:
            logger.warning(f"Non-admin user attempted admin login: {email}")
            return None
        
        # Update last login time
        user.last_login = now()
        db.add(user)
        db.commit()
        db.refresh(user)

        # Create access and refresh tokens
        access_token = create_access_token(subject=user.id)
        refresh_token = create_refresh_token(subject=user.id)

        # Prepare login response
        response = {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "bearer",
            "user": {
                "id": user.id,
                "email": user.email,
                "first_name": user.first_name,
                "last_name": user.last_name,
                "status": user.status,
                "is_admin": user.is_admin
            }
        }

        return response
        
    except Exception as e:
        logger.error(f"Admin authentication error: {str(e)}", exc_info=True)
        raise 

def get_all_user_points(db: Session) -> List[Dict]:
    """
    모든 사용자의 포인트 정보를 조회합니다.
    
    Args:
        db (Session): 데이터베이스 세션
    
    Returns:
        List[Dict]: 모든 사용자의 포인트 정보 목록
    """
    from app.models.user_points import UserPoints
    from app.models.user import User
    
    # 유저 정보와 포인트 정보를 함께 조회
    results = db.query(
        UserPoints.user_id, 
        UserPoints.total_points, 
        UserPoints.updated_at,
        User.email,
        User.first_name,
        User.last_name
    ).join(
        User, 
        UserPoints.user_id == User.id
    ).all()
    
    # 결과 포맷팅
    user_points_list = []
    for result in results:
        user_points_list.append({
            "user_id": result.user_id,
            "email": result.email,
            "first_name": result.first_name,
            "last_name": result.last_name,
            "total_points": result.total_points,
            "updated_at": result.updated_at
        })
    
    return user_points_list 

def update_user_by_admin(db: Session, user_id: int, user_data: Dict) -> Dict:
    """
    관리자 권한으로 사용자 정보를 업데이트합니다.
    
    Args:
        db (Session): 데이터베이스 세션
        user_id (int): 업데이트할 사용자의 ID
        user_data (Dict): 업데이트할 사용자 정보
        
    Returns:
        Dict: 업데이트된 사용자 정보
        
    Raises:
        HTTPException: 사용자를 찾을 수 없거나 업데이트 중 오류가 발생한 경우
    """
    from app.models.user import User
    
    # 사용자 조회
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {user_id} not found"
        )
    
    try:
        # 사용자 정보 업데이트
        for key, value in user_data.items():
            setattr(user, key, value)
        
        db.add(user)
        db.commit()
        db.refresh(user)
        
        # 응답을 위한 사용자 정보 가공
        user_info = {
            "id": user.id,
            "email": user.email,
            "first_name": user.first_name,
            "last_name": user.last_name,
            "phone_code": user.phone_code,
            "phone": user.phone,
            "gender": user.gender,
            "birth_date": user.birth_date,
            "daily_step_goal": user.daily_step_goal,
            "status": user.status,
            "updated_at": user.updated_at
        }
        
        return user_info
    
    except Exception as e:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to update user: {str(e)}"
        )

def delete_product_category(db: Session, category_id: int) -> Dict[str, Any]:
    """
    관리자 권한으로 상품 카테고리를 삭제합니다.
    
    Args:
        db (Session): 데이터베이스 세션
        category_id (int): 삭제할 상품 카테고리의 ID
        
    Returns:
        Dict: 삭제 성공 메시지와 삭제된 카테고리 ID
        
    Raises:
        HTTPException: 카테고리가 존재하지 않거나 관련 제품이 있어 삭제할 수 없는 경우
    """
    from app.models.product_category import ProductCategory
    from sqlalchemy.exc import IntegrityError
    from sqlalchemy import or_
    
    try:
        # 카테고리 존재 여부 확인
        category = db.query(ProductCategory).filter(ProductCategory.id == category_id).first()
        
        if not category:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Product category with id {category_id} not found"
            )
        
        # 나중에 사용하기 위해 카테고리 코드와 이름 저장
        category_code = category.code
        category_name = category.name
        
        # 카테고리에 속한 제품이 있는지 확인
        products_count = db.query(Product).filter(Product.category_id == category_id).count()
        
        if products_count > 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Cannot delete category with id {category_id} because it has {products_count} products. Remove or reassign products first."
            )
            
        # 카테고리 삭제 전 정보 저장
        category_info = {
            "id": category.id,
            "code": category.code,
            "name": category.name
        }
        
        # 카테고리 삭제
        db.delete(category)
        db.commit()
        
        # 삭제 확인 - 데이터베이스에서 해당 ID로 다시 조회
        deleted_check = db.query(ProductCategory).filter(ProductCategory.id == category_id).first()
        if deleted_check is not None:
            logger.error(f"Category still exists after deletion: ID={category_id}")
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 
                detail="Failed to delete product category. The category still exists after deletion attempt."
            )
        
        # 삭제 후 동일한 코드나 이름의 카테고리가 여전히 존재하는지 확인
        duplicate_check = db.query(ProductCategory).filter(
            or_(
                ProductCategory.code == category_code,
                ProductCategory.name == category_name
            )
        ).first()
        
        if duplicate_check is not None:
            logger.warning(f"Found duplicate category after deletion: ID={duplicate_check.id}, Code={duplicate_check.code}, Name={duplicate_check.name}")
            return {
                "message": "Category with ID {category_id} was deleted, but a similar category still exists",
                "deleted_category": category_info,
                "similar_category": {
                    "id": duplicate_check.id,
                    "code": duplicate_check.code,
                    "name": duplicate_check.name
                },
                "warning": "자동 복제 방지 시스템이 감지되었습니다. 데이터베이스 트리거나 외부 프로세스가 삭제된 카테고리를 다시 생성했을 수 있습니다."
            }
        
        logger.info(f"Successfully deleted product category: ID={category_id}, Name={category_info['name']}")
        
        return {
            "message": "Product category deleted successfully", 
            "deleted_category": category_info
        }
        
    except IntegrityError as e:
        db.rollback()
        logger.error(f"IntegrityError deleting product category: {str(e)}")
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot delete this category due to database constraints: {str(e)}"
        )
        
    except HTTPException:
        # 이미 HTTPException이 발생한 경우는 그대로 전달
        raise
        
    except Exception as e:
        db.rollback()
        logger.error(f"Error deleting product category: {str(e)}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"An unexpected error occurred while deleting the product category: {str(e)}"
        )

def get_all_orders(db: Session, skip: int = 0, limit: int = 100) -> List[Dict]:
    """
    모든 사용자의 주문정보를 조회합니다.
    
    Args:
        db (Session): 데이터베이스 세션
        skip (int): 건너뛸 레코드 수
        limit (int): 조회할 최대 레코드 수
        
    Returns:
        List[Dict]: 모든 주문 정보 목록
    """
    try:
        # 모든 주문 조회 (최신순으로 정렬)
        orders = db.query(Order).order_by(Order.created_at.desc()).offset(skip).limit(limit).all()
        
        if not orders:
            logger.info("No orders found in the database")
            return []
        
        # 주문 정보를 응답 형식으로 변환
        result = []
        for order in orders:
            # 주문 항목 정보 조회
            order_items = []
            for item in order.items:
                # 상품 정보 조회 (이름, 이미지 등)
                product = db.query(Product).filter(Product.id == item.product_id).first()
                product_name = product.name if product else None
                product_image = product.thumbnail if product else None
                
                # 주문 항목 정보 구성
                order_item = {
                    "product_id": item.product_id,
                    "quantity": item.quantity,
                    "id": item.id,
                    "order_id": item.order_id,
                    "points_per_item": item.points_per_item,
                    "total_points": item.total_points,
                    "created_at": item.created_at.isoformat(),
                    "product_name": product_name,
                    "product_image": product_image,
                    "product_price": item.points_per_item
                }
                order_items.append(order_item)
            
            # 주문 정보 구성
            order_info = {
                "id": order.id,
                "user_id": order.user_id,
                "order_number": order.order_number,
                "total_points": order.total_points,
                "status": order.status,
                "address_id": order.address_id,
                "shipping_details": order.shipping_details,
                "save_address": False,  # 기본값
                "address_title": None,  # 기본값
                "items": order_items,
                "created_at": order.created_at.isoformat(),
                "updated_at": order.updated_at.isoformat()
            }
            result.append(order_info)
        
        logger.info(f"Retrieved {len(result)} orders from the database")
        return result
        
    except Exception as e:
        logger.error(f"Error retrieving orders: {str(e)}", exc_info=True)
        raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")

def update_order_status(db: Session, order_id: int, new_status: str) -> Dict[str, Any]:
    """
    주문 상태를 업데이트합니다.
    
    Args:
        db (Session): 데이터베이스 세션
        order_id (int): 업데이트할 주문 ID
        new_status (str): 새로운 주문 상태 (DELIVERED, PROCESSING, PENDING, CANCELLED)
        
    Returns:
        Dict[str, Any]: 업데이트된 주문 정보
        
    Raises:
        HTTPException: 주문이 존재하지 않거나 잘못된 상태 값이 제공된 경우
    """
    try:
        # 유효한 상태 값 검증
        valid_statuses = ["DELIVERED", "PROCESSING", "PENDING", "CANCELLED"]
        new_status = new_status.upper()  # 대소문자 통일을 위해 대문자로 변환
        
        if new_status not in valid_statuses:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Invalid status value. Must be one of: {', '.join(valid_statuses)}"
            )
        
        # 주문 존재 여부 확인
        order = db.query(Order).filter(Order.id == order_id).first()
        if not order:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Order with ID {order_id} not found"
            )
        
        # 주문 상태 업데이트
        old_status = order.status
        order.status = new_status
        db.commit()
        db.refresh(order)
        
        logger.info(f"Order status updated - Order ID: {order_id}, Old status: {old_status}, New status: {new_status}")
        
        # 주문 정보를 응답 형식으로 변환하여 반환
        order_items = []
        for item in order.items:
            # 상품 정보 조회
            product = db.query(Product).filter(Product.id == item.product_id).first()
            product_name = product.name if product else None
            product_image = product.thumbnail if product else None
            
            order_item = {
                "product_id": item.product_id,
                "quantity": item.quantity,
                "id": item.id,
                "order_id": item.order_id,
                "points_per_item": item.points_per_item,
                "total_points": item.total_points,
                "created_at": item.created_at.isoformat(),
                "product_name": product_name,
                "product_image": product_image,
                "product_price": item.points_per_item
            }
            order_items.append(order_item)
        
        # 주문 정보 구성
        order_info = {
            "id": order.id,
            "user_id": order.user_id,
            "order_number": order.order_number,
            "total_points": order.total_points,
            "status": order.status,
            "address_id": order.address_id,
            "shipping_details": order.shipping_details,
            "save_address": False,  # 기본값
            "address_title": None,  # 기본값
            "items": order_items,
            "created_at": order.created_at.isoformat(),
            "updated_at": order.updated_at.isoformat()
        }
        
        return order_info
        
    except HTTPException:
        # 이미 HTTP 예외가 발생한 경우 다시 발생
        raise
    except Exception as e:
        logger.error(f"Error updating order status: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Internal server error: {str(e)}"
        )

def create_advertisement(db: Session, ad_data: Dict[str, Any]) -> Dict[str, Any]:
    """
    새로운 광고를 생성합니다. 중복된 video_list_order 값이 있으면 자동으로 조정합니다.
    
    Args:
        db (Session): 데이터베이스 세션
        ad_data (Dict[str, Any]): 광고 데이터 (ad_url, video_list_order, ad_video_points)
        
    Returns:
        Dict[str, Any]: 생성된 광고 정보
    """
    from app.models.advertisement import Advertisement
    
    try:
        # 입력된 순서 값
        requested_order = ad_data["video_list_order"]
        
        # 1. 같은 순서 값을 가진 기존 광고가 있는지 확인
        existing_ads_with_same_or_higher_order = (
            db.query(Advertisement)
            .filter(Advertisement.video_list_order >= requested_order)
            .order_by(Advertisement.video_list_order.desc())  # 높은 순서부터 처리
            .all()
        )
        
        # 2. 같은 순서 값이 있다면, 그 순서 값 이상의 모든 광고의 순서를 1씩 증가
        if existing_ads_with_same_or_higher_order:
            for existing_ad in existing_ads_with_same_or_higher_order:
                existing_ad.video_list_order += 1
                db.add(existing_ad)
            
            logger.info(f"Adjusted order for {len(existing_ads_with_same_or_higher_order)} existing advertisements")
        
        # 3. 새 광고 생성
        new_ad = Advertisement(
            ad_url=ad_data["ad_url"],
            video_list_order=requested_order,
            ad_video_points=ad_data["ad_video_points"]
        )
        
        db.add(new_ad)
        db.commit()
        db.refresh(new_ad)
        
        # 응답을 위한 광고 정보 가공
        ad_info = {
            "id": new_ad.id,
            "ad_url": new_ad.ad_url,
            "video_list_order": new_ad.video_list_order,
            "ad_video_points": new_ad.ad_video_points,
            "created_at": new_ad.created_at.isoformat()
        }
        
        logger.info(f"Advertisement created successfully - ID: {new_ad.id}, Order: {new_ad.video_list_order}")
        return ad_info
        
    except Exception as e:
        db.rollback()
        logger.error(f"Error creating advertisement: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to create advertisement: {str(e)}"
        )

def delete_advertisement(db: Session, ad_id: int) -> Dict[str, Any]:
    """
    광고를 삭제합니다.
    
    Args:
        db (Session): 데이터베이스 세션
        ad_id (int): 삭제할 광고 ID
        
    Returns:
        Dict[str, Any]: 삭제 결과 메시지
    """
    from app.models.advertisement import Advertisement
    
    try:
        # 광고 존재 여부 확인
        ad = db.query(Advertisement).filter(Advertisement.id == ad_id).first()
        if not ad:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Advertisement with ID {ad_id} not found"
            )
        
        # 삭제 전 광고 정보 저장
        ad_info = {
            "id": ad.id,
            "ad_url": ad.ad_url,
            "video_list_order": ad.video_list_order,
            "ad_video_points": ad.ad_video_points
        }
        
        # 광고 삭제
        db.delete(ad)
        db.commit()
        
        logger.info(f"Advertisement deleted successfully - ID: {ad_id}")
        return {
            "message": "Advertisement deleted successfully",
            "deleted_advertisement": ad_info
        }
        
    except HTTPException:
        # 이미 HTTP 예외가 발생한 경우 다시 발생
        raise
    except Exception as e:
        db.rollback()
        logger.error(f"Error deleting advertisement: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to delete advertisement: {str(e)}"
        )