from flask import Flask, send_from_directory, request, Response, jsonify
import os
import mimetypes
import time
from threading import Lock
import logging
import re
import json
from datetime import datetime
import random

# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)

MEDIA_DIR = '/home/redjifox/obs'
CHUNK_SIZE = 2 * 1024 * 1024  # 2MB chunks
BUFFER_SIZE = 64 * 1024       # 64KB буфер чтения
CACHE_TIMEOUT = 60            # 60 секунд кэширования

# Кэш для информации о файлах
file_cache = {}
cache_lock = Lock()
last_cleanup = time.time()

# Хранилище плейлистов
playlists = {}
playlist_lock = Lock()

def cleanup_cache():
    """Очистка устаревшего кэша"""
    global last_cleanup
    current_time = time.time()
    
    if current_time - last_cleanup > 30:
        with cache_lock:
            expired_keys = []
            for key, info in file_cache.items():
                if current_time - info['cache_time'] > CACHE_TIMEOUT:
                    expired_keys.append(key)
            
            for key in expired_keys:
                del file_cache[key]
            
            last_cleanup = current_time
            if expired_keys:
                logger.info(f"Cleaned up {len(expired_keys)} cache entries")

def get_file_info(file_path):
    """Получаем информацию о файле с кэшированием"""
    cleanup_cache()
    
    with cache_lock:
        if file_path in file_cache:
            cached_info = file_cache[file_path]
            if (os.path.exists(file_path) and 
                os.path.getsize(file_path) == cached_info['size'] and
                os.path.getmtime(file_path) == cached_info['mtime']):
                return cached_info
        
        if not os.path.exists(file_path):
            return None
            
        file_info = {
            'size': os.path.getsize(file_path),
            'mtime': os.path.getmtime(file_path),
            'path': file_path,
            'cache_time': time.time()
        }
        
        file_cache[file_path] = file_info
        return file_info

def parse_range_header(range_header, file_size):
    """Правильный парсинг Range заголовка"""
    if not range_header or not range_header.startswith('bytes='):
        return None, None, file_size
    
    try:
        range_str = range_header[6:]
        ranges = range_str.split(',')
        first_range = ranges[0].strip()
        
        if '-' not in first_range:
            return None, None, file_size
        
        start_str, end_str = first_range.split('-', 1)
        start = int(start_str) if start_str else 0
        end = int(end_str) if end_str else file_size - 1
        
        if start < 0:
            start = 0
        if start >= file_size:
            return None, None, file_size
        if end >= file_size:
            end = file_size - 1
        if end < start:
            end = file_size - 1
            
        length = end - start + 1
        return start, end, length
        
    except (ValueError, AttributeError):
        return None, None, file_size

def get_video_files():
    """Получаем список видео файлов"""
    video_files = []
    for file in os.listdir(MEDIA_DIR):
        file_path = os.path.join(MEDIA_DIR, file)
        if os.path.isfile(file_path) and file.lower().endswith(('.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv')):
            video_files.append(file)
    return sorted(video_files)

def create_playlist(name, files, shuffle=False, loop=False):
    """Создает плейлист"""
    with playlist_lock:
        playlists[name] = {
            'files': files,
            'shuffle': shuffle,
            'loop': loop,
            'created': datetime.now().isoformat(),
            'current_index': 0
        }
        return playlists[name]

def get_playlist(name):
    """Получает плейлист"""
    with playlist_lock:
        return playlists.get(name)
def get_next_video(playlist_name):
    """Получает следующее видео из плейлиста"""
    with playlist_lock:
        playlist = playlists.get(playlist_name)
        if not playlist or not playlist['files']:
            return None
        
        files = playlist['files']
        current_index = playlist['current_index']
        
        if playlist['shuffle']:
            # Режим shuffle - случайный файл
            next_index = random.randint(0, len(files) - 1)
        else:
            # Последовательный режим
            next_index = (current_index + 1) % len(files)
        
        playlist['current_index'] = next_index
        return files[next_index]

@app.route('/media/<path:filename>')
def stream_media(filename):
    """Потоковая передача отдельного файла"""
    try:
        file_path = os.path.join(MEDIA_DIR, filename)
        
        file_info = get_file_info(file_path)
        if not file_info:
            return f"Файл {filename} не найден", 404
        
        file_size = file_info['size']
        range_header = request.headers.get('Range')
        
        mime_type, _ = mimetypes.guess_type(file_path)
        if not mime_type:
            mime_type = 'video/mp4'
        
        if not range_header:
            response = send_from_directory(MEDIA_DIR, filename)
            response.headers['Accept-Ranges'] = 'bytes'
            response.headers['Cache-Control'] = 'no-cache'
            return response
        
        start, end, length = parse_range_header(range_header, file_size)
        
        if start is None:
            response = send_from_directory(MEDIA_DIR, filename)
            response.headers['Accept-Ranges'] = 'bytes'
            response.headers['Cache-Control'] = 'no-cache'
            return response
        
        logger.info(f"Streaming {filename}: bytes {start}-{end}/{file_size}")
        
        def generate():
            try:
                with open(file_path, 'rb') as f:
                    f.seek(start)
                    remaining = length
                    
                    while remaining > 0:
                        chunk_size = min(BUFFER_SIZE, remaining)
                        data = f.read(chunk_size)
                        if not data:
                            break
                        yield data
                        remaining -= len(data)
                        
            except Exception as e:
                logger.error(f"Error streaming file: {e}")
        
        response = Response(
            generate(),
            status=206,
            mimetype=mime_type,
            direct_passthrough=True
        )
        
        response.headers.add('Content-Length', str(length))
        response.headers.add('Accept-Ranges', 'bytes')
        response.headers.add('Content-Range', f'bytes {start}-{end}/{file_size}')
        response.headers.add('Cache-Control', 'no-cache')
        response.headers.add('X-Content-Type-Options', 'nosniff')
        
        return response
        
    except Exception as e:
        logger.error(f"Error in stream_media: {e}")
        return f"Internal server error: {e}", 500

@app.route('/playlist/<playlist_name>')
def stream_playlist(playlist_name):
    """Потоковая передача плейлиста как единого потока"""
    try:
        playlist = get_playlist(playlist_name)
        if not playlist:
            return f"Плейлист {playlist_name} не найден", 404
        
        # Получаем текущее видео из плейлиста
        current_video = playlist['files'][playlist['current_index']]
        file_path = os.path.join(MEDIA_DIR, current_video)
        
        file_info = get_file_info(file_path)
        if not file_info:
            return f"Файл {current_video} не найден", 404
        
        file_size = file_info['size']
        range_header = request.headers.get('Range')
        
        mime_type, _ = mimetypes.guess_type(file_path)
        if not mime_type:
            mime_type = 'video/mp4'
        
        if not range_header:
            # Для плейлистов всегда используем частичный контент
            range_header = 'bytes=0-'
        
        start, end, length = parse_range_header(range_header, file_size)
        
        if start is None:
            start, end, length = 0, min(CHUNK_SIZE, file_size) - 1, min(CHUNK_SIZE, file_size)
        
        logger.info(f"Playlist {playlist_name}: {current_video} ({start}-{end}/{file_size})")
        
        def generate_playlist():
            try:
                with open(file_path, 'rb') as f:
                    f.seek(start)
                    remaining = length
                    
                    while remaining > 0:
                        chunk_size = min(BUFFER_SIZE, remaining)
                        data = f.read(chunk_size)
                        if not data:
                            break
                        yield data
                        remaining -= len(data)
                        
            except Exception as e:
                logger.error(f"Error streaming playlist: {e}")
        
        response = Response(
            generate_playlist(),
            status=206,
            mimetype=mime_type,
            direct_passthrough=True
        )
        
        response.headers.add('Content-Length', str(length))
        response.headers.add('Accept-Ranges', 'bytes')
        response.headers.add('Content-Range', f'bytes {start}-{end}/{file_size}')
        response.headers.add('Cache-Control', 'no-cache')
        response.headers.add('X-Content-Type-Options', 'nosniff')
        response.headers.add('X-Playlist-Name', playlist_name)
        response.headers.add('X-Current-Video', current_video)
        response.headers.add('X-Video-Index', str(playlist['current_index']))
        response.headers.add('X-Total-Videos', str(len(playlist['files'])))
        
        return response
        
    except Exception as e:
        logger.error(f"Error in stream_playlist: {e}")
        return f"Internal server error: {e}", 500

@app.route('/api/playlists', methods=['GET', 'POST'])
def manage_playlists():
    """API для управления плейлистами"""
    if request.method == 'GET':
        return jsonify({
            'playlists': {name: {k: v for k, v in data.items() if k != 'files'} 
                         for name, data in playlists.items()}
        })
    
    elif request.method == 'POST':
        try:
            data = request.get_json()
            name = data.get('name')
            files = data.get('files', [])
            shuffle = data.get('shuffle', False)
            loop = data.get('loop', False)
            
            if not name or not files:
                return jsonify({'error': 'Name and files are required'}), 400
            
            # Проверяем существование файлов
            valid_files = []
            for file in files:
                if os.path.exists(os.path.join(MEDIA_DIR, file)):
                    valid_files.append(file)
            
            if not valid_files:
                return jsonify({'error': 'No valid files found'}), 400
            
            playlist = create_playlist(name, valid_files, shuffle, loop)
            return jsonify({
                'success': True,
                'playlist': {k: v for k, v in playlist.items() if k != 'files'},
                'file_count': len(valid_files)
            })
            
        except Exception as e:
            return jsonify({'error': str(e)}), 500

@app.route('/api/playlist/<playlist_name>/next', methods=['POST'])
def next_video(playlist_name):
    """Переключение на следующее видео в плейлисте"""
    try:
        next_video = get_next_video(playlist_name)
        if not next_video:
            return jsonify({'error': 'Playlist not found or empty'}), 404
        
        return jsonify({
            'success': True,
            'next_video': next_video,
            'playlist': playlist_name
        })
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.after_request
def after_request(response):
    """Добавляем CORS заголовки"""
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Range, Content-Type, Origin, Accept')
    response.headers.add('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST')
    response.headers.add('Access-Control-Expose-Headers', 'Content-Range, Content-Length, Accept-Ranges, X-Playlist-Name, X-Current-Video, X-Video-Index, X-Total-Videos')
    response.headers.add('Cache-Control', 'no-cache')
    return response

@app.route('/media/<path:filename>', methods=['OPTIONS'])
@app.route('/playlist/<playlist_name>', methods=['OPTIONS'])
def options_handler(filename=None, playlist_name=None):
    """Обработчик OPTIONS запросов"""
    response = Response()
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Headers'] = 'Range, Content-Type, Origin, Accept'
    response.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, OPTIONS, POST'
    response.headers['Access-Control-Expose-Headers'] = 'Content-Range, Content-Length, Accept-Ranges, X-Playlist-Name, X-Current-Video, X-Video-Index, X-Total-Videos'
    response.headers['Cache-Control'] = 'no-cache'
    return response

@app.route('/list')
def list_files():
    """API для получения списка файлов"""
    try:
        files = []
        for file in os.listdir(MEDIA_DIR):
            file_path = os.path.join(MEDIA_DIR, file)
            if os.path.isfile(file_path):
                mime_type, _ = mimetypes.guess_type(file_path)
                files.append({
                    'name': file,
                    'size': os.path.getsize(file_path),
                    'type': mime_type or 'unknown',
                    'url': f'/media/{file}',
                    'last_modified': os.path.getmtime(file_path)
                })
        return {'files': sorted(files, key=lambda x: x['name'])}
    except Exception as e:
        logger.error(f"Error listing files: {e}")
        return {'error': str(e)}, 500

@app.route('/')
def home():
    try:
        video_files = get_video_files()
        playlists_info = {name: {k: v for k, v in data.items() if k != 'files'} 
                         for name, data in playlists.items()}
    except Exception as e:
        video_files = [f"Ошибка: {e}"]
        playlists_info = {}
    
    file_list = "\n".join([f'<li><a href="/media/{file}" target="_blank">{file}</a></li>' for file in video_files[:10]])
    playlist_list = "\n".join([f'<li><strong>{name}</strong>: {len(playlists[name]["files"])} videos - <a href="/playlist/{name}" target="_blank">Play</a></li>' 
                              for name in playlists.keys()])
    
    return f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>uSharpVideo Media Server</title>
        <meta charset="utf-8">
        <style>
            body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
            .container {{ max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
            .file-list {{ max-height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 20px 0; }}
            .playlist-list {{ max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; border-radius: 5px; margin: 20px 0; }}
            .info {{ background: #e8f4f8; padding: 20px; border-radius: 8px; margin: 20px 0; }}
            .test-video {{ margin: 30px 0; padding: 20px; background: #f9f9f9; border-radius: 8px; }}
            .create-playlist {{ margin: 20px 0; padding: 20px; background: #f0f8f0; border-radius: 8px; }}
            select, video, input, button {{ margin: 5px 0; }}
            video {{ width: 100%; max-width: 800px; border: 2px solid #333; border-radius: 5px; }}
            .status {{ padding: 10px; background: #eee; border-radius: 5px; margin: 10px 0; }}
        </style>
    </head>
    <body>
        <div class="container">
            <h1>🎬 uSharpVideo Media Server</h1>
            
            <div class="info">
                <h3>📊 Статус сервера</h3>
                <p><strong>✅ Плейлисты:</strong> Поддерживаются</p>
                <p><strong>📁 Директория:</strong> {MEDIA_DIR}</p>
                <p><strong>🎯 Для нескольких игроков:</strong> Используйте плейлисты</p>
            </div>

            <div class="create-playlist">
                <h3>📋 Создать плейлист</h3>
                <div>
                    <input type="text" id="playlistName" placeholder="Название плейлиста" style="width: 200px;">
                    <label><input type="checkbox" id="shufflePlaylist"> Shuffle</label>
                    <label><input type="checkbox" id="loopPlaylist"> Loop</label>
                    <button onclick="createPlaylist()">Создать плейлист</button>
                </div>
                <div id="playlistStatus" class="status"></div>
            </div>

            <h2>📂 Плейлисты ({len(playlists)})</h2>
            <div class="playlist-list">
                <ul>{playlist_list if playlists else '<li>Нет плейлистов</li>'}</ul>
            </div>
            
            <h2>🎥 Видео файлы ({len(video_files)})</h2>
            <div class="file-list">
                <ul>{file_list}</ul>
            </div>
            
            <div class="test-video">
                <h3>🎭 Тест воспроизведения</h3>
                <select id="videoSelect" onchange="updateVideo()">
                    <option value="">-- Выберите видео --</option>
                    <option value="">-- Плейлисты --</option>
                    {"".join([f'<option value="playlist:{name}">🎵 {name} ({len(playlists[name]["files"])} videos)</option>' for name in playlists.keys()])}
                    <option value="">-- Отдельные файлы --</option>
                    {"".join([f'<option value="file:{file}">{file}</option>' for file in video_files[:20]])}
                </select>
                
                <div id="videoContainer" style="margin-top: 20px; display: none;">
                    <video controls width="800" height="450" id="testVideo" preload="auto">
                        Ваш браузер не поддерживает видео тег.
                    </video>
                    <div class="status">
                        <p>Статус: <span id="status">Ожидание...</span></p>
                        <p>Информация: <span id="videoInfo">-</span></p>
                    </div>
                </div>
            </div>
        </div>
        
        <script>
            function createPlaylist() {{
                var name = document.getElementById('playlistName').value;
                var shuffle = document.getElementById('shufflePlaylist').checked;
                var loop = document.getElementById('loopPlaylist').checked;
                var status = document.getElementById('playlistStatus');
                
                if (!name) {{
                    status.innerHTML = '❌ Введите название плейлиста';
                    return;
                }}
                
                // Создаем плейлист из всех видео файлов
                var files = {json.dumps(video_files)};
                
                fetch('/api/playlists', {{
                    method: 'POST',
                    headers: {{ 'Content-Type': 'application/json' }},
                    body: JSON.stringify({{
                        name: name,
                        files: files,
                        shuffle: shuffle,
                        loop: loop
                    }})
                }})
                .then(response => response.json())
                .then(data => {{
                    if (data.success) {{
                        status.innerHTML = '✅ Плейлист создан: ' + name + ' (' + data.file_count + ' видео)';
                        setTimeout(() => location.reload(), 1000);
                    }} else {{
                        status.innerHTML = '❌ Ошибка: ' + (data.error || 'Unknown error');
                    }}
                }})
                .catch(error => {{
                    status.innerHTML = '❌ Ошибка сети: ' + error;
                }});
            }}
            
            function updateVideo() {{
                var select = document.getElementById('videoSelect');
                var video = document.getElementById('testVideo');
                var container = document.getElementById('videoContainer');
                var status = document.getElementById('status');
                var videoInfo = document.getElementById('videoInfo');
                
                if (select.value) {{
                    var [type, value] = select.value.split(':');
                    var videoUrl = type === 'playlist' ? '/playlist/' + encodeURIComponent(value) : '/media/' + encodeURIComponent(value);
                    
                    video.src = videoUrl;
                    container.style.display = 'block';
                    status.textContent = '🔄 Загрузка...';
                    videoInfo.textContent = type === 'playlist' ? 'Плейлист: ' + value : 'Файл: ' + value;
                    
                    video.onloadedmetadata = function() {{
                        status.textContent = '✅ Загружено';
                        if (type !== 'playlist') {{
                            videoInfo.textContent += ', Длительность: ' + formatTime(video.duration);
                        }}
                    }};
                    
                    video.onerror = function() {{
                        status.textContent = '❌ Ошибка загрузки';
                    }};
                    
                    video.play().catch(function(e) {{
                        console.log('Autoplay prevented:', e);
                    }});
                }} else {{
                    container.style.display = 'none';
                }}
            }}
            
            function formatTime(seconds) {{
                var minutes = Math.floor(seconds / 60);
                var secs = Math.floor(seconds % 60);
                return minutes + ':' + (secs < 10 ? '0' : '') + secs;
            }}
        </script>
    </body>
    </html>
    """

if __name__ == '__main__':
    print("=" * 70)
    print("🎬 uSharpVideo Media Server запускается...")
    print(f"📁 Директория: {MEDIA_DIR}")
    print("🎯 Поддержка плейлистов для нескольких игроков")
    print("=" * 70)
    
    try:
        video_files = get_video_files()
        print("🎥 Видео файлы:")
        for file in video_files[:8]:
            size = os.path.getsize(os.path.join(MEDIA_DIR, file))
            print(f"  - {file} ({size // (1024*1024)}MB)")
        if len(video_files) > 8:
            print(f"  ... и еще {len(video_files)-8} видео файлов")
            
        # Автоматически создаем дефолтный плейлист
        if video_files:
            default_playlist = create_playlist('default', video_files, shuffle=True, loop=True)
            print(f"🎵 Авто-плейлист: 'default' ({len(video_files)} видео)")
            
    except Exception as e:
        print(f"❌ Ошибка при инициализации: {e}")
    
    print("=" * 70)
    print("🌐 Сервер запущен на http://0.0.0.0:5000")
    print("📱 Откройте в браузере для управления плейлистами")
    print("=" * 70)
    
    app.run(host='0.0.0.0', port=1214, debug=False, threaded=True)


