#!/usr/bin/env python3
"""
Qwen 3.6-27B OPTIMIZED Downloader
🎯 ONLY downloads: 15x model shards (safetensors) + configs
❌ SKIPS ALL: unnecessary git/license files
Target size: ~52GB (15 shards × ~3.5GB each)
"""

import subprocess
import sys
import os

# Auto-install required packages
for package in ["huggingface_hub", "PySimpleGUI", "requests"]:
    try:
        __import__(package.replace("-", "_"))
    except ImportError:
        subprocess.check_call([sys.executable, "-m", "pip", "install", package])

import PySimpleGUI as sg
from huggingface_hub import list_repo_files, hf_hub_url
import requests
import threading
import time
from datetime import datetime

sg.theme('Light Blue 2')
sg.set_options(font=('Segoe UI', 10))

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DOWNLOAD_DIR = os.path.join(SCRIPT_DIR, "qwen3.6-27b")

# =============================================================================
# VERIFIED WHITELIST - Qwen3.6-27B (based on actual HF repo)
# =============================================================================
KEEP_ONLY = {
    # Model shards - ALL 15 needed
    'model-00001-of-00015.safetensors',
    'model-00002-of-00015.safetensors',
    'model-00003-of-00015.safetensors',
    'model-00004-of-00015.safetensors',
    'model-00005-of-00015.safetensors',
    'model-00006-of-00015.safetensors',
    'model-00007-of-00015.safetensors',
    'model-00008-of-00015.safetensors',
    'model-00009-of-00015.safetensors',
    'model-00010-of-00015.safetensors',
    'model-00011-of-00015.safetensors',
    'model-00012-of-00015.safetensors',
    'model-00013-of-00015.safetensors',
    'model-00014-of-00015.safetensors',
    'model-00015-of-00015.safetensors',
    
    # Index file
    'model.safetensors.index.json',
    
    # Config - ALL needed
    'config.json',
    'generation_config.json',
    'preprocessor_config.json',
    'video_preprocessor_config.json',
    'configuration.json',
    'chat_template.jinja',
    
    # Tokenizer
    'tokenizer.json',
    'tokenizer_config.json',
    'vocab.json',
    'merges.txt',
    
    # Docs
    'README.md',
}

def should_download(filename):
    """Whitelist: ONLY download if in KEEP_ONLY"""
    return filename in KEEP_ONLY

class DownloaderUI:
    def __init__(self):
        self.is_downloading = False
        self.is_paused = False
        self.start_time = None
        self.window = None
        self.download_thread = None
        self.force_download = False
        self.session = requests.Session()
        
    def format_bytes(self, bytes_val):
        """Convert bytes to human readable"""
        for unit in ['B', 'KB', 'MB', 'GB']:
            if bytes_val < 1024:
                return f"{bytes_val:.2f} {unit}"
            bytes_val /= 1024
        return f"{bytes_val:.2f} TB"
    
    def download_file(self, url, filepath):
        temp_filepath = f"{filepath}.part.{os.getpid()}.{threading.get_ident()}"
        try:
            os.makedirs(os.path.dirname(filepath), exist_ok=True)

            try:
                response_head = self.session.head(url, timeout=15, allow_redirects=True)
                file_size = int(response_head.headers.get('content-length', -1))
            except Exception:
                file_size = -1

            if not self.force_download and os.path.exists(filepath):
                local_size = os.path.getsize(filepath)
                if file_size > 0:
                    if local_size == file_size:
                        self.log_message(f"   ⏭️ Already exists ({self.format_bytes(file_size)}), skipping")
                        return True, file_size
                    else:
                        self.log_message(
                            f"   ⚠️ Size mismatch (local: {self.format_bytes(local_size)}, remote: {self.format_bytes(file_size)}), re-downloading"
                        )
                else:
                    self.log_message(
                        f"   ⚠️ Remote size unknown; existing file may be incomplete, re-downloading"
                    )

            self.log_message(f"   ⬇️ Downloading ({self.format_bytes(file_size) if file_size > 0 else 'unknown size'})...")
            response = self.session.get(url, stream=True, timeout=30)
            response.raise_for_status()

            downloaded = 0
            start_time = time.time()

            with open(temp_filepath, 'wb') as f:
                for chunk in response.iter_content(chunk_size=1024 * 512):
                    if not self.is_downloading:
                        if os.path.exists(temp_filepath):
                            try:
                                os.remove(temp_filepath)
                            except Exception:
                                pass
                        return False, downloaded

                    while self.is_paused and self.is_downloading:
                        time.sleep(0.5)

                    if chunk:
                        f.write(chunk)
                        downloaded += len(chunk)

                        if file_size > 0:
                            progress = (downloaded / file_size) * 100
                            self.window['-PROGRESS-'].update(int(progress))
                            self.window['-PERCENT-'].update(f"{progress:.1f}%")

                            elapsed = time.time() - start_time
                            if elapsed > 0:
                                speed = downloaded / elapsed / (1024 * 1024)
                                self.window['-SPEED-'].update(f"{speed:.2f} MB/s")
                                eta_sec = (file_size - downloaded) / (downloaded / elapsed)
                                self.window['-ETA-'].update(
                                    f"{int(eta_sec // 60)}m {int(eta_sec % 60)}s"
                                )

                        self.window['-DOWNLOADED-'].update(self.format_bytes(downloaded))

            if os.path.exists(temp_filepath):
                os.replace(temp_filepath, filepath)

            if os.path.exists(filepath):
                actual_size = os.path.getsize(filepath)
                self.log_message(f"   ✅ Saved: {self.format_bytes(actual_size)}")
                return True, actual_size
            else:
                self.log_message("   ❌ File not created!")
                return False, 0

        except Exception as e:
            if os.path.exists(temp_filepath):
                try:
                    os.remove(temp_filepath)
                except Exception:
                    pass
            self.log_message(f"   ❌ Error: {str(e)}")
            return False, 0
    def download_worker(self):
        """Background download thread"""
        retry_count = 0
        max_retries = 3
        
        while retry_count < max_retries:
            try:
                self.log_message(f"[{datetime.now().strftime('%H:%M:%S')}] 🔄 Fetching file list...")
                self.window['-STATUS-'].update("🔄 Fetching file list...")
                
                all_files = list_repo_files(repo_id="Qwen/Qwen3.6-27B", repo_type="model")
                files_to_download = [f for f in all_files if should_download(f)]
                
                self.log_message(f"[{datetime.now().strftime('%H:%M:%S')}] 📋 WHITELISTED: {len(files_to_download)} files")
                
                downloaded_count = 0
                total_files = len(files_to_download)
                
                for idx, filename in enumerate(files_to_download):
                    if not self.is_downloading:
                        self.log_message("⏸ Download cancelled")
                        return
                    
                    filepath = os.path.join(DOWNLOAD_DIR, filename)
                    self.log_message(f"📥 [{idx+1}/{total_files}] {filename}")
                    self.window['-STATUS-'].update(f"[{idx+1}/{total_files}] {filename}")
                    
                    self.window['-PROGRESS-'].update(0)
                    self.window['-PERCENT-'].update("0%")
                    
                    url = hf_hub_url(repo_id="Qwen/Qwen3.6-27B", filename=filename)
                    success, size = self.download_file(url, filepath)
                    
                    if success:
                        downloaded_count += 1
                        self.log_message(f"   ✅ {self.format_bytes(size)}")
                    else:
                        self.log_message(f"   ⚠️ Skipped or failed")
                
                if self.is_downloading:
                    file_count = len([f for f in os.listdir(DOWNLOAD_DIR) if os.path.isfile(os.path.join(DOWNLOAD_DIR, f))])
                    total_size = sum(os.path.getsize(os.path.join(DOWNLOAD_DIR, f)) for f in os.listdir(DOWNLOAD_DIR) if os.path.isfile(os.path.join(DOWNLOAD_DIR, f)))
                    
                    self.window['-STATUS-'].update(f"✅ Complete! {file_count} files, {self.format_bytes(total_size)}")
                    self.log_message(f"[{datetime.now().strftime('%H:%M:%S')}] ✅ All done!")
                    self.log_message(f"📁 Total: {file_count} files, {self.format_bytes(total_size)}")
                    self.window['-PROGRESS-'].update(100)
                    self.window['-PERCENT-'].update("100%")
                
                self.is_downloading = False
                break
                
            except Exception as e:
                retry_count += 1
                error_msg = str(e)[:80]
                self.log_message(f"⚠️ Error: {error_msg}")
                self.log_message(f"🔁 Retrying ({retry_count}/{max_retries})...")
                self.window['-STATUS-'].update(f"⚠️ Error, retrying... ({retry_count}/{max_retries})")
                time.sleep(3)
        
        if retry_count >= max_retries:
            self.window['-STATUS-'].update("❌ Failed - too many errors")
            self.log_message("❌ Download failed after multiple retries")
        
        self.is_downloading = False
        self.window['-BUTTON-'].update("Start Download")
        self.window['-FORCE-'].update(disabled=False)
        self.window['-PAUSE-'].update(visible=False)
    
    def create_layout(self):
        """Create GUI layout"""
        layout = [
            [sg.Text("⚡ Qwen 3.6-27B Downloader", font=('Segoe UI', 14, 'bold'))],
            [sg.Text("15 safetensors shards + configs (~52GB total)", text_color='#666', font=('Segoe UI', 9))],
            
            [sg.Text("Status:", font=('Segoe UI', 10, 'bold')), 
             sg.Text("Ready", key='-STATUS-', text_color='#0078D4', font=('Segoe UI', 10))],
            
            [sg.ProgressBar(100, size=(45, 20), key='-PROGRESS-', bar_color=('#0078D4', '#E8E8E8'))],
            
            [sg.Column([
                [sg.Text("Progress:", font=('Segoe UI', 9, 'bold')), sg.Text("0%", key='-PERCENT-', font=('Segoe UI', 9))],
                [sg.Text("Speed:", font=('Segoe UI', 9, 'bold')), sg.Text("0 MB/s", key='-SPEED-', font=('Segoe UI', 9))],
            ]), sg.Column([
                [sg.Text("Downloaded:", font=('Segoe UI', 9, 'bold')), sg.Text("0 B", key='-DOWNLOADED-', font=('Segoe UI', 9))],
                [sg.Text("ETA:", font=('Segoe UI', 9, 'bold')), sg.Text("--:--", key='-ETA-', font=('Segoe UI', 9))],
            ])],
            
            [sg.Button('Start Download', key='-BUTTON-', size=(15, 2), button_color=('#fff', '#0078D4')),
             sg.Button('Force Fresh', key='-FORCE-', size=(15, 2), button_color=('#fff', '#FF6B6B')),
             sg.Button('Pause', key='-PAUSE-', size=(15, 2), visible=False),
             sg.Button('Exit', size=(10, 2))],
            
            [sg.Multiline(size=(50, 12), key='-LOG-', disabled=True, autoscroll=True)],
        ]
        return layout
    
    def run(self):
        """Main UI loop"""
        layout = self.create_layout()
        self.window = sg.Window('Qwen Downloader', layout, finalize=True, size=(550, 600))
        
        self.log_message(f"📁 Download directory: {DOWNLOAD_DIR}")
        self.log_message("")
        self.log_message("⚠️  WARNING: This will download ~52GB of model files!")
        self.log_message("💡 Click 'Start Download' to resume incomplete downloads")
        self.log_message("💡 Click 'Force Fresh' to download COMPLETE model from scratch")
        self.log_message("")
        
        while True:
            event, values = self.window.read(timeout=500)
            
            if event == sg.WINDOW_CLOSED or event == 'Exit':
                if self.is_downloading:
                    if sg.popup_yes_no("Download in progress. Exit anyway?") != "Yes":
                        continue
                break
            
            if event == '-BUTTON-':
                if not self.is_downloading:
                    self.is_downloading = True
                    self.force_download = False
                    self.start_time = time.time()
                    self.log_message(f"[{datetime.now().strftime('%H:%M:%S')}] ⬇️ Download started (resume mode)")
                    self.window['-BUTTON-'].update('Cancel')
                    self.window['-PAUSE-'].update(visible=True)
                    self.window['-STATUS-'].update("🔄 Initializing...")
                    self.window['-PERCENT-'].update("0%")
                    self.window['-SPEED-'].update("0 MB/s")
                    self.window['-DOWNLOADED-'].update("0 B")
                    self.window['-PROGRESS-'].update(0)
                    self.download_thread = threading.Thread(target=self.download_worker, daemon=True)
                    self.download_thread.start()
                else:
                    self.is_downloading = False
                    self.window['-STATUS-'].update("⏹ Cancelled by user")
                    self.window['-BUTTON-'].update('Start Download')
                    self.window['-PAUSE-'].update(visible=False)
                    self.log_message(f"[{datetime.now().strftime('%H:%M:%S')}] ⏹ Download cancelled")
            
            if event == '-FORCE-':
                if not self.is_downloading:
                    self.is_downloading = True
                    self.force_download = True
                    self.start_time = time.time()
                    self.log_message(f"[{datetime.now().strftime('%H:%M:%S')}] 🔄 FORCE download started (will re-download all files)")
                    self.window['-BUTTON-'].update('Cancel')
                    self.window['-FORCE-'].update(disabled=True)
                    self.window['-PAUSE-'].update(visible=True)
                    self.window['-STATUS-'].update("🔄 Initializing...")
                    self.window['-PERCENT-'].update("0%")
                    self.window['-SPEED-'].update("0 MB/s")
                    self.window['-DOWNLOADED-'].update("0 B")
                    self.window['-PROGRESS-'].update(0)
                    self.download_thread = threading.Thread(target=self.download_worker, daemon=True)
                    self.download_thread.start()
            
            if event == '-PAUSE-' and self.is_downloading:
                self.is_paused = not self.is_paused
                if self.is_paused:
                    self.window['-PAUSE-'].update('Resume')
                    self.window['-STATUS-'].update("⏸ Paused")
                    self.log_message(f"⏸ Paused - click Resume to continue")
                else:
                    self.window['-PAUSE-'].update('Pause')
                    self.window['-STATUS-'].update("🔄 Resuming...")
                    self.log_message("▶️ Resumed")
        
        self.window.close()
    
    def log_message(self, message):
        """Add message to log"""
        if self.window:
            current = self.window['-LOG-'].get()
            self.window['-LOG-'].update(current + message + '\n')

if __name__ == "__main__":
    downloader = DownloaderUI()
    downloader.run()