#!/usr/bin/env python
"""
データベースのパス情報をS3形式に更新するスクリプト

物理ファイルが存在しない場合、データベースのパス情報のみをS3形式に変換
既存のローカルパス形式から、仮定のS3 URLパターンに変換します。

使用方法:
    python update_image_paths_to_s3.py

機能:
- GoalImagesのパスをS3形式に変換
- CheckinImagesのパスをS3形式に変換
- 既存のパス構造を保持しながらS3 URL形式に変換
- バックアップとロールバック機能付き
"""

import os
import sys
import django
from pathlib import Path
import json
from datetime import datetime

# Django settings setup
BASE_DIR = Path(__file__).resolve().parent
sys.path.append(str(BASE_DIR))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
django.setup()

from django.conf import settings
from rog.models import GoalImages, CheckinImages
from django.db import transaction
import logging
import re

# ロギング設定
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(f'path_update_log_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class PathUpdateService:
    """パス更新サービス"""
    
    def __init__(self):
        self.s3_bucket = settings.AWS_STORAGE_BUCKET_NAME
        self.s3_region = settings.AWS_S3_REGION_NAME
        self.s3_base_url = f"https://{self.s3_bucket}.s3.{self.s3_region}.amazonaws.com"
        
        self.update_stats = {
            'total_goal_images': 0,
            'updated_goal_images': 0,
            'total_checkin_images': 0,
            'updated_checkin_images': 0,
            'skipped_already_s3': 0,
            'failed_updates': [],
            'backup_file': f'path_update_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
        }
        
    def create_backup(self):
        """現在のパス情報をバックアップ"""
        logger.info("データベースパス情報をバックアップ中...")
        
        backup_data = {
            'backup_timestamp': datetime.now().isoformat(),
            'goal_images': [],
            'checkin_images': []
        }
        
        # GoalImages のバックアップ
        for goal_img in GoalImages.objects.filter(goalimage__isnull=False).exclude(goalimage=''):
            backup_data['goal_images'].append({
                'id': goal_img.id,
                'original_path': str(goal_img.goalimage),
                'event_code': goal_img.event_code,
                'team_name': goal_img.team_name,
                'cp_number': goal_img.cp_number
            })
            
        # CheckinImages のバックアップ
        for checkin_img in CheckinImages.objects.filter(checkinimage__isnull=False).exclude(checkinimage=''):
            backup_data['checkin_images'].append({
                'id': checkin_img.id,
                'original_path': str(checkin_img.checkinimage),
                'event_code': checkin_img.event_code,
                'team_name': checkin_img.team_name,
                'cp_number': checkin_img.cp_number
            })
        
        with open(self.update_stats['backup_file'], 'w', encoding='utf-8') as f:
            json.dump(backup_data, f, ensure_ascii=False, indent=2)
            
        logger.info(f"バックアップ完了: {self.update_stats['backup_file']}")
        logger.info(f"  GoalImages: {len(backup_data['goal_images'])}件")
        logger.info(f"  CheckinImages: {len(backup_data['checkin_images'])}件")
        
        return backup_data
    
    def convert_local_path_to_s3_url(self, local_path, event_code, team_name, image_type='checkin'):
        """
        ローカルパスをS3 URLに変換（100文字制限対応）
        
        例:
        goals/230205/2269a407-3745-44fc-977d-f0f22bda112f.jpg
        -> s3://{bucket}/各務原/goals/{team_name}/{filename}
        
        checkin/230205/09d76ced-aa87-41ee-9467-5fd30eb836d0.jpg
        -> s3://{bucket}/各務原/{team_name}/{filename}
        """
        try:
            # ファイル名を抽出
            filename = os.path.basename(local_path)
            
            if image_type == 'goal' or local_path.startswith('goals/'):
                # ゴール画像: s3://{bucket}/{event_code}/goals/{team_name}/{filename}
                s3_path = f"s3://{self.s3_bucket}/{event_code}/goals/{team_name}/{filename}"
            else:
                # チェックイン画像: s3://{bucket}/{event_code}/{team_name}/{filename}
                s3_path = f"s3://{self.s3_bucket}/{event_code}/{team_name}/{filename}"
            
            # 100文字制限チェック
            if len(s3_path) > 100:
                # 短縮版: ファイル名のみを使用
                if image_type == 'goal' or local_path.startswith('goals/'):
                    s3_path = f"s3://{self.s3_bucket}/goals/{filename}"
                else:
                    s3_path = f"s3://{self.s3_bucket}/checkin/{filename}"
                
                # それでも長い場合はファイル名だけ
                if len(s3_path) > 100:
                    s3_path = f"s3://{self.s3_bucket}/{filename}"
                    
                logger.warning(f"長いパスを短縮: {local_path} -> {s3_path}")
            
            return s3_path
            
        except Exception as e:
            logger.error(f"パス変換エラー: {local_path} -> {str(e)}")
            return None
    
    def is_already_s3_url(self, path):
        """既にS3 URLかどうかを判定"""
        return (
            path and (
                path.startswith('https://') or 
                path.startswith('http://') or
                path.startswith('s3://') or
                's3' in path.lower() or
                'amazonaws' in path.lower()
            )
        )
    
    def update_goal_images(self):
        """GoalImagesのパスを更新"""
        logger.info("=== GoalImagesのパス更新開始 ===")
        
        goal_images = GoalImages.objects.filter(goalimage__isnull=False).exclude(goalimage='')
        self.update_stats['total_goal_images'] = goal_images.count()
        
        logger.info(f"更新対象GoalImages: {self.update_stats['total_goal_images']}件")
        
        updated_count = 0
        skipped_count = 0
        
        # トランザクションを個別に処理
        for goal_img in goal_images:
            try:
                with transaction.atomic():
                    original_path = str(goal_img.goalimage)
                    
                    # 既にS3 URLの場合はスキップ
                    if self.is_already_s3_url(original_path):
                        skipped_count += 1
                        continue
                    
                    # S3 URLに変換
                    s3_url = self.convert_local_path_to_s3_url(
                        original_path, 
                        goal_img.event_code, 
                        goal_img.team_name, 
                        'goal'
                    )
                    
                    if s3_url and len(s3_url) <= 100:  # 100文字制限チェック
                        goal_img.goalimage = s3_url
                        goal_img.save()
                        updated_count += 1
                        
                        if updated_count <= 5:  # 最初の5件のみログ出力
                            logger.info(f"✅ GoalImage ID={goal_img.id}: {original_path} -> {s3_url}")
                    else:
                        self.update_stats['failed_updates'].append({
                            'type': 'goal',
                            'id': goal_img.id,
                            'original_path': original_path,
                            'reason': f'Path too long or conversion failed: {s3_url}'
                        })
                        
            except Exception as e:
                logger.error(f"❌ GoalImage ID={goal_img.id} 更新エラー: {str(e)}")
                self.update_stats['failed_updates'].append({
                    'type': 'goal',
                    'id': goal_img.id,
                    'original_path': str(goal_img.goalimage),
                    'reason': str(e)
                })
        
        self.update_stats['updated_goal_images'] = updated_count
        self.update_stats['skipped_already_s3'] += skipped_count
        
        logger.info(f"GoalImages更新完了: {updated_count}件更新、{skipped_count}件スキップ")
    
    def update_checkin_images(self):
        """CheckinImagesのパスを更新"""
        logger.info("=== CheckinImagesのパス更新開始 ===")
        
        checkin_images = CheckinImages.objects.filter(checkinimage__isnull=False).exclude(checkinimage='')
        self.update_stats['total_checkin_images'] = checkin_images.count()
        
        logger.info(f"更新対象CheckinImages: {self.update_stats['total_checkin_images']}件")
        
        updated_count = 0
        skipped_count = 0
        
        # トランザクションを個別に処理
        for checkin_img in checkin_images:
            try:
                with transaction.atomic():
                    original_path = str(checkin_img.checkinimage)
                    
                    # 既にS3 URLの場合はスキップ
                    if self.is_already_s3_url(original_path):
                        skipped_count += 1
                        continue
                    
                    # S3 URLに変換
                    s3_url = self.convert_local_path_to_s3_url(
                        original_path, 
                        checkin_img.event_code, 
                        checkin_img.team_name, 
                        'checkin'
                    )
                    
                    if s3_url and len(s3_url) <= 100:  # 100文字制限チェック
                        checkin_img.checkinimage = s3_url
                        checkin_img.save()
                        updated_count += 1
                        
                        if updated_count <= 5:  # 最初の5件のみログ出力
                            logger.info(f"✅ CheckinImage ID={checkin_img.id}: {original_path} -> {s3_url}")
                    else:
                        self.update_stats['failed_updates'].append({
                            'type': 'checkin',
                            'id': checkin_img.id,
                            'original_path': original_path,
                            'reason': f'Path too long or conversion failed: {s3_url}'
                        })
                        
            except Exception as e:
                logger.error(f"❌ CheckinImage ID={checkin_img.id} 更新エラー: {str(e)}")
                self.update_stats['failed_updates'].append({
                    'type': 'checkin',
                    'id': checkin_img.id,
                    'original_path': str(checkin_img.checkinimage),
                    'reason': str(e)
                })
        
        self.update_stats['updated_checkin_images'] = updated_count
        self.update_stats['skipped_already_s3'] += skipped_count
        
        logger.info(f"CheckinImages更新完了: {updated_count}件更新、{skipped_count}件スキップ")
    
    def generate_update_report(self):
        """更新レポートを生成"""
        logger.info("=== 更新レポート生成 ===")
        
        total_updated = self.update_stats['updated_goal_images'] + self.update_stats['updated_checkin_images']
        total_processed = self.update_stats['total_goal_images'] + self.update_stats['total_checkin_images']
        
        report = {
            'update_timestamp': datetime.now().isoformat(),
            'summary': {
                'total_processed': total_processed,
                'total_updated': total_updated,
                'goal_images_updated': self.update_stats['updated_goal_images'],
                'checkin_images_updated': self.update_stats['updated_checkin_images'],
                'skipped_already_s3': self.update_stats['skipped_already_s3'],
                'failed_updates': len(self.update_stats['failed_updates']),
                'success_rate': (total_updated / max(total_processed, 1) * 100)
            },
            'failed_updates': self.update_stats['failed_updates'],
            'backup_file': self.update_stats['backup_file']
        }
        
        # レポートファイルの保存
        report_file = f'path_update_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
        with open(report_file, 'w', encoding='utf-8') as f:
            json.dump(report, f, ensure_ascii=False, indent=2)
        
        # コンソール出力
        print("\n" + "="*60)
        print("🎯 データベースパス更新レポート")
        print("="*60)
        print(f"📊 処理総数: {total_processed:,}件")
        print(f"✅ 更新成功: {total_updated:,}件 ({report['summary']['success_rate']:.1f}%)")
        print(f"   - ゴール画像: {self.update_stats['updated_goal_images']:,}件")
        print(f"   - チェックイン画像: {self.update_stats['updated_checkin_images']:,}件")
        print(f"⏩ スキップ（既にS3）: {self.update_stats['skipped_already_s3']:,}件")
        print(f"❌ 失敗: {len(self.update_stats['failed_updates'])}件")
        print(f"📄 詳細レポート: {report_file}")
        print(f"💾 バックアップファイル: {self.update_stats['backup_file']}")
        
        if len(self.update_stats['failed_updates']) > 0:
            print("\n⚠️  失敗した更新:")
            for failure in self.update_stats['failed_updates'][:5]:
                print(f"   - {failure['type']} ID={failure['id']}: {failure['reason']}")
            if len(self.update_stats['failed_updates']) > 5:
                print(f"   ... 他 {len(self.update_stats['failed_updates']) - 5} 件")
        
        return report
    
    def run_update(self):
        """メイン更新処理"""
        logger.info("🚀 データベースパス更新開始")
        print("🚀 データベースのパス情報をS3形式に更新します...")
        
        try:
            # 1. バックアップ
            self.create_backup()
            
            # 2. GoalImages更新
            self.update_goal_images()
            
            # 3. CheckinImages更新
            self.update_checkin_images()
            
            # 4. レポート生成
            report = self.generate_update_report()
            
            logger.info("✅ パス更新完了")
            print("\n✅ パス更新が完了しました！")
            
            return report
            
        except Exception as e:
            logger.error(f"💥 パス更新中に重大なエラーが発生: {str(e)}")
            print(f"\n💥 パス更新エラー: {str(e)}")
            print(f"バックアップファイル: {self.update_stats['backup_file']}")
            raise

def main():
    """メイン関数"""
    print("="*60)
    print("🔄 データベースパスS3更新ツール")
    print("="*60)
    print("このツールは以下を実行します:")
    print("1. データベースの現在のパス情報をバックアップ")
    print("2. GoalImagesのパスをS3 URL形式に変換")
    print("3. CheckinImagesのパスをS3 URL形式に変換")
    print("4. 更新レポートの生成")
    print()
    print("⚠️  注意: 物理ファイルの移行は行いません")
    print("   データベースのパス情報のみを更新します")
    print()
    
    # 確認プロンプト
    confirm = input("パス更新を開始しますか？ [y/N]: ").strip().lower()
    if confirm not in ['y', 'yes']:
        print("パス更新をキャンセルしました。")
        return
    
    # 更新実行
    path_update_service = PathUpdateService()
    path_update_service.run_update()

if __name__ == "__main__":
    main()
