# src/utils/credit_manager.py
"""
크레딧 관리 유틸리티
- 크레딧 소모 순서: 이벤트 크레딧 → 일일 크레딧 → 월간 크레딧 → 추가 크레딧 → 무료 크레딧
- 크레딧 만료 정책 처리
"""
from datetime import datetime, timedelta, date, timezone
from sqlalchemy import and_
from src.models.db import db
from src.models.credit import CreditBalance, CreditTransaction, CreditType
from src.models.subscription import Subscription, SubscriptionStatus
from src.models.user import User
from src.utils.user_ref import resolve_user_ref
from src.utils.credit_mirror import mirror_balance_and_transactions
import json
import logging
from zoneinfo import ZoneInfo
KST = ZoneInfo("Asia/Seoul")

logger = logging.getLogger(__name__)


def get_or_create_credit_balance(user_id):
    """크레딧 잔액 조회 또는 생성"""
    credit_balance = CreditBalance.query.filter_by(user_id=user_id).first()
    if not credit_balance:
        credit_balance = CreditBalance(
            user_id=user_id,
            total_credit=0,
            free_credit=0,
            event_credit=0,
            daily_renewal_credit=0,
            subscription_credit=0,
            add_on_credit=0
        )
        db.session.add(credit_balance)
        db.session.commit()
        mirror_balance_and_transactions(user_id, balance=credit_balance)
    return credit_balance


def consume_credits(user_id, amount, description, reference_id=None, reference_type=None):
    """
    크레딧 소모 (소모 순서: 이벤트 → 일일 → 월간 → 추가 → 무료)
    
    Args:
        user_id: 사용자 ID
        amount: 소모할 크레딧 양 (양수)
        description: 거래 설명
        reference_id: 참조 ID
        reference_type: 참조 타입
    
    Returns:
        dict: {'success': bool, 'remaining_balance': int, 'error': str}
    """
    try:
        if not amount or amount <= 0:
            return {'success': False, 'remaining_balance': 0, 'error': 'amount는 양수여야 합니다.'}

        # (A) 아이템포턴시: 동일 USAGE 존재하면 재사용 처리
        if reference_id and reference_type:
            dup = CreditTransaction.query.filter_by(
                user_id=user_id,
                credit_type=CreditType.USAGE,
                reference_type=reference_type,
                reference_id=reference_id
            ).first()
            if dup:
                return {
                    'success': True,
                    'remaining_balance': dup.balance_after,
                    'consumed_breakdown': json.loads(dup.meta_data) if dup.meta_data else {}
                }

        credit_balance = get_or_create_credit_balance(user_id)

        if credit_balance.total_credit < amount:
            return {'success': False, 'remaining_balance': credit_balance.total_credit, 'error': '크레딧이 부족합니다.'}

        remaining = amount
        breakdown = {'event':0,'daily_renewal':0,'subscription':0,'add_on':0,'free':0}

        # 1) 이벤트
        use = min(remaining, credit_balance.event_credit)
        credit_balance.event_credit -= use; remaining -= use; breakdown['event'] = use

        # 2) 일일
        if remaining>0:
            use = min(remaining, credit_balance.daily_renewal_credit)
            credit_balance.daily_renewal_credit -= use; remaining -= use; breakdown['daily_renewal'] = use

        # 3) 구독
        if remaining>0:
            use = min(remaining, credit_balance.subscription_credit)
            credit_balance.subscription_credit -= use; remaining -= use; breakdown['subscription'] = use

        # 4) 애드온
        if remaining>0:
            use = min(remaining, credit_balance.add_on_credit)
            credit_balance.add_on_credit -= use; remaining -= use; breakdown['add_on'] = use

        # 5) 무료
        if remaining>0:
            use = min(remaining, credit_balance.free_credit)
            credit_balance.free_credit -= use; remaining -= use; breakdown['free'] = use

        credit_balance.total_credit = (
            credit_balance.free_credit + credit_balance.event_credit + credit_balance.daily_renewal_credit +
            credit_balance.subscription_credit + credit_balance.add_on_credit
        )

        txn = CreditTransaction(
            user_id=user_id,
            credit_type=CreditType.USAGE,
            amount=-amount,
            balance_after=credit_balance.total_credit,
            description=description,
            reference_id=reference_id,
            reference_type=reference_type,
            meta_data=json.dumps(breakdown)
        )
        db.session.add(txn)
        db.session.commit()
        mirror_balance_and_transactions(user_id, balance=credit_balance, transactions=[txn])

        return {'success': True, 'remaining_balance': credit_balance.total_credit, 'consumed_breakdown': breakdown}
    except Exception as e:
        db.session.rollback()
        return {'success': False, 'remaining_balance': 0, 'error': str(e)}


def add_credits(user_id, credit_type, amount, description, reference_id=None, reference_type=None):
    """
    크레딧 추가
    
    Args:
        user_id: 사용자 ID
        credit_type: CreditType enum
        amount: 추가할 크레딧 양 (양수)
        description: 거래 설명
        reference_id: 참조 ID
        reference_type: 참조 타입
    
    Returns:
        dict: {'success': bool, 'new_balance': int, 'error': str}
    """
    try:
        credit_balance = get_or_create_credit_balance(user_id)
        
        # 크레딧 타입에 따라 적절한 필드에 추가
        if credit_type == CreditType.EVENT:
            credit_balance.event_credit += amount
        elif credit_type == CreditType.FREE:
            credit_balance.free_credit += amount
        elif credit_type == CreditType.DAILY_RENEWAL:
            credit_balance.daily_renewal_credit += amount
        elif credit_type in [CreditType.SUBSCRIPTION_BASIC, CreditType.SUBSCRIPTION_PLUS, CreditType.SUBSCRIPTION_PRO]:
            credit_balance.subscription_credit += amount
        elif credit_type == CreditType.ADD_ON:
            credit_balance.add_on_credit += amount
        
        # total_credit을 각 타입별 크레딧의 합계로 자동 계산
        credit_balance.total_credit = (
            credit_balance.free_credit +
            credit_balance.event_credit +
            credit_balance.daily_renewal_credit +
            credit_balance.subscription_credit +
            credit_balance.add_on_credit
        )
        
        # 거래 내역 기록
        transaction = CreditTransaction(
            user_id=user_id,
            credit_type=credit_type,
            amount=amount,
            balance_after=credit_balance.total_credit,
            description=description,
            reference_id=reference_id,
            reference_type=reference_type
        )
        db.session.add(transaction)
        db.session.commit()
        mirror_balance_and_transactions(user_id, balance=credit_balance, transactions=[transaction])

        return {
            'success': True,
            'new_balance': credit_balance.total_credit
        }

    except Exception as e:
        db.session.rollback()
        return {
            'success': False,
            'new_balance': 0,
            'error': str(e)
        }


def renew_daily_credits(user_id):
    """
    일일 크레딧 '재설정' (미사용분 만료 후 =300 설정)
    - 멱등: 같은 KST 날짜에 중복 호출되어도 1회만 반영
    """
    try:
        now_utc = datetime.now(timezone.utc)
        now_kst = now_utc.astimezone(KST)
        today_key = now_kst.strftime("%Y-%m-%d")  # 아이템포턴시 키로 사용

        # 중복 방지: 이미 오늘 DAILY_RENEWAL 기록이 있으면 스킵
        exists = CreditTransaction.query.filter_by(
            user_id=user_id,
            credit_type=CreditType.DAILY_RENEWAL,
            reference_type='daily_refill',
            reference_id=today_key
        ).first()
        if exists:
            return {'success': True, 'added': 0, 'message': '이미 오늘 갱신되었습니다.'}

        # 잠금
        credit_balance = CreditBalance.query.filter_by(user_id=user_id)\
            .with_for_update().first()

        if not credit_balance:
            credit_balance = CreditBalance(
                user_id=user_id,
                total_credit=0, free_credit=0, event_credit=0,
                daily_renewal_credit=0, subscription_credit=0, add_on_credit=0
            )
            db.session.add(credit_balance)
            db.session.flush()

        # (A) 미사용분 만료 기록 (금액 0이면 스킵)
        expired = int(credit_balance.daily_renewal_credit or 0)
        expire_txn = None
        if expired > 0:
            # 만료 처리: daily_renewal_credit을 0으로
            credit_balance.daily_renewal_credit = 0
            credit_balance.total_credit = (
                credit_balance.free_credit + credit_balance.event_credit +
                0 + credit_balance.subscription_credit + credit_balance.add_on_credit
            )
            expire_txn = CreditTransaction(
                user_id=user_id,
                credit_type=CreditType.EXPIRE,   # ← DDL에서 추가한 타입
                amount=-expired,
                balance_after=credit_balance.total_credit,
                description='일일 크레딧 만료',
                reference_type='daily_expire',
                reference_id=today_key
            )
            db.session.add(expire_txn)

        # (B) 재설정 = 300
        daily_amount = 300
        credit_balance.daily_renewal_credit = daily_amount
        credit_balance.total_credit = (
            credit_balance.free_credit + credit_balance.event_credit +
            credit_balance.daily_renewal_credit + credit_balance.subscription_credit +
            credit_balance.add_on_credit
        )
        credit_balance.last_daily_renewal = now_utc  # UTC 저장

        refill_txn = CreditTransaction(
            user_id=user_id,
            credit_type=CreditType.DAILY_RENEWAL,
            amount=daily_amount,
            balance_after=credit_balance.total_credit,
            description='매일 갱신 크레딧(재설정)',
            reference_type='daily_refill',
            reference_id=today_key
        )
        db.session.add(refill_txn)
        db.session.commit()
        txns = [t for t in (expire_txn, refill_txn) if t is not None]
        mirror_balance_and_transactions(user_id, balance=credit_balance, transactions=txns)
        return {'success': True, 'added': daily_amount}
    except Exception as e:
        db.session.rollback()
        return {'success': False, 'added': 0, 'error': str(e)}


def renew_subscription_credits(user_id, subscription_id=None):
    """
    구독 크레딧 갱신 (구독 날짜 기준 매달 자동 갱신)
    
    Args:
        user_id: 사용자 ID
        subscription_id: 구독 ID (None이면 활성 구독 조회)
    
    Returns:
        dict: {'success': bool, 'added': int, 'error': str}
    """
    try:
        # 구독 정보 조회
        if subscription_id:
            subscription = Subscription.query.get(subscription_id)
        else:
            subscription = Subscription.query.filter_by(
                user_id=user_id,
                status=SubscriptionStatus.ACTIVE
            ).order_by(Subscription.created_at.desc()).first()
        
        if not subscription:
            return {
                'success': False,
                'added': 0,
                'error': '활성 구독이 없습니다.'
            }
        
        # 구독 플랜에 따른 크레딧 양 결정
        # product 관계가 로드되지 않았을 수 있으므로 직접 조회
        from src.models.subscription import Product
        product = Product.query.get(subscription.product_id) if subscription.product_id else None
        
        if not product:
            return {
                'success': False,
                'added': 0,
                'error': '구독 상품 정보를 찾을 수 없습니다.'
            }
        
        product_name = product.name.lower()
        if 'basic' in product_name:
            credit_amount = 1900
            credit_type = CreditType.SUBSCRIPTION_BASIC
        elif 'plus' in product_name:
            credit_amount = 3900
            credit_type = CreditType.SUBSCRIPTION_PLUS
        elif 'pro' in product_name:
            credit_amount = 19000
            credit_type = CreditType.SUBSCRIPTION_PRO
        else:
            # 기본값으로 Basic 크레딧 제공
            credit_amount = 1900
            credit_type = CreditType.SUBSCRIPTION_BASIC
        
        # 구독 크레딧 추가
        result = add_credits(
            user_id=user_id,
            credit_type=credit_type,
            amount=credit_amount,
            description=f'{product.name} 구독 크레딧 (월간)',
            reference_id=str(subscription.id),
            reference_type='subscription'
        )
        
        return {
            'success': result['success'],
            'added': credit_amount if result['success'] else 0,
            'error': result.get('error')
        }
        
    except Exception as e:
        db.session.rollback()
        return {
            'success': False,
            'added': 0,
            'error': str(e)
        }


def expire_event_credits(user_id, event_id=None):
    """
    이벤트 크레딧 만료 처리
    
    Args:
        user_id: 사용자 ID
        event_id: 이벤트 ID (None이면 모든 이벤트 크레딧 만료)
    
    Returns:
        dict: {'success': bool, 'expired': int, 'error': str}
    """
    try:
        credit_balance = get_or_create_credit_balance(user_id)
        
        if credit_balance.event_credit <= 0:
            return {
                'success': True,
                'expired': 0,
                'message': '만료할 이벤트 크레딧이 없습니다.'
            }
        
        expired_amount = credit_balance.event_credit
        credit_balance.event_credit = 0
        
        # total_credit을 각 타입별 크레딧의 합계로 자동 계산
        credit_balance.total_credit = (
            credit_balance.free_credit +
            credit_balance.event_credit +
            credit_balance.daily_renewal_credit +
            credit_balance.subscription_credit +
            credit_balance.add_on_credit
        )
        
        # 거래 내역 기록
        transaction = CreditTransaction(
            user_id=user_id,
            credit_type=CreditType.EVENT,
            amount=-expired_amount,
            balance_after=credit_balance.total_credit,
            description=f'이벤트 크레딧 만료',
            reference_id=event_id,
            reference_type='event_expiry'
        )
        db.session.add(transaction)
        db.session.commit()
        mirror_balance_and_transactions(user_id, balance=credit_balance, transactions=[transaction])

        return {
            'success': True,
            'expired': expired_amount
        }

    except Exception as e:
        db.session.rollback()
        return {
            'success': False,
            'expired': 0,
            'error': str(e)
        }


def add_signup_bonus(user_id):
    """
    회원가입 보너스 크레딧 추가 (이벤트 크레딧 +1000, 가입일로부터 30일 이내 사용)
    
    Returns:
        dict: {'success': bool, 'added': int, 'error': str}
    """
    try:
        from src.models.credit import EventCreditLot
        
        # 가입일 조회
        user = resolve_user_ref(user_id)
        if not user:
            return {'success': False, 'added': 0, 'error': '사용자를 찾을 수 없습니다.'}
        
        # 가입일 기준으로 30일 후 만료일 계산 (KST 기준)
        signup_date = user.created_at.date() if hasattr(user.created_at, 'date') else datetime.now().date()
        expires_at = signup_date + timedelta(days=30)
        
        # 이벤트 크레딧 추가
        result = add_credits(
            user_id=user_id,
            credit_type=CreditType.EVENT,
            amount=1000,
            description='회원가입 보너스 (30일 이내 사용)',
            reference_type='signup',
            reference_id='signup_bonus'
        )
        
        if not result.get('success'):
            return result
        
        # event_credit_lots 테이블에 레코드 생성
        event_lot = EventCreditLot(
            user_id=user_id,
            event_id='signup_bonus',
            amount_remaining=1000,
            expires_at=expires_at
        )
        db.session.add(event_lot)
        db.session.commit()
        
        logger.info(f"회원가입 보너스 크레딧 지급 완료: user_id={user_id}, amount=1000, expires_at={expires_at}")
        
        return result
        
    except Exception as e:
        db.session.rollback()
        logger.error(f"회원가입 보너스 크레딧 지급 실패: user_id={user_id}, error={str(e)}")
        return {'success': False, 'added': 0, 'error': str(e)}


def consume_subscription_credits_only(user_id, description='구독 취소로 인한 구독 크레딧 사용 완료', reference_id=None, reference_type='subscription_cancellation'):
    """
    구독 취소 시 구독 크레딧(subscription_credit)만 사용 완료로 처리
    다른 크레딧(이벤트, 일일, 추가, 무료)은 유지
    
    Args:
        user_id: 사용자 ID
        description: 거래 설명
        reference_id: 참조 ID (구독 ID 등)
        reference_type: 참조 타입
    
    Returns:
        dict: {'success': bool, 'consumed': int, 'error': str}
    """
    try:
        credit_balance = get_or_create_credit_balance(user_id)
        
        # 구독 크레딧이 없으면 스킵
        if credit_balance.subscription_credit <= 0:
            return {
                'success': True,
                'consumed': 0,
                'message': '사용할 구독 크레딧이 없습니다.'
            }
        
        consumed_amount = credit_balance.subscription_credit
        
        # 구독 크레딧만 0으로 설정
        credit_balance.subscription_credit = 0
        
        # total_credit을 각 타입별 크레딧의 합계로 자동 계산
        credit_balance.total_credit = (
            credit_balance.free_credit +
            credit_balance.event_credit +
            credit_balance.daily_renewal_credit +
            credit_balance.subscription_credit +
            credit_balance.add_on_credit
        )
        
        # 거래 내역 기록
        transaction = CreditTransaction(
            user_id=user_id,
            credit_type=CreditType.USAGE,
            amount=-consumed_amount,
            balance_after=credit_balance.total_credit,
            description=description,
            reference_id=reference_id,
            reference_type=reference_type,
            meta_data=json.dumps({'subscription': consumed_amount})
        )
        db.session.add(transaction)
        db.session.commit()
        mirror_balance_and_transactions(user_id, balance=credit_balance, transactions=[transaction])

        return {
            'success': True,
            'consumed': consumed_amount,
            'consumed_breakdown': {'subscription': consumed_amount}
        }

    except Exception as e:
        db.session.rollback()
        return {
            'success': False,
            'consumed': 0,
            'error': str(e)
        }


def consume_all_remaining_credits(user_id, description='구독 취소로 인한 남은 크레딧 사용 완료', reference_id=None, reference_type='subscription_cancellation'):
    """
    구독 취소 시 남은 크레딧을 모두 사용 완료로 처리 (deprecated - consume_subscription_credits_only 사용 권장)
    
    Args:
        user_id: 사용자 ID
        description: 거래 설명
        reference_id: 참조 ID (구독 ID 등)
        reference_type: 참조 타입
    
    Returns:
        dict: {'success': bool, 'consumed': int, 'error': str}
    """
    # 구독 크레딧만 소모하도록 변경
    return consume_subscription_credits_only(user_id, description, reference_id, reference_type)

