# app.py
import os
import uuid
import threading
import shutil
import subprocess
import json
from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

# ---------- Configuration ----------
UPLOAD_DIR = 'uploads'
OUTPUT_DIR = 'output'
TEMP_DIR = 'temp'
MUSIC_LIBRARY_DIR = 'music_library'
os.makedirs(UPLOAD_DIR, exist_ok=True)
os.makedirs(OUTPUT_DIR, exist_ok=True)
os.makedirs(TEMP_DIR, exist_ok=True)
os.makedirs(MUSIC_LIBRARY_DIR, exist_ok=True)

jobs = {}

# ---------- Music Library ----------
MUSIC_LIBRARY = [
    {"id": "none", "name": "No Music", "file": None},
]

def scan_music_library():
    if os.path.exists(MUSIC_LIBRARY_DIR):
        for file in os.listdir(MUSIC_LIBRARY_DIR):
            if file.endswith('.mp3'):
                exists = False
                for m in MUSIC_LIBRARY:
                    if m.get('file') == file:
                        exists = True
                        break
                if not exists:
                    name = file.replace('.mp3', '').replace('_', ' ').title()
                    MUSIC_LIBRARY.append({
                        "id": file.replace('.mp3', ''),
                        "name": f"🎵 {name}",
                        "file": file
                    })

scan_music_library()

def get_music_path(music_id):
    if not music_id or music_id == 'none':
        return None
    
    music_info = None
    for m in MUSIC_LIBRARY:
        if m.get('id') == music_id:
            music_info = m
            break
    
    if not music_info or not music_info.get('file'):
        return None
    
    music_file = music_info['file']
    music_path = os.path.join(MUSIC_LIBRARY_DIR, music_file)
    
    if not os.path.exists(music_path):
        return None
    
    return music_path

# ---------- FFmpeg helpers ----------
def run_ffmpeg(cmd, description="Running FFmpeg"):
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        error_msg = result.stderr[:500] if result.stderr else "Unknown error"
        print(f"❌ FFmpeg failed: {error_msg}")
        raise Exception(f"FFmpeg error: {error_msg}")
    return result.stdout + result.stderr

def get_video_info(video_path):
    try:
        cmd = ['ffprobe', '-v', 'error', '-show_entries', 
               'format=duration:stream=width,height,r_frame_rate', 
               '-of', 'json', video_path]
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode == 0:
            data = json.loads(result.stdout)
            duration = float(data.get('format', {}).get('duration', 300))
            
            width, height, fps = 1920, 1080, 30
            for stream in data.get('streams', []):
                if stream.get('codec_type') == 'video':
                    width = int(stream.get('width', 1920))
                    height = int(stream.get('height', 1080))
                    fps_str = stream.get('r_frame_rate', '30/1')
                    if '/' in fps_str:
                        num, den = fps_str.split('/')
                        fps = float(num) / float(den) if float(den) > 0 else 30
                    else:
                        fps = float(fps_str)
            return duration, width, height, fps
    except Exception as e:
        print(f"⚠️ Could not get video info: {e}")
    return 300, 1920, 1080, 30

def trim_segment_mobile_compatible(input_path, start, end, output_path):
    """
    Trim a segment with mobile-friendly encoding settings.
    Ensures compatibility with all devices (iPhone, Android, etc.)
    """
    duration = end - start
    cmd = [
        'ffmpeg',
        '-ss', str(start),
        '-i', input_path,
        '-t', str(duration),
        '-c:v', 'libx264',           # H.264 codec (universal)
        '-preset', 'medium',          # Good balance
        '-crf', '22',                 # Quality
        '-profile:v', 'baseline',     # Most compatible profile for mobile
        '-level', '3.0',              # Compatible with all devices
        '-pix_fmt', 'yuv420p',        # Standard pixel format
        '-c:a', 'aac',                # AAC audio (universal)
        '-b:a', '128k',               # Good quality
        '-ar', '44100',               # Standard sample rate
        '-movflags', '+faststart',    # For web streaming
        '-y',
        output_path
    ]
    run_ffmpeg(cmd, f"Trimming segment: {start:.1f}s to {end:.1f}s (mobile compatible)")

def concat_videos_mobile_compatible(clip_paths, output_path):
    """
    Concatenate multiple videos with mobile-compatible settings.
    """
    if len(clip_paths) == 1:
        # Re-encode single clip for compatibility
        cmd = [
            'ffmpeg',
            '-i', clip_paths[0],
            '-c:v', 'libx264',
            '-preset', 'medium',
            '-crf', '22',
            '-profile:v', 'baseline',
            '-level', '3.0',
            '-pix_fmt', 'yuv420p',
            '-c:a', 'aac',
            '-b:a', '128k',
            '-ar', '44100',
            '-movflags', '+faststart',
            '-y',
            output_path
        ]
        run_ffmpeg(cmd, "Re-encoding for mobile compatibility")
        return
    
    # Create concat list file
    list_path = os.path.join(TEMP_DIR, f'list_{uuid.uuid4().hex}.txt')
    with open(list_path, 'w') as f:
        for p in clip_paths:
            abs_path = os.path.abspath(p).replace('\\', '/')
            f.write(f"file '{abs_path}'\n")
    
    # Concatenate with mobile-friendly settings
    cmd = [
        'ffmpeg',
        '-f', 'concat',
        '-safe', '0',
        '-i', list_path,
        '-c:v', 'libx264',           # Re-encode to ensure compatibility
        '-preset', 'medium',
        '-crf', '22',
        '-profile:v', 'baseline',
        '-level', '3.0',
        '-pix_fmt', 'yuv420p',
        '-c:a', 'aac',
        '-b:a', '128k',
        '-ar', '44100',
        '-movflags', '+faststart',
        '-y',
        output_path
    ]
    run_ffmpeg(cmd, f"Concatenating {len(clip_paths)} clips (mobile compatible)")
    os.remove(list_path)

def mix_audio_mobile_compatible(video_path, music_path, output_path, mute_original=False):
    """
    Mix audio with mobile-compatible settings.
    """
    video_duration, _, _, _ = get_video_info(video_path)
    
    probe_cmd = ['ffprobe', '-v', 'error', '-show_entries', 'stream=codec_type', '-of', 'json', video_path]
    result = subprocess.run(probe_cmd, capture_output=True, text=True)
    has_audio = False
    if result.returncode == 0:
        data = json.loads(result.stdout)
        for stream in data.get('streams', []):
            if stream.get('codec_type') == 'audio':
                has_audio = True
                break
    
    music_duration = get_video_info(music_path)[0]
    trimmed_music = False
    
    print(f"\n🎵 Audio Mixing (Mobile Compatible):")
    print(f"   Video duration: {video_duration:.1f}s")
    print(f"   Music duration: {music_duration:.1f}s")
    print(f"   Mute Original: {mute_original}")
    print(f"   Video has audio: {has_audio}")
    
    if music_duration > video_duration:
        print(f"   ✂️ Trimming music to match video ({video_duration:.1f}s)")
        trimmed_path = music_path + '.trimmed.mp3'
        cmd = [
            'ffmpeg',
            '-i', music_path,
            '-t', str(video_duration),
            '-c', 'copy',
            '-y',
            trimmed_path
        ]
        run_ffmpeg(cmd, "Trimming music")
        music_path = trimmed_path
        trimmed_music = True
    
    # Always re-encode to ensure compatibility
    if mute_original:
        print("   🔇 Muting original audio, using music only")
        cmd = [
            'ffmpeg',
            '-i', video_path,
            '-i', music_path,
            '-c:v', 'libx264',
            '-preset', 'medium',
            '-crf', '22',
            '-profile:v', 'baseline',
            '-level', '3.0',
            '-pix_fmt', 'yuv420p',
            '-c:a', 'aac',
            '-b:a', '128k',
            '-ar', '44100',
            '-map', '0:v:0',
            '-map', '1:a:0',
            '-shortest',
            '-movflags', '+faststart',
            '-y',
            output_path
        ]
    elif has_audio:
        print("   🎵 Mixing original audio with music")
        cmd = [
            'ffmpeg',
            '-i', video_path,
            '-i', music_path,
            '-c:v', 'libx264',
            '-preset', 'medium',
            '-crf', '22',
            '-profile:v', 'baseline',
            '-level', '3.0',
            '-pix_fmt', 'yuv420p',
            '-filter_complex',
            '[0:a]volume=0.7[a0];[1:a]volume=0.3[a1];[a0][a1]amix=inputs=2:duration=shortest',
            '-c:a', 'aac',
            '-b:a', '128k',
            '-ar', '44100',
            '-movflags', '+faststart',
            '-y',
            output_path
        ]
    else:
        print("   🎵 No original audio, using music only")
        cmd = [
            'ffmpeg',
            '-i', video_path,
            '-i', music_path,
            '-c:v', 'libx264',
            '-preset', 'medium',
            '-crf', '22',
            '-profile:v', 'baseline',
            '-level', '3.0',
            '-pix_fmt', 'yuv420p',
            '-c:a', 'aac',
            '-b:a', '128k',
            '-ar', '44100',
            '-map', '0:v:0',
            '-map', '1:a:0',
            '-shortest',
            '-movflags', '+faststart',
            '-y',
            output_path
        ]
    
    run_ffmpeg(cmd, "Mixing audio (mobile compatible)")
    
    if trimmed_music and os.path.exists(music_path):
        os.remove(music_path)
    
    print(f"✅ Audio mixing complete (mobile compatible)!")

def generate_segments(video_path, target_duration=60):
    duration, _, _, _ = get_video_info(video_path)
    
    if duration <= target_duration:
        return [{"start": 0.0, "end": round(duration, 2)}]
    
    # Calculate number of segments
    if target_duration <= 30:
        num_segments = 4
    elif target_duration <= 60:
        num_segments = 6
    elif target_duration <= 120:
        num_segments = 8
    else:
        num_segments = 10
    
    segment_duration = target_duration / num_segments
    step = duration / (num_segments + 1)
    
    segments = []
    for i in range(num_segments):
        center = step * (i + 1)
        start = max(0, center - segment_duration/2)
        end = min(duration, center + segment_duration/2)
        if end - start > 0.5:
            segments.append({"start": round(start, 2), "end": round(end, 2)})
    
    return segments

def cleanup_uploaded_files(job_id):
    """Delete uploaded video and music files after processing"""
    job = jobs.get(job_id)
    if not job:
        return
    
    # Delete uploaded video
    video_path = job.get('video_path')
    if video_path and os.path.exists(video_path):
        try:
            os.remove(video_path)
            print(f"🗑️ Deleted uploaded video: {video_path}")
        except Exception as e:
            print(f"⚠️ Could not delete video: {e}")
    
    # Delete uploaded music
    music_path = job.get('music_path')
    if music_path and os.path.exists(music_path):
        try:
            os.remove(music_path)
            print(f"🗑️ Deleted uploaded music: {music_path}")
        except Exception as e:
            print(f"⚠️ Could not delete music: {e}")

def process_video(job_id, segments, mute=False, music_id=None):
    job = jobs.get(job_id)
    if not job:
        return
    
    video_path = job['video_path']
    uploaded_music = job.get('music_path')
    
    try:
        clip_dir = os.path.join(TEMP_DIR, f"clips_{job_id}")
        os.makedirs(clip_dir, exist_ok=True)
        
        print(f"\n🎬 Processing {len(segments)} segments (Mobile Compatible)...")
        
        # Trim segments with mobile-friendly settings
        clip_paths = []
        for i, seg in enumerate(segments):
            start = seg['start']
            end = seg['end']
            clip_path = os.path.join(clip_dir, f"clip_{i}.mp4")
            print(f"   Segment {i+1}: {start:.1f}s - {end:.1f}s")
            trim_segment_mobile_compatible(video_path, start, end, clip_path)
            clip_paths.append(clip_path)
        
        # Concatenate with mobile-friendly settings
        merged_path = os.path.join(TEMP_DIR, f"merged_{job_id}.mp4")
        print(f"🎬 Concatenating {len(clip_paths)} clips...")
        concat_videos_mobile_compatible(clip_paths, merged_path)
        
        # Get music
        music_path = None
        if uploaded_music and os.path.exists(uploaded_music):
            music_path = uploaded_music
        elif music_id and music_id != 'none':
            music_path = get_music_path(music_id)
        
        # Final output with mobile-friendly settings
        output_path = os.path.join(OUTPUT_DIR, f"highlight_{job_id}.mp4")
        
        if music_path and os.path.exists(music_path):
            print("🎵 Adding background music...")
            mix_audio_mobile_compatible(merged_path, music_path, output_path, mute)
            os.remove(merged_path)
        else:
            print("📹 No music selected, re-encoding for mobile compatibility...")
            # Re-encode for mobile compatibility
            cmd = [
                'ffmpeg',
                '-i', merged_path,
                '-c:v', 'libx264',
                '-preset', 'medium',
                '-crf', '22',
                '-profile:v', 'baseline',
                '-level', '3.0',
                '-pix_fmt', 'yuv420p',
                '-c:a', 'aac',
                '-b:a', '128k',
                '-ar', '44100',
                '-movflags', '+faststart',
                '-y',
                output_path
            ]
            run_ffmpeg(cmd, "Re-encoding for mobile compatibility")
            os.remove(merged_path)
        
        # Clean up clips
        shutil.rmtree(clip_dir)
        
        # 🔥 NEW: Delete uploaded files after successful processing
        cleanup_uploaded_files(job_id)
        
        job['output_path'] = output_path
        job['status'] = 'done'
        jobs[job_id] = job
        
        file_size = os.path.getsize(output_path) / 1024 / 1024
        print(f"\n✅ Video saved (Mobile Compatible): {output_path}")
        print(f"   File size: {file_size:.1f} MB")
        print(f"   Duration: {get_video_info(output_path)[0]:.1f}s")
        print(f"   Codec: H.264/AAC (Universal compatibility)")
        print(f"🗑️ Uploaded files have been cleaned up")
        
    except Exception as e:
        print(f"❌ Processing error: {e}")
        import traceback
        traceback.print_exc()
        
        # Clean up uploaded files even if there's an error
        cleanup_uploaded_files(job_id)
        
        job['status'] = 'error'
        job['error'] = str(e)
        jobs[job_id] = job

# ---------- Routes ----------
@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/music-library')
def get_music_library():
    scan_music_library()
    return jsonify(MUSIC_LIBRARY)

@app.route('/api/upload', methods=['POST'])
def upload():
    video = request.files.get('video')
    music = request.files.get('music')
    
    if not video:
        return jsonify({'error': 'No video'}), 400
    
    job_id = str(uuid.uuid4())
    video_path = os.path.join(UPLOAD_DIR, f"{job_id}_video.mp4")
    video.save(video_path)
    
    music_path = None
    if music:
        music_path = os.path.join(UPLOAD_DIR, f"{job_id}_music.mp3")
        music.save(music_path)
    
    jobs[job_id] = {
        'video_path': video_path,
        'music_path': music_path,
        'segments': [],
        'suggested': [],
        'output_path': None,
        'status': 'pending'
    }
    return jsonify({'id': job_id})

@app.route('/api/auto-generate/<job_id>', methods=['POST'])
def auto_generate(job_id):
    job = jobs.get(job_id)
    if not job:
        return jsonify({'error': 'Job not found'}), 404
    
    data = request.get_json() or {}
    target_duration = data.get('targetDuration', 60)
    mute = data.get('mute', False)
    music_id = data.get('musicId', None)
    
    try:
        suggested = generate_segments(job['video_path'], target_duration)
        job['suggested'] = suggested
        job['segments'] = suggested
        threading.Thread(target=process_video, args=(job_id, suggested, mute, music_id)).start()
        return jsonify({'message': 'Processing started', 'segments': suggested})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/generate/<job_id>', methods=['POST'])
def generate(job_id):
    job = jobs.get(job_id)
    if not job:
        return jsonify({'error': 'Job not found'}), 404
    
    data = request.get_json()
    segments = data.get('segments')
    mute = data.get('mute', False)
    music_id = data.get('musicId', None)
    
    if not segments or len(segments) == 0:
        return jsonify({'error': 'No segments provided'}), 400
    
    job['segments'] = segments
    job['status'] = 'processing'
    jobs[job_id] = job
    
    threading.Thread(target=process_video, args=(job_id, segments, mute, music_id)).start()
    return jsonify({'message': 'Processing started'})

@app.route('/api/status/<job_id>')
def status(job_id):
    job = jobs.get(job_id)
    if not job:
        return jsonify({'error': 'Not found'}), 404
    return jsonify({
        'id': job_id,
        'status': job['status'],
        'error': job.get('error'),
        'outputPath': job.get('output_path')
    })

@app.route('/api/download/<job_id>')
def download(job_id):
    job = jobs.get(job_id)
    if not job or job['status'] != 'done' or not job.get('output_path'):
        return jsonify({'error': 'Video not ready'}), 400
    
    if not os.path.exists(job['output_path']):
        return jsonify({'error': 'File not found'}), 404
    
    return send_file(job['output_path'], as_attachment=True, download_name=f"highlight_{job_id}.mp4")

# Optional: Add a cleanup endpoint to manually clear old files
@app.route('/api/cleanup/<job_id>', methods=['DELETE'])
def cleanup_job(job_id):
    """Manually cleanup a job's files"""
    job = jobs.get(job_id)
    if not job:
        return jsonify({'error': 'Job not found'}), 404
    
    # Clean up output file if exists
    output_path = job.get('output_path')
    if output_path and os.path.exists(output_path):
        try:
            os.remove(output_path)
            print(f"🗑️ Deleted output video: {output_path}")
        except Exception as e:
            print(f"⚠️ Could not delete output: {e}")
    
    # Clean up uploaded files
    cleanup_uploaded_files(job_id)
    
    # Remove job from memory
    del jobs[job_id]
    
    return jsonify({'message': 'Cleanup complete'})

if __name__ == '__main__':
    print("\n" + "="*60)
    print("🎬 ClipForge Server Starting...")
    print("="*60)
    print(f"\n📁 Music Library: {MUSIC_LIBRARY_DIR}/ ({len(MUSIC_LIBRARY)-1} tracks)")
    print(f"📁 Uploads: {UPLOAD_DIR}/")
    print(f"📁 Output: {OUTPUT_DIR}/")
    print("\n📌 Features:")
    print("   ✅ Video trimming (H.264/AAC)")
    print("   ✅ Mobile compatible output")
    print("   ✅ Background music (library + custom upload)")
    print("   ✅ Mute original audio")
    print("   ✅ Auto-segment generation")
    print("   ✅ Manual timeline editing")
    print("   ✅ Music trimmed to video length")
    print("   ✅ Automatic cleanup of uploaded files")  # New
    print("\n🎯 Video Settings (Universal Compatibility):")
    print("   📹 Codec: H.264 (Baseline Profile)")
    print("   🎵 Audio: AAC (128kbps)")
    print("   📱 Compatible: iPhone, Android, Web")
    print("\n" + "="*60)
    app.run(debug=True, use_reloader=False, port=5000)