"""
Update Checker Module for Progression Loader
Checks for new releases from https://git.hudsonriggs.systems/HRiggs/ProgressionMods/releases
"""

import requests
import json
import re
from packaging import version
import tkinter as tk
from tkinter import messagebox
import webbrowser
import threading
import time
from datetime import datetime, timedelta
import os
from pathlib import Path
from update_config import get_update_config
from ui_theme import COLORS, FONT_FAMILY, style_text_button, center_window

class UpdateChecker:
    def __init__(self, current_version=None):
        # Load configuration
        self.config = get_update_config()
        
        # Set version
        self.current_version = current_version or self.config["current_version"]
        
        # API configuration
        self.api_base_url = self.config["api_base_url"]
        self.repo_owner = self.config["repo_owner"]
        self.repo_name = self.config["repo_name"]
        self.releases_url = f"{self.api_base_url}/repos/{self.repo_owner}/{self.repo_name}/releases"
        
        # Check configuration
        self.last_check_file = Path(self.config["last_check_file"]) if self.config["last_check_file"] else None
        self.check_interval_hours = self.config["check_interval_hours"]
        self.include_prereleases = self.config["include_prereleases"]
        
        # Request configuration
        self.user_agent = self.config["user_agent"]
        self.request_timeout = self.config["request_timeout"]
        
    def get_current_version(self):
        """Get the current version of the application"""
        return self.current_version
    
    def set_current_version(self, version_string):
        """Set the current version of the application"""
        self.current_version = version_string
    
    def should_check_for_updates(self):
        """Check if enough time has passed since last update check"""
        # If no persistent file or check interval is 0, always check
        if not self.last_check_file or self.check_interval_hours == 0:
            return True
            
        if not self.last_check_file.exists():
            return True
            
        try:
            with open(self.last_check_file, 'r') as f:
                data = json.load(f)
                last_check = datetime.fromisoformat(data.get('last_check', '2000-01-01'))
                return datetime.now() - last_check > timedelta(hours=self.check_interval_hours)
        except (json.JSONDecodeError, ValueError, KeyError):
            return True
    
    def save_last_check_time(self):
        """Save the current time as the last check time"""
        # Skip saving if no persistent file is configured
        if not self.last_check_file:
            return
            
        try:
            data = {'last_check': datetime.now().isoformat()}
            with open(self.last_check_file, 'w') as f:
                json.dump(data, f)
        except Exception as e:
            print(f"Could not save last check time: {e}")
    
    def fetch_latest_release(self):
        """Fetch the latest release information from the API"""
        try:
            headers = {
                'Accept': 'application/json',
                'User-Agent': self.user_agent
            }
            
            response = requests.get(self.releases_url, headers=headers, timeout=self.request_timeout)
            response.raise_for_status()
            
            releases = response.json()
            
            if not releases:
                return None
            
            # Filter releases based on prerelease setting
            if not self.include_prereleases:
                releases = [r for r in releases if not r.get('prerelease', False)]
            
            # Filter out draft releases
            releases = [r for r in releases if not r.get('draft', False)]
            
            if not releases:
                return None
                
            # Get the latest release (first in the filtered list)
            latest_release = releases[0]
            
            return {
                'version': latest_release.get('tag_name', '').lstrip('v'),
                'name': latest_release.get('name', ''),
                'body': latest_release.get('body', ''),
                'html_url': latest_release.get('html_url', ''),
                'published_at': latest_release.get('published_at', ''),
                'assets': latest_release.get('assets', []),
                'prerelease': latest_release.get('prerelease', False),
                'draft': latest_release.get('draft', False)
            }
            
        except requests.exceptions.RequestException as e:
            print(f"Network error checking for updates: {e}")
            return None
        except json.JSONDecodeError as e:
            print(f"Error parsing release data: {e}")
            return None
        except Exception as e:
            print(f"Unexpected error checking for updates: {e}")
            return None
    
    def is_newer_version(self, latest_version):
        """Compare versions to see if the latest is newer than current"""
        try:
            current = version.parse(self.current_version)
            latest = version.parse(latest_version)
            return latest > current
        except Exception as e:
            print(f"Error comparing versions: {e}")
            # Fallback to string comparison
            return latest_version != self.current_version
    
    def check_for_updates_async(self, callback=None):
        """Check for updates in a background thread"""
        def check_thread():
            try:
                if not self.should_check_for_updates():
                    if callback:
                        callback(None, "No check needed")
                    return
                
                latest_release = self.fetch_latest_release()
                self.save_last_check_time()
                
                if callback:
                    callback(latest_release, None)
                    
            except Exception as e:
                if callback:
                    callback(None, str(e))
        
        thread = threading.Thread(target=check_thread, daemon=True)
        thread.start()
    
    def check_for_updates_sync(self):
        """Check for updates synchronously"""
        if not self.should_check_for_updates():
            return None, "No check needed"
            
        latest_release = self.fetch_latest_release()
        self.save_last_check_time()
        
        return latest_release, None

    def _show_notice_dialog(self, parent_window, title, message, accent_color):
        """Show a small themed notice dialog."""
        dialog = tk.Toplevel(parent_window)
        dialog.title(title)
        dialog.configure(bg=COLORS['bg_primary'])
        dialog.resizable(False, False)
        dialog.attributes('-topmost', True)

        panel = tk.Frame(
            dialog,
            bg=COLORS['bg_card'],
            highlightbackground=COLORS['border_light'],
            highlightcolor=COLORS['border_light'],
            highlightthickness=1,
        )
        panel.pack(fill='both', expand=True, padx=16, pady=16)

        tk.Label(
            panel,
            text=title,
            fg=accent_color,
            bg=COLORS['bg_card'],
            font=(FONT_FAMILY, 18, 'bold italic'),
        ).pack(pady=(18, 10))

        tk.Label(
            panel,
            text=message,
            fg=COLORS['text_body'],
            bg=COLORS['bg_card'],
            font=(FONT_FAMILY, 11),
            justify='center',
            wraplength=340,
        ).pack(padx=24, pady=(0, 18))

        close_btn = tk.Button(
            panel,
            text="Close",
            command=dialog.destroy,
            bg=COLORS['bg_card'],
            font=(FONT_FAMILY, 12, 'bold italic'),
            padx=0,
            pady=6,
        )
        style_text_button(close_btn, COLORS['accent_green'], COLORS['bg_card'])
        close_btn.pack(pady=(0, 18))

        center_window(dialog, 420, 220, parent_window)
        dialog.protocol("WM_DELETE_WINDOW", dialog.destroy)
        return dialog
    
    def show_update_dialog(self, parent_window, release_info):
        """Show an update notification dialog"""
        if not release_info:
            return
            
        latest_version = release_info['version']
        
        if not self.is_newer_version(latest_version):
            return  # No update needed
        
        # Create update dialog
        dialog = tk.Toplevel(parent_window)
        dialog.title("Update Available - Progression Loader")
        dialog.configure(bg=COLORS['bg_primary'])
        dialog.resizable(False, False)
        dialog.attributes('-topmost', True)
        
        # Set window icon if available
        try:
            if hasattr(parent_window, 'iconbitmap'):
                dialog.iconbitmap(parent_window.iconbitmap())
        except:
            pass
        
        dialog_width = 500
        dialog_height = 430
        center_window(dialog, dialog_width, dialog_height, parent_window)

        panel = tk.Frame(
            dialog,
            bg=COLORS['bg_card'],
            highlightbackground=COLORS['border_light'],
            highlightcolor=COLORS['border_light'],
            highlightthickness=1,
        )
        panel.pack(fill='both', expand=True, padx=16, pady=16)

        title_label = tk.Label(
            panel,
            text="Update Available",
            fg=COLORS['text_highlight'],
            bg=COLORS['bg_card'],
            font=(FONT_FAMILY, 18, 'bold italic'),
        )
        title_label.pack(pady=20)
        
        # Version info
        version_frame = tk.Frame(panel, bg=COLORS['bg_card'])
        version_frame.pack(pady=10)
        
        current_label = tk.Label(version_frame,
                                text=f"Current Version: {self.current_version}",
                                fg=COLORS['text_body'], bg=COLORS['bg_card'],
                                font=(FONT_FAMILY, 12))
        current_label.pack()
        
        latest_label = tk.Label(version_frame,
                               text=f"Latest Version: {latest_version}",
                               fg=COLORS['accent_green'], bg=COLORS['bg_card'],
                               font=(FONT_FAMILY, 12, 'bold'))
        latest_label.pack()
        
        # Release notes
        if release_info.get('body'):
            notes_label = tk.Label(panel,
                                  text="Release Notes:",
                                  fg=COLORS['text_primary'], bg=COLORS['bg_card'],
                                  font=(FONT_FAMILY, 12, 'bold italic'))
            notes_label.pack(pady=(20, 5))
            
            # Create scrollable text widget for release notes
            notes_frame = tk.Frame(panel, bg=COLORS['bg_card'])
            notes_frame.pack(fill='both', expand=True, padx=20, pady=(0, 20))
            
            notes_text = tk.Text(notes_frame,
                                height=8,
                                bg=COLORS['bg_tertiary'], fg=COLORS['text_body'],
                                font=(FONT_FAMILY, 10),
                                wrap=tk.WORD,
                                state='disabled',
                                relief='flat',
                                bd=0,
                                insertbackground=COLORS['text_primary'],
                                highlightthickness=1,
                                highlightbackground=COLORS['border_light'])
            
            scrollbar = tk.Scrollbar(
                notes_frame,
                bg=COLORS['bg_tertiary'],
                troughcolor=COLORS['bg_secondary'],
                activebackground=COLORS['bg_hover'],
            )
            scrollbar.pack(side='right', fill='y')
            
            notes_text.pack(side='left', fill='both', expand=True)
            notes_text.config(yscrollcommand=scrollbar.set)
            scrollbar.config(command=notes_text.yview)
            
            # Insert release notes
            notes_text.config(state='normal')
            notes_text.insert('1.0', release_info['body'])
            notes_text.config(state='disabled')
        
        # Buttons
        button_frame = tk.Frame(panel, bg=COLORS['bg_card'])
        button_frame.pack(pady=20)
        
        def download_update():
            webbrowser.open(release_info['html_url'])
            dialog.destroy()
        
        def remind_later():
            # If no persistent file, just close the dialog
            if not self.last_check_file:
                dialog.destroy()
                return
                
            # Reset last check time to check again sooner
            try:
                data = {'last_check': (datetime.now() - timedelta(hours=self.check_interval_hours - 4)).isoformat()}
                with open(self.last_check_file, 'w') as f:
                    json.dump(data, f)
            except:
                pass
            dialog.destroy()
        
        def skip_version():
            # If no persistent file, just close the dialog
            if not self.last_check_file:
                dialog.destroy()
                return
                
            # Save this version as skipped
            try:
                data = {
                    'last_check': datetime.now().isoformat(),
                    'skipped_version': latest_version
                }
                with open(self.last_check_file, 'w') as f:
                    json.dump(data, f)
            except:
                pass
            dialog.destroy()
        
        download_btn = tk.Button(button_frame,
                                text="Download Update",
                                command=download_update,
                                bg=COLORS['bg_card'],
                                font=(FONT_FAMILY, 12, 'bold italic'),
                                padx=0, pady=6)
        style_text_button(download_btn, COLORS['accent_green'], COLORS['bg_card'])
        download_btn.pack(side='left', padx=5)
        
        # Only show remind/skip buttons if persistence is enabled
        if self.last_check_file:
            later_btn = tk.Button(button_frame,
                                 text="Remind Later",
                                 command=remind_later,
                                 bg=COLORS['bg_card'],
                                 font=(FONT_FAMILY, 12, 'bold italic'),
                                 padx=0, pady=6)
            style_text_button(later_btn, COLORS['accent_yellow'], COLORS['bg_card'])
            later_btn.pack(side='left', padx=5)
            
            skip_btn = tk.Button(button_frame,
                                text="Skip Version",
                                command=skip_version,
                                bg=COLORS['bg_card'],
                                font=(FONT_FAMILY, 12, 'bold italic'),
                                padx=0, pady=6)
            style_text_button(skip_btn, COLORS['accent_red'], COLORS['bg_card'])
            skip_btn.pack(side='left', padx=5)
        else:
            close_btn = tk.Button(button_frame,
                                 text="Close",
                                 command=dialog.destroy,
                                 bg=COLORS['bg_card'],
                                 font=(FONT_FAMILY, 12, 'bold italic'),
                                 padx=0, pady=6)
            style_text_button(close_btn, COLORS['accent_red'], COLORS['bg_card'])
            close_btn.pack(side='left', padx=5)
        
        # Handle window close
        dialog.protocol("WM_DELETE_WINDOW", dialog.destroy)
        
        return dialog
    
    def should_skip_version(self, version_string):
        """Check if this version should be skipped"""
        # Skip version checking if no persistent file is configured
        if not self.last_check_file:
            return False
            
        try:
            with open(self.last_check_file, 'r') as f:
                data = json.load(f)
                return data.get('skipped_version') == version_string
        except:
            return False
    
    def manual_check_for_updates(self, parent_window):
        """Manually check for updates and show result"""
        def check_complete(release_info, error):
            if error:
                self._show_notice_dialog(
                    parent_window,
                    "Update Check Failed",
                    f"Could not check for updates:\n{error}",
                    COLORS['accent_red'],
                )
                return
            
            if not release_info:
                self._show_notice_dialog(
                    parent_window,
                    "No Updates",
                    "Could not retrieve release information.",
                    COLORS['accent_yellow'],
                )
                return
            
            latest_version = release_info['version']
            
            if self.is_newer_version(latest_version) and not self.should_skip_version(latest_version):
                self.show_update_dialog(parent_window, release_info)
            else:
                self._show_notice_dialog(
                    parent_window,
                    "Up To Date",
                    f"You are running the latest version ({self.current_version}).",
                    COLORS['accent_green'],
                )
        
        # Show checking message
        checking_dialog = tk.Toplevel(parent_window)
        checking_dialog.title("Checking for Updates")
        checking_dialog.configure(bg=COLORS['bg_primary'])
        checking_dialog.resizable(False, False)
        checking_dialog.attributes('-topmost', True)

        panel = tk.Frame(
            checking_dialog,
            bg=COLORS['bg_card'],
            highlightbackground=COLORS['border_light'],
            highlightcolor=COLORS['border_light'],
            highlightthickness=1,
        )
        panel.pack(fill='both', expand=True, padx=14, pady=14)

        label = tk.Label(
            panel,
            text="Checking for updates...",
            fg=COLORS['text_primary'],
            bg=COLORS['bg_card'],
            font=(FONT_FAMILY, 12, 'bold italic'),
        )
        label.pack(expand=True)

        center_window(checking_dialog, 320, 120, parent_window)
        
        def check_and_close():
            release_info, error = self.check_for_updates_sync()
            parent_window.after(0, lambda: checking_dialog.destroy())
            parent_window.after(0, lambda: check_complete(release_info, error))
        
        # Start check in background
        threading.Thread(target=check_and_close, daemon=True).start()


def integrate_update_checker_with_gui(gui_class):
    """
    Decorator to integrate update checker with the main GUI class
    """
    original_init = gui_class.__init__
    
    def new_init(self, *args, **kwargs):
        # Call original init
        original_init(self, *args, **kwargs)
        
        # Initialize update checker
        self.update_checker = UpdateChecker()
        
        # Add update check to menu if it exists
        self.add_update_menu()
        
        # Check for updates on startup (after a short delay)
        self.root.after(5000, self.check_for_updates_on_startup)
    
    def add_update_menu(self):
        """Add update check option to the application"""
        try:
            # Try to add to existing menu bar
            if hasattr(self, 'menubar'):
                help_menu = tk.Menu(self.menubar, tearoff=0)
                help_menu.add_command(label="Check for Updates", command=self.manual_update_check)
                self.menubar.add_cascade(label="Help", menu=help_menu)
            else:
                # Create a simple button in the GUI
                self.add_update_button()
        except Exception as e:
            print(f"Could not add update menu: {e}")
    
    def add_update_button(self):
        """Add an update check button to the GUI"""
        try:
            # Find a suitable parent frame (try footer first, then main frame)
            parent_frame = None
            if hasattr(self, 'footer_frame'):
                parent_frame = self.footer_frame
            elif hasattr(self, 'main_frame'):
                parent_frame = self.main_frame
            elif hasattr(self, 'root'):
                parent_frame = self.root
            
            if parent_frame:
                update_btn = tk.Button(parent_frame,
                                     text="Check Updates",
                                     command=self.manual_update_check,
                                     bg=COLORS['bg_card'],
                                     font=(FONT_FAMILY, 10, 'bold italic'),
                                     padx=8, pady=3)
                style_text_button(update_btn, COLORS['accent_yellow'], COLORS['bg_card'])
                update_btn.pack(side='right', padx=5, pady=5)
        except Exception as e:
            print(f"Could not add update button: {e}")
    
    def check_for_updates_on_startup(self):
        """Check for updates when the application starts"""
        def update_callback(release_info, error):
            if error or not release_info:
                return  # Silently fail on startup
            
            latest_version = release_info['version']
            if (self.update_checker.is_newer_version(latest_version) and 
                not self.update_checker.should_skip_version(latest_version)):
                # Show update dialog
                self.update_checker.show_update_dialog(self.root, release_info)
        
        self.update_checker.check_for_updates_async(update_callback)
    
    def manual_update_check(self):
        """Manually triggered update check"""
        self.update_checker.manual_check_for_updates(self.root)
    
    # Add methods to the class
    gui_class.add_update_menu = add_update_menu
    gui_class.add_update_button = add_update_button
    gui_class.check_for_updates_on_startup = check_for_updates_on_startup
    gui_class.manual_update_check = manual_update_check
    gui_class.__init__ = new_init
    
    return gui_class
