# src/tasks.py
"""
Celery 태스크 정의
정기구독 자동 갱신 및 결제 처리
"""
import os
import uuid
import requests
import logging
from datetime import datetime, timedelta, date
from calendar import monthrange
from zoneinfo import ZoneInfo
from src.celery_app import celery
from src.models.db import db
from flask import Flask

# Flask 앱 인스턴스 생성 (태스크 실행 시 사용)
def get_app():
    """Flask 앱 인스턴스 반환"""
    from src.main import app
    return app
from src.models.user import User
from src.models.subscription import Subscription, SubscriptionStatus, Product, PaymentStatus, PaymentMethod
from src.models.payment import PaymentHistory
from src.utils.email_service import send_payment_link_email
from src.routes.payments_v2 import TOSS_CONFIG, get_front_url, get_back_url

logger = logging.getLogger(__name__)
KST = ZoneInfo("Asia/Seoul")

def _add_one_month_same_day(base_date: date, start_day: int) -> date:
    """base_date 기준으로 +1개월, 시작일의 '일(day)' 유지. 말일 보정."""
    if base_date.month == 12:
        y, m = base_date.year + 1, 1
    else:
        y, m = base_date.year, base_date.month + 1
    
    last_day = monthrange(y, m)[1]
    keep_day = min(start_day, last_day)
    return date(y, m, keep_day)

@celery.task(name='src.tasks.check_subscription_renewals')
def check_subscription_renewals():
    """
    정기구독 갱신 체크 태스크
    next_billing_date가 오늘인 구독을 찾아 결제 링크 생성 및 이메일 발송
    """
    app = get_app()
    with app.app_context():
        try:
            now_kst = datetime.now(KST)
            today = now_kst.date()
            
            # 갱신이 필요한 구독 조회 (next_billing_date가 오늘인 활성 구독)
            subscriptions = Subscription.query.filter(
                Subscription.status == SubscriptionStatus.ACTIVE,
                Subscription.auto_renew == True,
                Subscription.next_billing_date.isnot(None),
                db.func.date(Subscription.next_billing_date) == today
            ).all()
            
            logger.info(f"정기구독 갱신 체크: {len(subscriptions)}개 구독 발견")
            
            for subscription in subscriptions:
                try:
                    # 각 구독에 대해 결제 링크 생성 및 이메일 발송 태스크 실행
                    create_payment_link_and_send_email.delay(subscription.id)
                except Exception as e:
                    logger.error(f"구독 {subscription.id} 갱신 처리 실패: {str(e)}")
            
            return {
                'success': True,
                'checked': len(subscriptions),
                'timestamp': now_kst.isoformat()
            }
            
        except Exception as e:
            logger.error(f"정기구독 갱신 체크 실패: {str(e)}")
            return {
                'success': False,
                'error': str(e)
            }

@celery.task(name='src.tasks.create_payment_link_and_send_email')
def create_payment_link_and_send_email(subscription_id):
    """
    결제 링크 생성 및 이메일 발송
    
    Args:
        subscription_id: 구독 ID
    """
    app = get_app()
    with app.app_context():
        try:
            subscription = Subscription.query.get(subscription_id)
            if not subscription:
                logger.error(f"구독을 찾을 수 없습니다: {subscription_id}")
                return {'success': False, 'error': '구독을 찾을 수 없습니다.'}
            
            if subscription.status != SubscriptionStatus.ACTIVE:
                logger.warning(f"구독이 활성 상태가 아닙니다: {subscription_id}, 상태: {subscription.status}")
                return {'success': False, 'error': '구독이 활성 상태가 아닙니다.'}
            
            # 사용자 정보 조회
            user = User.query.get(18)   # 테스트용 사용자 ID
            # user = User.query.get(subscription.user_id)
            if not user:
                logger.error(f"사용자를 찾을 수 없습니다: {subscription.user_id}")
                return {'success': False, 'error': '사용자를 찾을 수 없습니다.'}
            
            # 상품 정보 조회
            product = Product.query.get(subscription.product_id)
            if not product:
                logger.error(f"상품을 찾을 수 없습니다: {subscription.product_id}")
                return {'success': False, 'error': '상품을 찾을 수 없습니다.'}
            
            # 결제 금액 결정
            billing_cycle = subscription.billing_cycle or product.billing_cycle or 'monthly'
            if billing_cycle == 'yearly':
                amount = product.price_year if hasattr(product, 'price_year') else product.price * 12
            else:
                amount = product.price
            
            # 주문 ID 생성
            order_id = f"mlink_renew_{subscription.id}_{uuid.uuid4().hex[:8]}_{int(datetime.now().timestamp())}"
            
            # 토스페이먼츠 결제 링크 생성
            payment_result = create_toss_payment_link(
                order_id=order_id,
                amount=amount,
                product_name=product.name,
                billing_cycle=billing_cycle,
                user_id=subscription.user_id,
                subscription_id=subscription.id
            )
            
            if not payment_result or not payment_result.get('payment_link'):
                logger.error(f"결제 링크 생성 실패: {subscription_id}")
                return {'success': False, 'error': '결제 링크 생성 실패'}
            
            payment_link = payment_result['payment_link']
            pay_token = payment_result.get('pay_token', '')
            
            # PaymentHistory 생성 (PENDING 상태)
            payment_history = PaymentHistory(
                user_id=subscription.user_id,
                subscription_id=subscription.id,
                payment_key=pay_token,  # payToken 저장
                order_id=order_id,
                amount=amount,
                status=PaymentStatus.PENDING,
                payment_method=PaymentMethod.TOSS_PAYMENTS,
                product_id=product.id,
                product_name=product.name,
                product_price=amount,
                billing_cycle=billing_cycle,
                created_at=datetime.now()
            )
            db.session.add(payment_history)
            db.session.commit()
            
            # 이메일 발송
            billing_date = subscription.next_billing_date.strftime('%Y년 %m월 %d일') if subscription.next_billing_date else '오늘'
            email_sent = send_payment_link_email(
                user_email=user.email,
                user_name=user.username,
                subscription_name=product.name,
                payment_link=payment_link,
                billing_date=billing_date,
                amount=amount
            )
            
            if email_sent:
                logger.info(f"결제 링크 이메일 발송 완료: 구독 {subscription_id}, 사용자 {user.email}")
            else:
                logger.warning(f"결제 링크 이메일 발송 실패: 구독 {subscription_id}, 사용자 {user.email}")
            
            return {
                'success': True,
                'subscription_id': subscription_id,
                'order_id': order_id,
                'payment_link': payment_link,
                'email_sent': email_sent
            }
            
        except Exception as e:
            db.session.rollback()
            logger.error(f"결제 링크 생성 및 이메일 발송 실패: {str(e)}")
            return {
                'success': False,
                'error': str(e)
            }

def create_toss_payment_link(order_id, amount, product_name, billing_cycle, user_id, subscription_id):
    """
    토스페이먼츠 결제 링크 생성
    
    Args:
        order_id: 주문 ID
        amount: 결제 금액
        product_name: 상품명
        billing_cycle: 결제 주기
        user_id: 사용자 ID
        subscription_id: 구독 ID
    
    Returns:
        dict: {'payment_link': str, 'pay_token': str} (실패 시 None)
    """
    try:
        # 세금 계산
        amount_taxable = int(amount / 1.1)
        amount_vat = amount - amount_taxable
        
        # 결제 링크 만료 시간 계산 (현재 시간 + 60분, 최대 60분까지 가능)
        expiration_datetime = datetime.now() + timedelta(minutes=60)
        expired_time_str = expiration_datetime.strftime('%Y-%m-%d %H:%M:%S')
        
        payment_data = {
            'orderNo': order_id,
            'amount': amount,
            'amountTaxFree': 0,
            'amountTaxable': amount_taxable,
            'amountVat': amount_vat,
            'productDesc': f'{product_name} 추가 구매',
            'apiKey': TOSS_CONFIG['api_key'],
            'autoExecute': True,
            # 'resultCallback': f'{get_back_url()}/api/v2/payments/toss/callback',
            'resultCallback': 'https://pay.toss.im/payfront/demo/callback',  # 토스페이먼츠 데모 콜백 사용
            'callbackVersion': 'V2',
            'retUrl': f'{get_front_url()}/payment/success?orderId={order_id}&subscriptionId={subscription_id}',
            'retCancelUrl': f'{get_front_url()}/payment/fail?orderId={order_id}&subscriptionId={subscription_id}',
            'expiredTime': expired_time_str  # 결제 만료 예정 시각 (60분 후, 최대 60분) - 형식: "YYYY-MM-DD HH:MM:SS"
        }
        
        # 토스페이먼츠 API 호출
        response = requests.post(
            TOSS_CONFIG['base_url'],
            headers={'Content-Type': 'application/json'},
            json=payment_data,
            timeout=10
        )
        
        if response.status_code == 200:
            result = response.json()
            pay_token = result.get('payToken')
            checkout_page = result.get('checkoutPage', '')
            
            if pay_token:
                # 토스페이먼츠 API 응답에서 제공하는 checkoutPage 사용
                if checkout_page:
                    payment_link = checkout_page
                else:
                    # checkoutPage가 없는 경우에만 직접 생성 (fallback)
                    payment_link = f'https://pay.toss.im/transfer-web/linkgen-mobile?payToken={pay_token}'
                    logger.warning(f"토스페이먼츠 응답에 checkoutPage가 없어 직접 생성: {order_id}")
                
                logger.info(f"토스페이먼츠 결제 링크 생성 성공: {order_id}, payment_link: {payment_link}")
                return {
                    'payment_link': payment_link,
                    'pay_token': pay_token
                }
            else:
                logger.error(f"토스페이먼츠 응답에 payToken이 없습니다: {result}")
                return None
        else:
            logger.error(f"토스페이먼츠 API 호출 실패: {response.status_code} - {response.text}")
            return None
            
    except Exception as e:
        logger.error(f"토스페이먼츠 결제 링크 생성 중 오류: {str(e)}")
        return None

@celery.task(name='src.tasks.cleanup_expired_subscriptions')
def cleanup_expired_subscriptions():
    """
    만료된 구독 정리 태스크
    end_date가 지난 활성 구독을 만료 상태로 변경
    """
    app = get_app()
    with app.app_context():
        try:
            now = datetime.now()
            
            # 만료된 구독 조회
            expired_subscriptions = Subscription.query.filter(
                Subscription.status == SubscriptionStatus.ACTIVE,
                Subscription.end_date < now
            ).all()
            
            count = 0
            for subscription in expired_subscriptions:
                subscription.status = SubscriptionStatus.EXPIRED
                subscription.auto_renew = False
                count += 1
            
            db.session.commit()
            
            logger.info(f"만료된 구독 정리 완료: {count}개")
            
            return {
                'success': True,
                'cleaned': count,
                'timestamp': now.isoformat()
            }
            
        except Exception as e:
            db.session.rollback()
            logger.error(f"만료된 구독 정리 실패: {str(e)}")
            return {
                'success': False,
                'error': str(e)
            }

