# Third party imports
import os
from pathlib import Path, WindowsPath
import time
import ffmpeg
import json
import tomlkit
import hashlib
import numpy as np
from moviepy.editor import VideoFileClip, concatenate_videoclips
import subprocess
import ctypes
from ctypes import wintypes

# Local imports
from utils.console import print_step, print_substep

class VideoCompiler:
   def __init__(self):
       self.config = self.load_config()
       self.config_hash = self.get_config_hash()
       self.results_folder = self.config['paths']['source_folder']
       self.merge_folder = self.config['paths']['merge_folder'] 
       self.target_duration = self.config['merger']['target_duration'] * 60 # convert to seconds
       self.subreddit_durations = {}
       self.processed_videos_file = "processed_videos.json"

       os.makedirs(self.merge_folder, exist_ok=True)
       print_step(f"Looking for videos in: {self.results_folder}")
       print_step(f"Current mode: {self.config['merger']['mode']}")
       print_step(f"Current status: {self.config['merger']['status']}")

       self.processed_videos = self.load_processed_videos()

   def get_config_hash(self):
       """Get hash of current config file to detect changes"""
       try:
           with open("merger_config.toml", "rb") as f:
               return hashlib.md5(f.read()).hexdigest()
       except FileNotFoundError:
           return None

   def check_config_changed(self):
       """Check if config file has been modified"""
       current_hash = self.get_config_hash()
       if current_hash != self.config_hash:
           print_step("Config file changed! Reloading settings...")
           self.config = self.load_config()
           self.config_hash = current_hash
           self.results_folder = self.config['paths']['source_folder']
           self.merge_folder = self.config['paths']['merge_folder']
           self.target_duration = self.config['merger']['target_duration'] * 60
           print_step(f"New settings loaded:")
           print_substep(f"Mode: {self.config['merger']['mode']}")
           print_substep(f"Status: {self.config['merger']['status']}")
           print_substep(f"Target duration: {self.config['merger']['target_duration']} minutes")
           if self.config['merger']['mode'] == 'specific':
               print_substep(f"Specific subreddit: {self.config['merger']['specific_subreddit']}")
           return True
       return False

   def check_status(self):
       """Check current processing status"""
       self.check_config_changed()
       status = self.config['merger']['status']

       if status == "paused":
           print_step("Processing is paused. Waiting for resume...")
           while self.config['merger']['status'] == "paused":
               time.sleep(10)  # Check every 10 seconds
               self.check_config_changed()
           print_step("Processing resumed!")
           return True
       elif status == "stopping":
           print_step("Stop signal received. Finishing current task...")
           return False
       return True

   def wait_with_status_check(self, minutes):
       """Wait specified minutes while checking for status changes"""
       print_step(f"Waiting {minutes} minutes before next check...")
       for _ in range(minutes):
           if not self.check_status():  # Stop if requested
               return False
           time.sleep(60)  # Sleep for 1 minute
           if self.check_config_changed():
               print_step("Config changed during wait. Starting new cycle...")
               return True
       return True

   def load_processed_videos(self):
       """Load list of already processed videos"""
       try:
           if os.path.exists(self.processed_videos_file):
               with open(self.processed_videos_file, 'r') as f:
                   return json.load(f)
           return []
       except Exception as e:
           print_substep(f"Error loading processed videos: {str(e)}")
           return []

   def save_processed_videos(self):
       """Save list of processed videos"""
       try:
           with open(self.processed_videos_file, 'w') as f:
               json.dump(self.processed_videos, f)
       except Exception as e:
           print_substep(f"Error saving processed videos: {str(e)}")

   def load_config(self):
       """Load configuration from toml file"""
       try:
           with open("merger_config.toml", "r", encoding="utf-8") as f:
               return tomlkit.load(f)
       except FileNotFoundError:
           print_step("Config file not found. Creating default config...")
           self.create_default_config()
           return self.load_config()

   def create_default_config(self):
       """Create default configuration file"""
       doc = tomlkit.document()

       merger = tomlkit.table()
       merger.add("mode", "fastest")
       merger.add("specific_subreddit", "AmItheAsshole+relationship_advice+confession+tifu")
       merger.add("check_interval", 30)
       merger.add("target_duration", 10)
       merger.add("auto_process", True)
       merger.add("status", "running")
       doc.add("merger", merger)

       paths = tomlkit.table()
       paths.add("source_folder", r"C:\Users\buggy\Desktop\FullyAutomatedRedditVideoMakerBot-main\results")
       paths.add("merge_folder", "MergeVideo")
       doc.add("paths", paths)

       processing = tomlkit.table()
       processing.add("delete_source", False)
       processing.add("output_format", "mp4")
       processing.add("safe_mode", True)
       processing.add("finish_current_before_pause", True)
       doc.add("processing", processing)

       with open("merger_config.toml", "w", encoding="utf-8") as f:
           f.write(tomlkit.dumps(doc))

   def get_video_duration(self, video_path):
       """Get duration of video in seconds using ffmpeg"""
       try:
           probe = ffmpeg.probe(video_path)
           duration = float(probe['streams'][0]['duration'])
           print_substep(f"Duration of {os.path.basename(video_path)}: {duration:.2f} seconds")
           return duration
       except Exception as e:
           print_substep(f"Error getting duration: {str(e)}")
           return 0

   def find_video_pairs(self, subreddit_folder):
       """Find matching pairs of videos (_out.mp4 and .mp4) in a subreddit folder"""
       try:
           folder_path = os.path.join(self.results_folder, subreddit_folder)
           print_step(f"Scanning folder: {folder_path}")

           if not os.path.exists(folder_path):
               print_substep(f"Folder not found: {folder_path}")
               return {}, 0

           video_pairs = {}
           total_duration = 0

           # First find all subtitled versions (_out.mp4) and get their durations
           for file in os.listdir(folder_path):
               if file.endswith('_out.mp4'):  # Only look for subtitled versions
                   base_name = file.replace('_out.mp4', '')

                   if base_name not in video_pairs:
                       video_pairs[base_name] = {
                           'normal': None,
                           'subtitled': None,
                           'duration': 0
                       }

                   full_path = os.path.join(folder_path, file)
                   video_pairs[base_name]['subtitled'] = full_path
                   duration = self.get_video_duration(full_path)
                   video_pairs[base_name]['duration'] = duration
                   total_duration += duration

           # Now find matching normal versions but don't add their duration
           for file in os.listdir(folder_path):
               if file.endswith('.mp4') and not file.endswith('_out.mp4'):
                   base_name = file.replace('.mp4', '')
                   if base_name in video_pairs:
                       video_pairs[base_name]['normal'] = os.path.join(folder_path, file)

           # Print found pairs and total duration
           print_step(f"Found video pairs in {subreddit_folder}:")
           complete_pairs = 0
           for base_name, pair in video_pairs.items():
               if pair['normal'] and pair['subtitled']:
                   complete_pairs += 1
                   print_substep(f"Pair {complete_pairs}: {base_name}")
                   print_substep(f" Normal: {os.path.basename(pair['normal'])}")
                   print_substep(f" Subtitled: {os.path.basename(pair['subtitled'])}")
                   print_substep(f" Duration: {pair['duration']:.2f} seconds")

           print_substep(f"Total complete pairs: {complete_pairs}")
           print_substep(f"Total duration: {total_duration:.2f} seconds")

           return video_pairs, total_duration

       except Exception as e:
           print_substep(f"Error scanning folder: {str(e)}")
           return {}, 0

   def find_best_combination(self):
       """Find best combination of videos for fastest 10 minutes"""
       available_pairs = []
       total_duration = 0

       # Track remaining time properly
       for subreddit in os.listdir(self.results_folder):
           if os.path.isdir(os.path.join(self.results_folder, subreddit)):
               pairs, folder_duration = self.find_video_pairs(subreddit)
               total_duration += folder_duration
               for name, pair in pairs.items():
                   if pair['normal'] and pair['subtitled']:
                       if name not in self.processed_videos:
                           available_pairs.append({
                               'name': name,
                               'subreddit': subreddit,
                               'duration': pair['duration'],  # This is only from the _out.mp4 version
                               'files': pair
                           })

       if not available_pairs:
           print_step("No unprocessed videos found!")
           return None

       # Sort by duration (longest first for efficient packing)
       available_pairs.sort(key=lambda x: x['duration'], reverse=True)

       selected_pairs = []
       current_duration = 0
       remaining_time = self.target_duration

       # Try to get as close to target_duration as possible
       for pair in available_pairs:
           if current_duration + pair['duration'] <= self.target_duration:
               selected_pairs.append(pair)
               current_duration += pair['duration']
               remaining_time = self.target_duration - current_duration
               print_substep(f"\nAdded: {pair['name']} ({pair['duration']:.2f}s) from {pair['subreddit']}")
               print_substep(f"Current total duration: {current_duration:.2f}s")
               print_substep(f"Still need {remaining_time:.2f}s to reach target")

       if selected_pairs:
           print_substep(f"\nFinal compilation duration: {current_duration:.2f} seconds")
           if remaining_time > 0:
               print_substep(f"Could not find enough videos to fill remaining {remaining_time:.2f} seconds")

       return selected_pairs

   def process_specific_folder(self):
       """Process videos from specific folder"""
       subreddit = self.config['merger']['specific_subreddit']
       print_step(f"Processing specific folder: {subreddit}")

       pairs, duration = self.find_video_pairs(subreddit)
       if not pairs:
           print_step("No videos found in specified folder!")
           return None

       selected_pairs = []
       current_duration = 0

       for name, pair in pairs.items():
           if name not in self.processed_videos and pair['normal'] and pair['subtitled']:
               if current_duration + pair['duration'] <= self.target_duration:
                   selected_pairs.append({
                       'name': name,
                       'subreddit': subreddit,
                       'duration': pair['duration'],
                       'files': pair
                   })
                   current_duration += pair['duration']

       if selected_pairs:
           for pair in selected_pairs:
               self.processed_videos.append(pair['name'])
           self.save_processed_videos()

       return selected_pairs

   def process_videos(self):
       """Process videos based on current mode"""
       # Check for config changes before processing
       if self.check_config_changed():
           if self.config['processing']['safe_mode']:
               print_step("Config changed during operation. Restarting process...")
               return None

       mode = self.config['merger']['mode']
       if mode == "fastest":
           return self.find_best_combination()
       elif mode == "specific":
           return self.process_specific_folder()
       else:
           print_step(f"Unknown mode: {mode}")
           return None

   def splice_video(self, video_pair):
       """Create spliced version with clean intro and main content"""
       try:
           print_substep(f"Processing video: {video_pair['name']}")

           # Load both versions
           clean_video = VideoFileClip(video_pair['files']['normal'])
           subtitled_video = VideoFileClip(video_pair['files']['subtitled'])

           # Detect title end using audio analysis
           title_end = self.detect_title_end(clean_video)
           print_substep(f"Title ends at: {title_end:.2f}s")

           # Get clean intro after title from normal version
           clean_portion = clean_video.subclip(title_end)

           # Get main content from subtitled version starting at the same point
           subtitled_portion = subtitled_video.subclip(title_end)

           # Add 1 second crossfade between portions
           clean_portion = clean_portion.crossfadein(1.0)
           subtitled_portion = subtitled_portion.crossfadein(1.0)

           # Combine with transition
           final = concatenate_videoclips([clean_portion, subtitled_portion], method="compose")

           # Clean up source clips
           clean_video.close()
           subtitled_video.close()

           return final

       except Exception as e:
           print_substep(f"Error splicing video: {str(e)}")
           if 'clean_video' in locals():
               clean_video.close()
           if 'subtitled_video' in locals():
               subtitled_video.close()
           return None

   def detect_title_end(self, video):
       """Detect when title reading ends based on audio analysis"""
       try:
           # Extract audio
           audio = video.audio

           # Convert to array
           audio_array = audio.to_soundarray()

           # Parameters for detection
           window_size = int(audio.fps * 0.5)  # 0.5 second window
           threshold = 0.1  # Adjust based on testing

           # Find first silence after speech
           for i in range(0, len(audio_array) - window_size, window_size):
               window = audio_array[i:i + window_size]
               if np.max(np.abs(window)) < threshold:
                   # Convert sample position to seconds
                   return i / audio.fps

           # Fallback if no clear silence found
           return 5.0

       except Exception as e:
           print_substep(f"Error detecting title end: {str(e)}")
           return 5.0

   def merge_videos(self, selected_videos):
       """Merge selected videos with proper splicing and transitions"""
       clips = []

       try:
           # Process each video with proper splicing
           for video in selected_videos:
               spliced = self.splice_video(video)
               if spliced:
                   clips.append(spliced)

           # Add transitions between videos
            final_clips = []
            for i, clip in enumerate(clips):
                if i > 0:
                    # Add 1 second crossfade between videos
                    clip = clip.crossfadein(1.0)
                final_clips.append(clip)

            # Concatenate all clips
            final = concatenate_videoclips(final_clips, method="compose")

            # Write final video
            output_path = os.path.join(
                self.merge_folder,
                f"compilation_{int(time.time())}.mp4"
            )

            final.write_videofile(
                output_path,
                codec='libx264',
                audio_codec='aac',
                temp_audiofile='temp-audio.m4a',
                remove_temp=True,
                threads=4
            )

            return output_path

        except Exception as e:
            print_substep(f"Error merging videos: {str(e)}")
            return None
        finally:
            # Clean up all clips
            for clip in clips:
                try:
                    clip.close()
                except:
                    pass

def main():
    compiler = VideoCompiler()
    print_step("Monitoring for config changes...")
    print_substep("You can modify merger_config.toml at any time to change settings")
    print_substep("Set status to 'paused' to pause, 'running' to resume, or 'stopping' to stop")

    first_run = True

    while True:
        try:
            # Reset processed videos list at start of each cycle
            compiler.processed_videos = []

            # Check status first
            if not compiler.check_status():
                print_step("Stopping process...")
                break

            # Check if auto-processing is still enabled
            compiler.check_config_changed()
            if not compiler.config['merger']['auto_process']:
                print_step("Auto-processing disabled in config. Exiting.")
                break

            selected_videos = compiler.process_videos()

            if selected_videos:
                print_step("\nVideos Selected for Compilation:")
                total_duration = 0
                for video in selected_videos:
                    print_substep(f"Video: {video['name']}")
                    print_substep(f"From: {video['subreddit']}")
                    print_substep(f"Duration: {video['duration']:.2f} seconds")
                    print_substep("-" * 50)
                    total_duration += video['duration']

                print_step(f"Total Compilation Duration: {total_duration:.2f} seconds")

                # Only wait if not first run
                if not first_run:
                    check_interval = compiler.config['merger']['check_interval']
                    if not compiler.wait_with_status_check(check_interval):
                        break
                else:
                    first_run = False
            else:
                print_step("Not enough unprocessed videos for compilation.")
                check_interval = compiler.config['merger']['check_interval']
                if not compiler.wait_with_status_check(check_interval):
                    break

        except Exception as e:
            print_step(f"Error occurred: {str(e)}")
            print_step("Waiting 5 minutes before retrying...")
            if not compiler.wait_with_status_check(5):
                break

if __name__ == "__main__":
    main(), Unindent amount does not match previous indentPylance
(variable) final_clips: list, Unindent amount does not match previous indentPylance