from .imports import *
import threading,os,re,urllib.request,subprocess,requests,shutil,tempfile
import os,re,hashlib,unicodedata
M3U8 = lazy_import('m3u8','M3U8')
m3u8_To_MP4 = lazy_import('m3u8_To_MP4')
yt_dlp= lazy_import('yt_dlp')
PyPDF2= lazy_import('PyPDF2')
FFmpegFixupPostProcessor = lazy_import('yt_dlp','postprocessor','ffmpeg','FFmpegFixupPostProcessor')
logger = get_logFile('video_bp')
def get_output_basename(output_basename=None,output_filename=None,output_ext=None):
    basename = output_basename
    filename = output_filename
    ext = output_ext
    
    if basename:
        if ext and not filename:
            filename,_ = os.path.splitext(basename)
        if filename and not ext:
            _,ext = os.path.splitext(basename)
        if not filename and not ext:
            filename,ext = os.path.splitext(basename)
    if filename:
        if not ext:
            filename,ext = os.path.splitext(filename)
        else:
            filename,_ = os.path.splitext(filename)
    if not filename:
        filename = 'video'
    if not ext:
       ext = '.mp4'
    return f"{filename}{ext}"

class VideoDownloader:
    def __init__(self, url,
                 title=None,
                 download_directory=os.getcwd(),
                 user_agent=None,
                 video_extention=None, 
                 download_video=True,
                 get_info=False,
                 auto_file_gen=True,
                 standalone_download=False,
                 output_filename=None,
                 output_ext=None,
                 output_basename=None,
                 ydl_opts=None,
                 video_id=None,
                 **kwargs
                 ):
        output_basename = get_output_basename(output_basename=output_basename,output_ext=video_extention)
        output_filename,output_ext = os.path.splitext(output_basename)
        
        self.url = url
        self.ydl_opts = ydl_opts or {}
        self.video_id = video_id
        self.monitoring = True
        self.pause_event = threading.Event()
        self.get_download = download_video
        self.get_info = get_info
        self.user_agent = user_agent
        self.title = title
        self.auto_file_gen = auto_file_gen
        self.standalone_download = standalone_download
        self.video_extention = output_ext
        self.output_basename = output_basename
        self.download_directory = download_directory
        self.output_filename = output_filename  # New parameter for custom filename
        self.header = {}  # Placeholder for UserAgentManagerSingleton if needed
        self.outtmpl = os.path.join(self.download_directory,self.output_basename)
        self.video_urls = [self.url]
        self.info = {}
        self.starttime = None
        self.downloaded = 0
        self.video_urls = url if isinstance(url, list) else [url]
        self.send_to_dl()
        


    def send_to_dl(self):
        if self.standalone_download:
            self.standalone_downloader()
        else:
            self.start()

    def get_headers(self, url):
        response = requests.get(url)
        if response.status_code == 200:
            return response.headers
        else:
            logger.error(f"Failed to retrieve headers for {url}. Status code: {response.status_code}")
            return {}

    @staticmethod
    def get_directory_path(directory, name, video_extention):
        file_path = os.path.join(directory, f"{name}.{video_extention}")
        i = 0
        while os.path.exists(file_path):
            file_path = os.path.join(directory, f"{name}_{i}.{video_extention}")
            i += 1
        return file_path

    def progress_callback(self, stream, chunk, bytes_remaining):
        total_size = stream.filesize
        self.downloaded = total_size - bytes_remaining

    def download(self):
        outtmpl = self.outtmpl
        for video_url in self.video_urls:
            # Use custom filename if provided, otherwise generate a short temporary one

            ydl_opts = {
                'external_downloader': 'ffmpeg',
                'outtmpl': self.outtmpl,
                'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
                'merge_output_format': self.video_extention.lstrip('.'),
                'noprogress': True,
                'quiet': True,  # Reduce verbosity in logs
            }
            ydl_opts = {**ydl_opts, **self.ydl_opts}  # merge user opts
            try:
                with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                    self.info = ydl.extract_info(video_url, download=self.get_download)
                    self.downloading = False
                    self.starttime = get_time_stamp()  # Assuming get_time_stamp() exists
                    if self.auto_file_gen:
                        file_path = ydl.prepare_filename(self.info)
                        if self.get_info:
                            self.info['file_path'] = file_path  # Fixed typo 'aath'
                    if self.get_info:
                        self.stop()
                        
            except Exception as e:
                logger.error(f"Failed to download {video_url}: {str(e)}")
            self.stop()
        self.info['video_id']= self.video_id or self.info.get('display_id') or self.info.get('id')
        return self.info

    def monitor(self):
        while self.monitoring:
            logger.info("Monitoring...")
            self.pause_event.wait(60)  # Check every minute
            if self.starttime:
                elapsed_time = subtract_it(get_time_stamp(),self.starttime)
                if self.downloaded != 0 and elapsed_time != 0:
                    cumulative_time = add_it(self.downloaded,elapsed_time)
                    percent = divide_it(self.downloaded,cumulative_time)
                else:
                    percent = 0
                if elapsed_time != 0:
                    try:
                        downloaded_minutes = divide_it(elapsed_time,60)
                        estimated_download_minutes = divide_it(downloaded_minutes,percent)
                        estimated_download_time =  subtract_it(estimated_download_minutes,downloaded_minutes)
                    except ZeroDivisionError:
                        logger.warning("Caught a division by zero in monitor!")
                        continue
                if downloaded_minutes != 0 and subtract_it(percent,downloaded_minutes) != 0:
                    estimated_download_minutes = divide_it(downloaded_minutes,percent)
                    estimated_download_time =  subtract_it(estimated_download_minutes,downloaded_minutes)
                    logger.info(f"Estimated download time: {estimated_download_time} minutes")
                if estimated_download_time >= 1.5:
                    logger.info("Restarting download due to slow speed...")
                    self.start()  # Restart download

    def start(self):
        self.download_thread = threading.Thread(target=self.download)
        self.download_thread.daemon = True
        self.monitor_thread = threading.Thread(target=self.monitor)
        self.download_thread.start()
        self.monitor_thread.start()
        self.download_thread.join()
        self.monitor_thread.join()

    def stop(self):
        self.monitoring = False
        self.pause_event.set()

def download_image(url, save_path=None):
    """
    Downloads an image from a URL and saves it to the specified path.
    
    Args:
        url (str): The URL of the image to download
        save_path (str, optional): Path to save the image. If None, uses the filename from URL
        
    Returns:
        str: Path where the image was saved, or None if download failed
    """
    try:
        # Send GET request to the URL
        response = requests.get(url, stream=True)
        
        # Check if the request was successful
        if response.status_code == 200:
            # Set decode_content=True to automatically handle Content-Encoding
            response.raw.decode_content = True
            
            # If no save_path provided, extract filename from URL
            if save_path is None:
                # Get filename from URL
                filename = url.split('/')[-1]
                save_path = filename
            
            # Ensure the directory exists
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            
            # Write the image content to file
            with open(save_path, 'wb') as f:
                f.write(response.content)
            
            print(f"Image successfully downloaded to {save_path}")
            return save_path
        else:
            print(f"Failed to download image. Status code: {response.status_code}")
            return None
            
    except requests.exceptions.RequestException as e:
        print(f"Error downloading image: {str(e)}")
        return None
    except Exception as e:
        print(f"An unexpected error occurred: {str(e)}")
        return None

def get_thumbnails(directory,info):
    thumbnails_dir = os.path.join(directory,'thumbnails')
    os.makedirs(thumbnails_dir, exist_ok=True)
    thumbnails = info.get('thumbnails',[])
    for i,thumbnail_info in enumerate(thumbnails):
        thumbnail_url = thumbnail_info.get('url')
        thumbnail_base_url = thumbnail_url.split('?')[0]
        baseName = os.path.basename(thumbnail_base_url)
        fileName,ext = os.path.splitext(baseName)
        baseName = f"{fileName}{ext}"
        resolution = info['thumbnails'][i].get('resolution')
        if resolution:
            baseName = f"{resolution}_{baseName}"
        img_id = info['thumbnails'][i].get('id')
        if img_id:
            baseName = f"{img_id}_{baseName}"
        thumbnail_path = os.path.join(thumbnails_dir,baseName)
        info['thumbnails'][i]['path']=thumbnail_path
        download_image(thumbnail_url, save_path=thumbnail_path)
    return info

def optimize_video_for_safari(input_file, reencode=False):
    """
    Optimizes an MP4 file for Safari by moving the 'moov' atom to the beginning.
    Optionally, re-encodes the video for maximum compatibility.
    
    Args:
        input_file (str): Path to the original MP4 file.
        reencode (bool): If True, re-encode the video for Safari compatibility.
        
    Returns:
        str: Path to the optimized MP4 file.
    """
    tmp_dir = tempfile.mkdtemp()
    try:
        local_input = os.path.join(tmp_dir, os.path.basename(input_file))
        shutil.copy2(input_file, local_input)
        
        base, ext = os.path.splitext(local_input)
        local_output = f"{base}_optimized{ext}"
        
        if reencode:
            # Re-encoding command for maximum Safari compatibility
            command = [
                "ffmpeg", "-i", local_input,
                "-c:v", "libx264", "-profile:v", "baseline", "-level", "3.0", "-pix_fmt", "yuv420p",
                "-c:a", "aac", "-b:a", "128k",
                "-movflags", "faststart",
                local_output
            ]
        else:
            # Simple faststart with stream copy
            command = [
                "ffmpeg", "-i", local_input,
                "-c", "copy", "-movflags", "faststart",
                local_output
            ]
        
        try:
            subprocess.run(command, check=True)
            shutil.copy2(local_output, input_file)
            print(f"Optimized video saved as {input_file}")
        except subprocess.CalledProcessError as e:
            print(f"Error during optimization: {e}")
        return input_file
    finally:
        shutil.rmtree(tmp_dir)
def bool_or_default(obj,default=True):
    if obj == None:
        obj =  default
    return obj

def get_temp_id(url):
    url = str(url)
    url_length = len(url)
    len_neg = 20
    len_neg = len_neg if url_length >= len_neg else url_length
    temp_id = re.sub(r'[^\w\d.-]', '_', url)[-len_neg:]
    return temp_id
def get_temp_file_name(url):
    temp_id = get_temp_id(url)
    temp_filename = f"temp_{temp_id}.mp4"
    return temp_filename
def get_display_id(info):
    display_id = info.get('display_id') or info.get('id')
    return display_id
def get_video_title(info):
    title = info.get('title', 'video')[:30]
    return title
def get_safe_title(title):
    re_str = r'[^\w\d.-]'
    safe_title = re.sub(re_str, '_', title)
    return safe_title
def get_video_info_from_mgr(video_mgr):
    try:
        if hasattr(video_mgr,"info"):
            info = video_mgr.info
            return info
    except Exception as e:
        print(f"{e}")
        return None
    return video_mgr
def get_video_mgr(url, download_directory=None, output_filename=None,output_ext=None,output_basename=None,
                   get_info=None, download_video=None, ydl_opts=None,video_id=None,**kwargs):
    directory = download_directory or get_default_videos_dir()
    
    output_basename = get_output_basename(output_basename=output_basename,output_filename=output_filename,output_ext=output_ext)
    output_basename = output_basename or get_temp_file_name(url)
    get_info = bool_or_default(get_info)
    download_video = bool_or_default(download_video, default=False)
    video_mgr = VideoDownloader(
        url=url,
        download_directory=directory,
        download_video=download_video,
        get_info=get_info,
        output_basename=output_basename,
        ydl_opts=ydl_opts,
        video_id=None
        # pass through
    )
    return video_mgr
def downloadvideo(url, download_directory=None, output_filename=None,output_ext=None,output_basename=None,
                   get_info=None, download=True, ydl_opts=None,**kwargs):
    download = download or kwargs.get('download_video')
    output_basename = get_output_basename(output_basename=output_basename,output_filename=output_filename,output_ext=output_ext)
    video_mgr = get_video_mgr(url, directory=directory, output_basename=output_basename,
                   get_info=get_info, download_video=download,ydl_opts=ydl_opts)
    return get_video_info_from_mgr(video_mgr)
def get_video_info(url, download_directory=None, output_filename=None,output_ext=None,output_basename=None,
                   get_info=None, download_video=False, ydl_opts=None,**kwargs):
    output_basename = get_output_basename(output_basename=output_basename,output_filename=output_filename,output_ext=output_ext)
    video_mgr = get_video_mgr(url, download_directory=download_directory, output_basename=output_basename,
                   get_info=get_info, download_video=download_video,ydl_opts=ydl_opts,**kwargs)
    return get_video_info_from_mgr(video_mgr)

def get_video_id(url, download_directory=None, output_filename=None,output_ext=None,output_basename=None,
                   get_info=None, download_video=False, ydl_opts=None,**kwargs):
    output_basename = get_output_basename(output_basename=output_basename,output_filename=output_filename,output_ext=output_ext)
    video_info = get_video_info(url, download_directory=download_directory, output_basename=output_basename,
                   get_info=get_info, download_video=download_video,ydl_opts=ydl_opts,**kwargs)
    return video_info.get('id')
def dl_video(url, download_directory=None, output_filename=None,output_ext=None,output_basename=None,
             get_info=None, download_video=None, ydl_opts=None,**kwargs):
    output_basename = get_output_basename(output_basename=output_basename,output_filename=output_filename,output_ext=output_ext)
    mgr = get_video_info(
        url,
        download_directory=download_directory,
        output_basename=output_basename,
        get_info=get_info,
        download_video=download_video,
        ydl_opts=ydl_opts,**kwargs)
    return get_video_info_from_mgr(mgr)
def for_dl_video(url,download_directory=None,output_filename=None,get_info=None,download_video=None,output_ext=None,output_basename=None,**kwargs):
    get_info = bool_or_default(get_info,default=True)
    download_video =bool_or_default(download_video,default=True)
    output_basename = get_output_basename(output_basename=output_basename,output_filename=output_filename,output_ext=output_ext)
    video_mgr = dl_video(url,download_directory=download_directory,output_basename=output_basename,get_info=get_info,download_video=download_video,**kwargs)
    if get_video_info_from_mgr(video_mgr):
        return get_video_info_from_mgr(video_mgr)
    videos = soupManager(url).soup.find_all('video')
    for video in videos:
        src = video.get("src")
        video_mgr = dl_video(src,download_directory=download_directory,output_basename=output_basename,get_info=get_info,download_video=download_video,**kwargs)
        if get_video_info_from_mgr(video_mgr):
            return get_video_info_from_mgr(video_mgr)

download_video = downloadvideo
downloadVideo = downloadvideo
