import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk
import requests
import threading
import io
import os
import vlc  # Ensure you have python-vlc installed
import time  # For delay control
from pynput import mouse  # For mouse listening

# Video Player Class
class VideoPlayer:
    def __init__(self, canvas, video_url, vlc_instance, play_stop_button, seek_var=None, seek_scale=None):
        """
        Initializes the VideoPlayer.

        :param canvas: Tkinter Canvas where the video will be displayed.
        :param video_url: URL of the video to be played.
        :param vlc_instance: Shared VLC Instance object.
        :param play_stop_button: Button widget to control play/stop.
        :param seek_var: DoubleVar linked to the seek Scale.
        :param seek_scale: Scale widget for seeking.
        """
        self.canvas = canvas
        self.video_url = video_url
        self.vlc_instance = vlc_instance
        self.play_stop_button = play_stop_button
        self.seek_var = seek_var
        self.seek_scale = seek_scale
        self.player = self.vlc_instance.media_player_new()
        self.playing = False
        self.video_handle = None  # Video DOM element id
        self.seeking = False  # User interaction with seek bar
        self.after_id = None  # ID for the after callback

        if self.seek_scale and self.seek_var:
            # Bind seek scale interactions
            self.seek_scale.bind("<ButtonPress-1>", self.on_seek_start)
            self.seek_scale.bind("<ButtonRelease-1>", self.on_seek_end)

        # Bind the destroy event to handle cleanup
        self.canvas.bind("<Destroy>", self.on_destroy)

    def on_seek_start(self, event):
        """Called when the user starts interacting with the seek bar."""
        self.seeking = True

    def on_seek_end(self, event):
        """Called when the user finishes interacting with the seek bar."""
        if self.get_length() > 0:
            new_time = (self.seek_var.get() / 100) * self.get_length()
            self.player.set_time(int(new_time))
        self.seeking = False

    def on_destroy(self, event):
        """Called when the widget is destroyed. Stops playback and cancels pending tasks."""
        if self.playing:
            self.stop_video()
        # Cancel any pending seek updates
        if self.seek_scale and self.after_id:
            try:
                self.seek_scale.after_cancel(self.after_id)
            except Exception:
                pass
        # Stop VLC player
        self.player.stop()

    def start_video(self):
        """Starts video playback."""
        if self.playing:
            return
        self.playing = True

        # Get window ID and attach to VLC
        if os.name == "nt":
            self.video_handle = self.canvas.winfo_id()
            self.player.set_hwnd(self.video_handle)
        elif os.name == "posix":
            self.video_handle = self.canvas.winfo_id()
            self.player.set_xwindow(self.video_handle)
        else:
            # Implement MacOS-specific methods if needed
            pass

        # Set media
        media = self.vlc_instance.media_new(self.video_url)
        self.player.set_media(media)

        # Configure VLC to use the entire video area
        self.player.video_set_aspect_ratio("")  # Disable aspect ratio constraint
        self.player.video_set_scale(0)  # Normal scaling

        # Play video
        self.player.play()

        # Update button text
        self.play_stop_button.config(text="■")

        # Wait for video length to set up the seek bar
        if self.seek_scale and self.seek_var:
            self.wait_for_length()

    def wait_for_length(self):
        """Waits until the video length is available and sets up the seek bar."""
        length = self.get_length()
        if length > 0:
            # Configure Seek Scale from 0-100
            self.seek_scale.config(to=100)
            # Start updating the seek bar
            self.update_seek()
        else:
            # Video length not obtained yet, retry after 100ms
            self.canvas.after(100, self.wait_for_length)

    def stop_video(self):
        """Stops video playback."""
        if not self.playing:
            return
        self.playing = False
        self.player.stop()

        # Update button text
        self.play_stop_button.config(text="▶")

        # Reset the seek bar
        if self.seek_var:
            self.seek_var.set(0)

        # Cancel seek bar updates
        if self.seek_scale and self.after_id:
            try:
                self.seek_scale.after_cancel(self.after_id)
            except Exception:
                pass

    def get_length(self):
        """Returns the length of the video in milliseconds."""
        return self.player.get_length()

    def get_time(self):
        """Returns the current playback time in milliseconds."""
        return self.player.get_time()

    def update_seek(self):
        """Updates the seek bar based on the current playback time."""
        if self.playing and not self.seeking:
            current_time = self.get_time()
            length = self.get_length()
            if length > 0:
                progress = (current_time / length) * 100
                self.seek_var.set(progress)
        # Schedule the next update after 500ms
        if self.seek_scale:
            self.after_id = self.seek_scale.after(500, self.update_seek)

# Video Download Function (Asynchronous)
def download_video(video_url, code, button):
    """Downloads a video asynchronously and updates the UI upon completion."""
    if not video_url:
        print(f"Video URL not found (Code: {code}).")
        return

    def download():
        try:
            response = requests.get(video_url, stream=True)
            response.raise_for_status()

            # Create 'videos' directory if it doesn't exist
            if not os.path.exists('videos'):
                os.makedirs('videos')

            # Create filename using code
            file_name = f"{code}.mp4"
            save_path = os.path.join('videos', file_name)

            with open(save_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            print(f"Video successfully downloaded: {save_path}")

            # Update download button to green and disable it
            button.after(0, lambda: button.config(style='Green.TButton', text="Downloaded", state="disabled"))
        except Exception as e:
            print(f"Error downloading video (Code: {code}): {e}")
            button.after(0, lambda: messagebox.showerror("Download Error", f"Could not download video: {e}"))

    # Start download in a separate daemon thread
    threading.Thread(target=download, daemon=True).start()

# Data Fetching Function
def fetch_data(callback):
    """Fetches video data from Instagram and invokes the callback with the data."""
    headers = {
        'user-agent': 'Instagram 146.0.0.27.125 (iPhone12,1; iOS 13_3; en_US; en-US; scale=2.00; 1656x3584; 190542906)'
    }
    data = {
        'target_user_id': '55408673825',
        'page_size': '12'
    }
    try:
        response = requests.post('https://i.instagram.com/api/v1/clips/user/', headers=headers, data=data)
        response.raise_for_status()  # Catch HTTP errors
        response_data = response.json()

        max_id = response_data['paging_info'].get('max_id')
        print("Max ID:", max_id)

        items = []
        for i in range(len(response_data.get("items", []))):
            try:
                media = response_data["items"][i]["media"]
                image_versions = media["image_versions2"]
                candidates = image_versions.get("candidates", [])

                # If 'candidates' is a list, take the first item
                if isinstance(candidates, list) and len(candidates) > 0:
                    candidates_frame = candidates[0].get("url")
                    if candidates_frame:
                        # Also get the video URL
                        video_versions = media.get("video_versions", [])
                        if video_versions and isinstance(video_versions, list):
                            video_url = video_versions[0].get("url")
                        else:
                            video_url = None

                        # Get 'code' value
                        code = media.get("code", f"video_{i}")

                        items.append({
                            "image_url": candidates_frame,
                            "video_url": video_url,
                            "code": code
                        })
                else:
                    # If 'candidates' is directly a dictionary
                    candidates_frame = image_versions["candidates"][0]["url"]
                    # Also get the video URL
                    video_versions = media.get("video_versions", [])
                    if video_versions and isinstance(video_versions, list):
                        video_url = video_versions[0].get("url")
                    else:
                        video_url = None

                    # Get 'code' value
                    code = media.get("code", f"video_{i}")

                    items.append({
                        "image_url": candidates_frame,
                        "video_url": video_url,
                        "code": code
                    })
            except (KeyError, IndexError, TypeError) as e:
                print(f"Error fetching data for item {i}:", e)

        callback(items)

    except Exception as e:
        print("Error fetching data:", e)
        callback([])  # Return empty list on error

# Image Downloading and Adding Function
def load_images(items, frame, vlc_instance, columns=4, image_size=(600, 900)):
    """Downloads images and adds them to the GUI."""
    def download_and_display(index, item):
        image_url = item["image_url"]
        video_url = item["video_url"]
        code = item["code"]

        try:
            img_response = requests.get(image_url)
            img_response.raise_for_status()
            img_data = img_response.content
            image = Image.open(io.BytesIO(img_data))
            image = image.resize(image_size, Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(image)

            # Tkinter updates must occur in the main thread using 'after'
            def add_image():
                row = index // columns
                col = index % columns

                # Create Image Frame
                image_frame = ttk.Frame(frame, relief="raised", borderwidth=2)
                image_frame.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
                frame.grid_rowconfigure(row, weight=1)
                frame.grid_columnconfigure(col, weight=1)

                # Create Canvas for Video and Buttons
                video_canvas = tk.Canvas(image_frame, width=image_size[0], height=image_size[1], highlightthickness=0)
                video_canvas.pack(fill="both", expand=True)

                # Add Image to Canvas
                video_canvas.create_image(0, 0, anchor="nw", image=photo)
                video_canvas.image = photo  # Keep a reference

                # Create Frame for Buttons and place it at the top-right corner
                buttons_frame = ttk.Frame(image_frame)
                buttons_frame.place(relx=0.85, rely=0.01)

                # Play/Stop Button
                play_stop_button = ttk.Button(buttons_frame, text="▶", width=3)
                play_stop_button.pack(side="top", pady=2)

                # Download Button
                if video_url:
                    download_button = ttk.Button(buttons_frame, text="Download")
                    download_button.config(command=lambda b=download_button: download_video(video_url, code, b))
                else:
                    download_button = ttk.Button(buttons_frame, text="Download", state="disabled")
                download_button.pack(side="top", pady=2)

                # Create Frame for Seek Bar
                seek_frame = ttk.Frame(image_frame)
                seek_frame.pack(fill="x", padx=5, pady=5)

                # Create Seek Bar Scale
                seek_var = tk.DoubleVar()
                seek_scale = ttk.Scale(seek_frame, orient='horizontal', length=image_size[0], from_=0, to=100, variable=seek_var)
                seek_scale.pack(fill='x')

                # Initialize Video Player if video exists
                video_player = None
                if video_url:
                    video_player = VideoPlayer(video_canvas, video_url, vlc_instance, play_stop_button, seek_var, seek_scale)
                    # Add VideoPlayer instance to the app's list
                    app = frame.master.master  # Assuming frame.master is canvas, frame.master.master is ImageApp
                    if isinstance(app, ImageApp):
                        app.video_players.append(video_player)

                def toggle_video():
                    nonlocal video_player
                    if video_player and video_player.playing:
                        video_player.stop_video()
                    elif video_player:
                        video_player.start_video()
                        # Ensure buttons stay on top
                        buttons_frame.lift()

                play_stop_button.config(command=toggle_video)

            frame.after(0, add_image)
        except Exception as e:
            print(f"Error downloading image ({image_url}):", e)

    # Download and add images concurrently
    for idx, item in enumerate(items):
        threading.Thread(target=download_and_display, args=(idx, item), daemon=True).start()

# Main Application Class
class ImageApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Instagram Videos")
        self.state('zoomed')  # Maximize window (OS-specific)
        self.bind("<F11>", self.toggle_fullscreen)  # Toggle fullscreen with F11
        self.bind("<Escape>", self.exit_fullscreen)  # Exit fullscreen with Escape

        # Style Definitions
        self.style = ttk.Style()
        self.style.configure('Green.TButton', foreground='white', background='#4CAF50')  # Green style
        self.style.map('Green.TButton',
                       foreground=[('active', 'white')],
                       background=[('active', '#45a049')])

        # Shared VLC instance, initialized without audio
        self.vlc_instance = vlc.Instance('--no-xlib', '--no-audio')  # '--no-audio' turns off audio

        # Scrollable Frame Setup
        container = ttk.Frame(self)
        container.pack(fill="both", expand=True)

        canvas = tk.Canvas(container)
        scrollbar = ttk.Scrollbar(container, orient="vertical", command=canvas.yview)
        self.scrollable_frame = ttk.Frame(canvas)

        canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        self.scrollable_canvas = canvas  # Make canvas accessible

        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(
                scrollregion=canvas.bbox("all")
            )
        )

        # Initialize debounce variables for mouse scrolling
        self.last_scroll_time = 0
        self.scroll_delay = 100  # milliseconds

        # Start mouse wheel listener using Pynput
        self.start_mouse_listener(canvas)

        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

        # Initial loading message
        self.loading_label = ttk.Label(self.scrollable_frame, text="Loading images...", font=("Arial", 16))
        self.loading_label.grid(row=0, column=0, columnspan=4, padx=10, pady=10)

        # Initialize list to track VideoPlayer instances
        self.video_players = []

        # Start data fetching in a separate thread
        threading.Thread(target=self.start_fetching, daemon=True).start()

        # Handle GUI closure
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def start_mouse_listener(self, canvas):
        """Starts a global mouse wheel listener using Pynput."""
        def on_scroll(x, y, dx, dy):
            current_time = time.time() * 1000  # Current time in ms
            if current_time - self.last_scroll_time < self.scroll_delay:
                return  # Debounce: ignore if within delay
            self.last_scroll_time = current_time

            # dy: Vertical scroll (positive: down, negative: up)
            if dy > 0:
                direction = -1  # Scroll down
            elif dy < 0:
                direction = 1  # Scroll up
            else:
                direction = 0

            if direction != 0:
                # Must update Tkinter in the main thread
                canvas.after(0, lambda: canvas.yview_scroll(direction, "units"))

        def listen():
            with mouse.Listener(on_scroll=on_scroll) as listener:
                listener.join()

        # Start the mouse listener in a separate daemon thread
        listener_thread = threading.Thread(target=listen, daemon=True)
        listener_thread.start()

    def toggle_fullscreen(self, event=None):
        """Toggles fullscreen mode."""
        is_fullscreen = self.attributes("-fullscreen")
        self.attributes("-fullscreen", not is_fullscreen)

    def exit_fullscreen(self, event=None):
        """Exits fullscreen mode."""
        self.attributes("-fullscreen", False)

    def start_fetching(self):
        """Starts fetching data."""
        fetch_data(self.on_data_fetched)

    def on_data_fetched(self, items):
        """Handles data after fetching."""
        # Remove loading message
        self.scrollable_frame.after(0, self.loading_label.destroy)

        if not items:
            # Inform the user if no videos were found or data couldn't be fetched
            info_label = ttk.Label(self.scrollable_frame, text="No videos found or data could not be fetched.",
                                   font=("Arial", 14))
            info_label.grid(row=1, column=0, padx=10, pady=10)
            return

        # Load and display images
        load_images(items, self.scrollable_frame, self.vlc_instance, columns=4, image_size=(600, 900))

    def on_closing(self):
        """Stops all VideoPlayers and then destroys the main window."""
        try:
            # Stop all VideoPlayer instances
            for vp in self.video_players:
                vp.stop_video()

            # Allow some time for threads to clean up gracefully
            time.sleep(0.5)

        except Exception as e:
            print(f"Error during closing: {e}")

        finally:
            # Destroy the main window
            self.destroy()

if __name__ == "__main__":
    app = ImageApp()
    app.mainloop()

burda play tuşuna basınca oynayan videoya şık bir fulscreen butonu ekle. ona basınca full screene alsın ve video oynatılsın. tabii bu fulscreende, videodan çıkmak(programın anasayfasına aynı şekilde ulaşmak) sonraki önceki videoya gitmek, ileri geri sarmak vs ekle. şık olsun