Remove use of API on this script, but maintain every function of this one, send me all updated scripte : 
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import yfinance as yf
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from datetime import datetime
import pandas as pd
import feedparser
from ta.trend import SMAIndicator
from ta.momentum import RSIIndicator
from statsmodels.tsa.arima.model import ARIMA
import threading
import time
import webbrowser
import re
import logging
import requests  # Added for fetching macroeconomic data
from threading import Event

# Configure logging
logging.basicConfig(
    filename='finance_app.log',
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s:%(message)s'
)

class FinanceApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Analyse Financière")
        self.root.geometry("1300x900")  # Increased to accommodate new widgets
        self.alert_thresholds = []  # List to store multiple alert thresholds
        self.alert_active = False
        self.alert_lock = threading.Lock()  # For thread safety
        self.stop_event = Event()  # Event to signal thread termination

        # Define multiple news sources with categories
        self.news_sources = {
            "BBC Business": {"url": "http://feeds.bbci.co.uk/news/business/rss.xml", "category": "Business"},
            "Reuters Business": {"url": "http://feeds.reuters.com/reuters/businessNews", "category": "Business"},
            "CNBC": {"url": "https://www.cnbc.com/id/100003114/device/rss/rss.html", "category": "Business"},
            "Bloomberg": {"url": "https://www.bloomberg.com/feed/podcast/etf-report.xml", "category": "Business"},  # Example feed
            "TechCrunch": {"url": "http://feeds.feedburner.com/TechCrunch/", "category": "Technology"},  # Added a technology source
            "Custom": {"url": "", "category": "Custom"}  # Option to add a custom RSS URL
        }

        # Define possible categories
        self.categories = ["All", "Business", "Technology", "Politics", "Health", "Custom"]

        self.create_widgets()  # Now safe to call create_widgets
        self.news_data = []
        self.filtered_news = []  # To map listbox entries to actual news data

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

    def create_widgets(self):
        # Use a Notebook for tabs
        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True)

        # Stock Data Tab
        stock_tab = ttk.Frame(notebook)
        notebook.add(stock_tab, text='Données Boursières')
        self.create_stock_widgets(stock_tab)

        # Macroeconomic Indicators Tab
        macro_tab = ttk.Frame(notebook)
        notebook.add(macro_tab, text='Macroéconomie')
        self.create_macro_widgets(macro_tab)

        # Economic News Tab
        news_tab = ttk.Frame(notebook)
        notebook.add(news_tab, text='Actualités Économiques')
        self.create_news_widgets(news_tab)

        # Status Bar
        self.status_var = tk.StringVar()
        self.status_var.set("Prêt")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor='w')
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # Progress Bar
        self.progress = ttk.Progressbar(self.root, mode='indeterminate')
        self.progress.pack(fill=tk.X)

    ## Stock Data Tab ##
    def create_stock_widgets(self, parent):
        control_frame = ttk.Frame(parent)
        control_frame.pack(pady=10, padx=10, fill=tk.X)

        # Stock Symbol
        ttk.Label(control_frame, text="Symbole Boursier:").grid(row=0, column=0, padx=5, pady=5, sticky='E')
        self.symbol_entry = ttk.Entry(control_frame, width=20)
        self.symbol_entry.grid(row=0, column=1, padx=5, pady=5, sticky='W')
        self.symbol_entry.insert(0, "AAPL")  # Default symbol

        # Start Date
        ttk.Label(control_frame, text="Date de début (YYYY-MM-DD):").grid(row=1, column=0, padx=5, pady=5, sticky='E')
        self.start_entry = ttk.Entry(control_frame, width=20)
        self.start_entry.grid(row=1, column=1, padx=5, pady=5, sticky='W')
        self.start_entry.insert(0, "2020-01-01")

        # End Date
        ttk.Label(control_frame, text="Date de fin (YYYY-MM-DD):").grid(row=2, column=0, padx=5, pady=5, sticky='E')
        self.end_entry = ttk.Entry(control_frame, width=20)
        self.end_entry.grid(row=2, column=1, padx=5, pady=5, sticky='W')
        self.end_entry.insert(0, datetime.today().strftime('%Y-%m-%d'))

        # Fetch and Display Button
        self.fetch_button = ttk.Button(control_frame, text="Récupérer et Afficher", command=self.fetch_data)
        self.fetch_button.grid(row=3, column=0, columnspan=2, pady=10)

        # Frame for the plot
        self.plot_frame = ttk.Frame(parent)
        self.plot_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Alert Section
        alert_frame = ttk.LabelFrame(parent, text="Alertes Personnalisées")
        alert_frame.pack(pady=10, padx=10, fill=tk.X)

        ttk.Label(alert_frame, text="Alerte: Prix ≥ ").grid(row=0, column=0, padx=5, pady=5, sticky='E')
        self.alert_entry = ttk.Entry(alert_frame, width=15)
        self.alert_entry.grid(row=0, column=1, padx=5, pady=5, sticky='W')
        self.alert_entry.insert(0, "150")  # Default value

        self.set_alert_button = ttk.Button(alert_frame, text="Définir Alerte", command=self.set_alert)
        self.set_alert_button.grid(row=0, column=2, padx=5, pady=5)

        # Listbox for defined alerts
        self.alerts_listbox = tk.Listbox(alert_frame, height=5)
        self.alerts_listbox.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky='WE')

        self.remove_alert_button = ttk.Button(alert_frame, text="Supprimer Alerte Sélectionnée",
                                            command=self.remove_selected_alert)
        self.remove_alert_button.grid(row=2, column=0, columnspan=3, pady=5)

    def fetch_data(self):
        # Disable fetch button and show status
        self.fetch_button.config(state='disabled')
        self.set_status("Récupération des données...")
        self.progress.start()
        threading.Thread(target=self.fetch_data_thread, daemon=True).start()

    def fetch_data_thread(self):
        symbol = self.symbol_entry.get().strip().upper()
        start_date = self.start_entry.get().strip()
        end_date = self.end_entry.get().strip()

        # Validate symbol
        if not re.match("^[A-Z0-9.-]{1,10}$", symbol):
            self.show_error("Erreur", "Veuillez entrer un symbole boursier valide et unique (lettres, chiffres, points, tirets).")
            self.update_fetch_status(False)
            return

        # Validate dates
        try:
            start_dt = datetime.strptime(start_date, '%Y-%m-%d')
            end_dt = datetime.strptime(end_date, '%Y-%m-%d')
            if start_dt >= end_dt:
                self.show_error("Erreur", "La date de début doit être antérieure à la date de fin.")
                self.update_fetch_status(False)
                return
            if end_dt > datetime.today():
                self.show_error("Erreur", "La date de fin ne peut pas être dans le futur.")
                self.update_fetch_status(False)
                return
        except ValueError:
            self.show_error("Erreur", "Veuillez entrer des dates au format YYYY-MM-DD.")
            self.update_fetch_status(False)
            return

        try:
            data = yf.download(symbol, start=start_date, end=end_date)
            if data.empty:
                self.show_error("Erreur", "Aucune donnée trouvée pour ce symbole.")
                self.update_fetch_status(False)
                return

            # Compute technical indicators
            data = self.compute_technical_indicators(data)

            # Schedule plot_data to run in the main thread
            self.root.after(0, lambda: self.plot_data(data, symbol))
            self.update_fetch_status(True)
        except Exception as e:
            logging.error(f"Erreur lors de la récupération des données: {e}")
            self.show_error("Erreur", f"Impossible de récupérer les données : {e}")
            self.update_fetch_status(False)

    def update_fetch_status(self, success):
        # Re-enable fetch button and update status
        if success:
            self.set_status("Données récupérées avec succès.")
        else:
            self.set_status("Erreur lors de la récupération des données.")
        self.fetch_button.config(state='normal')
        self.progress.stop()

    def compute_technical_indicators(self, data):
        try:
            if 'Close' not in data.columns:
                raise ValueError("La colonne 'Close' est absente des données téléchargées.")
            
            data_close = data['Close']
            if isinstance(data_close, pd.DataFrame):
                data_close = data_close.iloc[:, 0]

            if data_close.empty:
                raise ValueError("La série temporelle 'Close' est vide.")

            # Calculate 20-day Simple Moving Average (SMA)
            sma = SMAIndicator(close=data_close, window=20)
            data['SMA20'] = sma.sma_indicator()

            # Calculate 14-day Relative Strength Index (RSI)
            rsi = RSIIndicator(close=data_close, window=14)
            data['RSI'] = rsi.rsi()
        except Exception as e:
            logging.error(f"Erreur lors du calcul des indicateurs techniques: {e}")
            self.show_error("Erreur", f"Erreur lors du calcul des indicateurs techniques : {e}")
        return data

    def plot_data(self, data, symbol):
        try:
            # Clear previous plot
            for widget in self.plot_frame.winfo_children():
                widget.destroy()

            plt.close('all')  # Close previous figures to prevent memory leaks

            fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)

            # Plot Close Prices and SMA
            ax1.plot(data.index, data['Close'], label='Prix de Clôture')
            ax1.plot(data.index, data['SMA20'], label='SMA 20')
            ax1.set_title(f"Prix de Clôture Historique de {symbol} avec SMA 20")
            ax1.set_ylabel("Prix (USD)")
            ax1.legend()
            ax1.grid(True)

            # Plot RSI
            ax2.plot(data.index, data['RSI'], label='RSI', color='orange')
            ax2.axhline(70, color='red', linestyle='--')
            ax2.axhline(30, color='green', linestyle='--')
            ax2.set_title("Indice de Force Relative (RSI)")
            ax2.set_ylabel("RSI")
            ax2.legend()
            ax2.grid(True)

            fig.tight_layout()

            canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)
            canvas.draw()
            canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

            # Add forecast button
            forecast_button = ttk.Button(self.plot_frame, text="Afficher Prévisions",
                                         command=lambda: self.plot_forecast(data, symbol))
            forecast_button.pack(pady=5)
        except Exception as e:
            logging.error(f"Erreur lors de l'affichage des graphiques: {e}")
            self.show_error("Erreur", f"Erreur lors de l'affichage des graphiques : {e}")

    def plot_forecast(self, data, symbol, periods=30):
        # Disable forecast button and show status
        self.set_status("Génération des prévisions...")
        self.progress.start()
        threading.Thread(target=self.plot_forecast_thread, args=(data, symbol, periods), daemon=True).start()

    def plot_forecast_thread(self, data, symbol, periods):
        try:
            # Prepare data for ARIMA
            df = data['Close'].dropna()
            if isinstance(df, pd.DataFrame):
                df = df.iloc[:, 0]  # Assume first column if multiple
            required_length = 30  # Minimum length for ARIMA
            if len(df) < required_length:
                self.show_error("Erreur", f"Données insuffisantes pour générer des prévisions. Au moins {required_length} points de données sont requis.")
                self.update_fetch_status(False)
                return

            model = ARIMA(df, order=(5, 1, 0))
            model_fit = model.fit()
            forecast = model_fit.forecast(steps=periods)

            # Dates for forecasts (business days)
            last_date = df.index[-1]
            forecast_dates = pd.bdate_range(last_date + pd.Timedelta(days=1), periods=periods)

            # Create the plot
            fig, ax = plt.subplots(figsize=(10, 6))
            ax.plot(data.index, data['Close'], label='Prix de Clôture')
            ax.plot(forecast_dates, forecast, label='Prévisions', linestyle='--', color='red')
            ax.set_title(f"Prévision des Prix de {symbol} avec ARIMA")
            ax.set_xlabel("Date")
            ax.set_ylabel("Prix (USD)")
            ax.legend()
            ax.grid(True)

            fig.tight_layout()

            # Schedule the creation of forecast window in the main thread
            self.root.after(0, lambda: self.display_forecast(fig))
            self.set_status("Prévisions générées avec succès.")
        except Exception as e:
            logging.error(f"Erreur lors de la génération des prévisions: {e}")
            self.show_error("Erreur", f"Impossible de générer les prévisions : {e}")
            self.set_status("Erreur lors de la génération des prévisions.")
        finally:
            self.progress.stop()

    def display_forecast(self, fig):
        try:
            # Display in a new window
            forecast_window = tk.Toplevel(self.root)
            forecast_window.title("Prévisions des Prix")
            forecast_window.geometry("800x600")

            canvas = FigureCanvasTkAgg(fig, master=forecast_window)
            canvas.draw()
            canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

            # Add a close button
            close_button = ttk.Button(forecast_window, text="Fermer", command=forecast_window.destroy)
            close_button.pack(pady=5)
        except Exception as e:
            logging.error(f"Erreur lors de l'affichage des prévisions: {e}")
            self.show_error("Erreur", f"Erreur lors de l'affichage des prévisions : {e}")

    ## Macroeconomic Indicators Tab ##
    def create_macro_widgets(self, parent):
        macro_frame = ttk.Frame(parent)
        macro_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Section Title
        ttk.Label(macro_frame, text="Indicateurs Macroéconomiques", font=("Helvetica", 16)).pack(pady=10)

        # Indicators Table
        self.macro_tree = ttk.Treeview(macro_frame, columns=("Indicateur", "Valeur"), show='headings')
        self.macro_tree.heading("Indicateur", text="Indicateur")
        self.macro_tree.heading("Valeur", text="Valeur")
        self.macro_tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Fetch Indicators Button
        self.fetch_macro_button = ttk.Button(macro_frame, text="Récupérer Indicateurs", command=self.fetch_macro_data)
        self.fetch_macro_button.pack(pady=5)

    def fetch_macro_data(self):
        # Disable fetch button and show status
        self.fetch_macro_button.config(state='disabled')
        self.set_status("Récupération des indicateurs macroéconomiques...")
        self.progress.start()
        threading.Thread(target=self.fetch_macro_data_thread, daemon=True).start()

    def fetch_macro_data_thread(self):
        try:
            # Example using FRED API
            api_key = 'YOUR_FRED_API_KEY'  # Replace with your actual FRED API key
            indicators = {
                "PIB (USD)": "GDP",
                "Taux de Chômage (%)": "UNRATE",
                "Inflation Annuelle (%)": "CPIAUCSL",
                "Taux d'Intérêt (%)": "FEDFUNDS"
            }
            macro_indicators = {}
            for name, series_id in indicators.items():
                response = requests.get(f"https://api.stlouisfed.org/fred/series/observations", params={
                    'series_id': series_id,
                    'api_key': api_key,
                    'file_type': 'json',
                    'limit': 1,
                    'sort_order': 'desc'
                })
                data = response.json()
                if 'observations' in data and len(data['observations']) > 0:
                    latest = data['observations'][0]
                    value = latest['value']
                    if value == ".":
                        value = "N/A"
                    macro_indicators[name] = value
                else:
                    macro_indicators[name] = "N/A"

            # Clear old data
            self.root.after(0, self.clear_macro_tree)

            # Insert new data
            for indicateur, valeur in macro_indicators.items():
                self.root.after(0, lambda i=indicateur, v=valeur: self.macro_tree.insert("", "end", values=(i, v)))

            self.set_status("Indicateurs macroéconomiques récupérés avec succès.")
        except Exception as e:
            logging.error(f"Erreur lors de la récupération des indicateurs macroéconomiques: {e}")
            self.show_error("Erreur", f"Impossible de récupérer les indicateurs macroéconomiques : {e}")
            self.set_status("Erreur lors de la récupération des indicateurs macroéconomiques.")
        finally:
            self.fetch_macro_button.config(state='normal')
            self.progress.stop()

    def clear_macro_tree(self):
        for row in self.macro_tree.get_children():
            self.macro_tree.delete(row)

    ## Economic News Tab ##
    def create_news_widgets(self, parent):
        news_frame = ttk.Frame(parent)
        news_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # Section Title
        ttk.Label(news_frame, text="Actualités Économiques", font=("Helvetica", 16)).pack(pady=10)

        # Filters Frame
        filters_frame = ttk.Frame(news_frame)
        filters_frame.pack(pady=5, padx=10, fill=tk.X)

        # Category Filter
        ttk.Label(filters_frame, text="Catégorie:").pack(side=tk.LEFT, padx=(0, 5))
        self.category_var = tk.StringVar()
        self.category_combo = ttk.Combobox(filters_frame, textvariable=self.category_var, values=self.categories, state='readonly')
        self.category_combo.current(0)  # Default to 'All'
        self.category_combo.pack(side=tk.LEFT, padx=(0, 15))

        # Date Filter
        ttk.Label(filters_frame, text="Date:").pack(side=tk.LEFT, padx=(0, 5))
        self.date_filter_var = tk.StringVar()
        self.date_filter_combo = ttk.Combobox(filters_frame, textvariable=self.date_filter_var,
                                             values=["All Time", "Dernière Heure", "Aujourd'hui", "Cette Semaine", "Ce Mois"],
                                             state='readonly')
        self.date_filter_combo.current(0)
        self.date_filter_combo.pack(side=tk.LEFT, padx=(0, 15))

        # Search Bar
        ttk.Label(filters_frame, text="Recherche:").pack(side=tk.LEFT, padx=(0,5))
        self.search_entry = ttk.Entry(filters_frame, width=30)
        self.search_entry.pack(side=tk.LEFT, padx=(0,5))
        self.search_button = ttk.Button(filters_frame, text="Rechercher", command=self.search_news)
        self.search_button.pack(side=tk.LEFT, padx=(0,5))

        # News Source Selection
        source_frame = ttk.Frame(news_frame)
        source_frame.pack(pady=5, padx=10, fill=tk.X)

        ttk.Label(source_frame, text="Source d'Actualités:").pack(side=tk.LEFT, padx=(0, 5))

        self.news_source_var = tk.StringVar()
        self.news_source_combo = ttk.Combobox(source_frame, textvariable=self.news_source_var,
                                             values=list(self.news_sources.keys()), state='readonly')
        self.news_source_combo.current(0)  # Set default to the first source
        self.news_source_combo.pack(side=tk.LEFT, padx=(0, 5))

        # Button to add a custom source
        add_source_button = ttk.Button(source_frame, text="Ajouter Source", command=self.add_custom_source)
        add_source_button.pack(side=tk.LEFT, padx=(5, 0))

        # Fetch News Button
        fetch_frame = ttk.Frame(news_frame)
        fetch_frame.pack(pady=5, padx=10, fill=tk.X)

        self.fetch_news_button = ttk.Button(fetch_frame, text="Récupérer Actualités", command=self.fetch_news)
        self.fetch_news_button.pack(side=tk.LEFT, pady=5)

        # Copy All Titles Button
        copy_button = ttk.Button(fetch_frame, text="Copier Tous les Titres", command=self.copy_all_titles)
        copy_button.pack(side=tk.LEFT, padx=5, pady=5)

        # Refresh Button
        refresh_button = ttk.Button(fetch_frame, text="Rafraîchir Filtre", command=self.apply_filters)
        refresh_button.pack(side=tk.LEFT, padx=5, pady=5)

        # Listbox to display news
        self.news_list = tk.Listbox(news_frame, height=20)
        self.news_list.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        self.news_list.bind('<<ListboxSelect>>', self.open_news_link)

        # Scrollbar for the list
        scrollbar = ttk.Scrollbar(news_frame, orient="vertical", command=self.news_list.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.news_list.config(yscrollcommand=scrollbar.set)

    def add_custom_source(self):
        # Prompt user to enter a custom RSS URL
        custom_url = simpledialog.askstring("Ajouter Source", "Entrez l'URL du flux RSS:")
        if custom_url:
            # Validate the URL format
            if not re.match(r'^https?:\/\/', custom_url):
                self.show_error("Erreur", "Veuillez entrer une URL valide commençant par http:// ou https://")
                return
            # Check for duplicate URL
            if any(source['url'] == custom_url for source in self.news_sources.values()):
                self.show_error("Erreur", "Cette source RSS a déjà été ajoutée.")
                return
            # Attempt to parse the feed to ensure it's valid
            try:
                feed = feedparser.parse(custom_url)
                if feed.bozo:
                    raise ValueError("Le flux RSS fourni est invalide ou inaccessible.")
            except Exception as e:
                self.show_error("Erreur", f"Impossible de parser le flux RSS : {e}")
                return
            # Assign a unique key for the custom source
            custom_keys = [key for key in self.news_sources.keys() if key.startswith("Custom")]
            new_custom_key = f"Custom {len(custom_keys) + 1}"
            # Optionally, ask for a category for the custom source
            category = simpledialog.askstring("Catégorie", "Entrez une catégorie pour cette source (e.g., Business, Technology):")
            if not category:
                category = "Custom"
            self.news_sources[new_custom_key] = {"url": custom_url, "category": category}
            # Refresh the Combobox to include the updated sources
            self.news_source_combo['values'] = list(self.news_sources.keys())
            self.news_source_combo.set(new_custom_key)
            # If the category is new, add it to the list of categories
            if category not in self.categories and category != "All":
                self.categories.append(category)
                self.category_combo['values'] = self.categories
            self.show_info("Source Ajoutée", f"La source personnalisée '{new_custom_key}' a été ajoutée avec succès.")

    def fetch_news(self):
        # Disable fetch button and show status
        self.fetch_news_button.config(state='disabled')
        self.set_status("Récupération des actualités...")
        self.progress.start()
        threading.Thread(target=self.fetch_news_thread, daemon=True).start()

    def fetch_news_thread(self):
        try:
            selected_source = self.news_source_var.get()
            source_info = self.news_sources.get(selected_source, {})
            rss_url = source_info.get("url", '')

            if selected_source.startswith("Custom") and not rss_url:
                self.show_error("Erreur", "Veuillez fournir une URL valide pour la source personnalisée.")
                self.set_status("Erreur lors de la récupération des actualités.")
                return

            # If RSS URL is empty, use the selected source's URL
            if not rss_url:
                rss_url = self.news_sources.get(selected_source, {}).get("url", 'http://feeds.bbci.co.uk/news/business/rss.xml')  # Default to BBC Business

            feed = feedparser.parse(rss_url)

            if feed.bozo:
                raise Exception("Erreur dans le parsing du flux RSS.")

            news = []
            for entry in feed.entries[:50]:  # Increased to 50 for more articles
                title = entry.title if 'title' in entry else 'Sans Titre'
                link = entry.link if 'link' in entry else None
                published = entry.published if 'published' in entry else 'No Date'
                category = entry.get('category', source_info.get("category", "Unknown"))
                if link:
                    # Attempt to retrieve the publication date as datetime
                    try:
                        published_dt = datetime(*entry.published_parsed[:6])
                    except:
                        published_dt = None
                    news.append({'title': title, 'link': link, 'published': published, 'category': category, 'published_dt': published_dt})

            self.news_data = news  # Store data for future use

            # Schedule GUI updates on the main thread
            self.root.after(0, self.apply_filters)
            self.set_status("Actualités récupérées avec succès.")
        except Exception as e:
            logging.error(f"Erreur lors de la récupération des actualités: {e}")
            self.show_error("Erreur", f"Impossible de récupérer les actualités : {e}")
            self.set_status("Erreur lors de la récupération des actualités.")
        finally:
            self.fetch_news_button.config(state='normal')
            self.progress.stop()

    def apply_filters(self):
        # Clear the listbox
        self.news_list.delete(0, tk.END)
        self.filtered_news = []  # Reset filtered news

        # Get current filter values
        selected_category = self.category_var.get()
        selected_date_filter = self.date_filter_var.get()

        # Current date and time for filtering
        now = datetime.now()

        # Filter news based on category and date
        filtered_news = []
        for item in self.news_data:
            # Category Filter
            if selected_category != "All" and item['category'] != selected_category:
                continue

            # Date Filter
            if selected_date_filter != "All Time" and item['published_dt']:
                if selected_date_filter == "Dernière Heure":
                    time_delta = now - item['published_dt']
                    if time_delta.total_seconds() > 3600:
                        continue
                elif selected_date_filter == "Aujourd'hui":
                    if item['published_dt'].date() != now.date():
                        continue
                elif selected_date_filter == "Cette Semaine":
                    start_of_week = now - pd.Timedelta(days=now.weekday())
                    if item['published_dt'].date() < start_of_week.date():
                        continue
                elif selected_date_filter == "Ce Mois":
                    start_of_month = now.replace(day=1)
                    if item['published_dt'].date() < start_of_month.date():
                        continue

            filtered_news.append(item)

        self.filtered_news = filtered_news  # Update the filtered news list

        # Display the filtered news
        for item in self.filtered_news:
            self.news_list.insert(tk.END, f"[{item['published']}] {item['category']} - {item['title']}")

    def search_news(self):
        query = self.search_entry.get().strip().lower()
        if not query:
            self.apply_filters()  # If search is empty, apply current filters
            return

        # Clear the listbox
        self.news_list.delete(0, tk.END)
        self.filtered_news = []  # Reset filtered news

        # Get current filter values
        selected_category = self.category_var.get()
        selected_date_filter = self.date_filter_var.get()

        # Current date and time for filtering
        now = datetime.now()

        # Filter news based on search query, category, and date
        filtered_news = []
        for item in self.news_data:
            # Search Query
            if query not in item['title'].lower():
                continue

            # Category Filter
            if selected_category != "All" and item['category'] != selected_category:
                continue

            # Date Filter
            if selected_date_filter != "All Time" and item['published_dt']:
                if selected_date_filter == "Dernière Heure":
                    time_delta = now - item['published_dt']
                    if time_delta.total_seconds() > 3600:
                        continue
                elif selected_date_filter == "Aujourd'hui":
                    if item['published_dt'].date() != now.date():
                        continue
                elif selected_date_filter == "Cette Semaine":
                    start_of_week = now - pd.Timedelta(days=now.weekday())
                    if item['published_dt'].date() < start_of_week.date():
                        continue
                elif selected_date_filter == "Ce Mois":
                    start_of_month = now.replace(day=1)
                    if item['published_dt'].date() < start_of_month.date():
                        continue

            filtered_news.append(item)

        self.filtered_news = filtered_news  # Update the filtered news list

        # Display the filtered news
        for item in self.filtered_news:
            self.news_list.insert(tk.END, f"[{item['published']}] {item['category']} - {item['title']}")

    def open_news_link(self, event):
        selection = event.widget.curselection()
        if selection:
            index = selection[0]
            if index < len(self.filtered_news):
                link = self.filtered_news[index]['link']
                if re.match(r'^https?:\/\/', link):
                    try:
                        webbrowser.open(link, new=1)
                    except Exception as e:
                        logging.error(f"Erreur lors de l'ouverture du lien: {e}")
                        self.show_error("Erreur", f"Impossible d'ouvrir le lien : {e}")
                else:
                    self.show_error("Erreur", "URL invalide.")

    def copy_all_titles(self):
        if not self.filtered_news:
            self.show_error("Erreur", "Aucun titre à copier.")
            return
        all_titles = "\n".join([f"[{item['published']}] {item['category']} - {item['title']}" for item in self.filtered_news])
        try:
            self.root.clipboard_clear()
            self.root.clipboard_append(all_titles)
            self.show_info("Succès", "Tous les titres ont été copiés dans le presse-papiers.")
        except Exception as e:
            logging.error(f"Erreur lors de la copie des titres: {e}")
            self.show_error("Erreur", f"Impossible de copier les titres : {e}")

    ## Alert System ##
    def set_alert(self):
        try:
            threshold = float(self.alert_entry.get())
            symbol = self.symbol_entry.get().strip().upper()

            if not symbol:
                self.show_error("Erreur", "Veuillez définir d'abord le symbole boursier.")
                return

            alert = {'symbol': symbol, 'threshold': threshold}

            with self.alert_lock:
                if alert not in self.alert_thresholds:
                    self.alert_thresholds.append(alert)
                    self.alerts_listbox.insert(tk.END, f"{symbol} ≥ {threshold} USD")
                    self.show_info("Alerte Définie", f"Alerte définie pour {symbol} à {threshold} USD.")
                else:
                    self.show_error("Erreur", f"L'alerte pour {symbol} ≥ {threshold} USD existe déjà.")

                if not self.alert_active:
                    self.stop_event.clear()  # Reset the stop_event before starting the thread
                    self.alert_active = True
                    alert_thread = threading.Thread(target=self.check_price_alert, daemon=True)
                    alert_thread.start()
        except ValueError:
            self.show_error("Erreur", "Veuillez entrer une valeur numérique valide pour le seuil d'alerte.")

    def remove_selected_alert(self):
        try:
            selection = self.alerts_listbox.curselection()
            if not selection:
                self.show_error("Erreur", "Veuillez sélectionner une alerte à supprimer.")
                return
            index = selection[0]
            with self.alert_lock:
                self.alerts_listbox.delete(index)
                del self.alert_thresholds[index]
            self.show_info("Alerte Supprimée", f"Alerte numéro {index + 1} supprimée.")

            # If no alerts left, deactivate the alert thread
            with self.alert_lock:
                if not self.alert_thresholds:
                    self.alert_active = False
                    self.stop_event.set()
        except IndexError:
            self.show_error("Erreur", "Erreur lors de la suppression de l'alerte.")
        except Exception as e:
            logging.error(f"Erreur lors de la suppression de l'alerte: {e}")
            self.show_error("Erreur", "Erreur lors de la suppression de l'alerte.")

    def check_price_alert(self):
        while not self.stop_event.is_set() and self.alert_active:
            with self.alert_lock:
                if not self.alert_thresholds:
                    self.alert_active = False
                    break
                alerts_copy = self.alert_thresholds.copy()

            try:
                symbols = list({alert['symbol'] for alert in alerts_copy})  # Unique symbols
                if not symbols:
                    break
                data = yf.download(tickers=symbols, period='1d', interval='1m', threads=True)

                for alert in alerts_copy:
                    symbol = alert['symbol']
                    threshold = alert['threshold']
                    if symbol in data['Close']:
                        close_series = data['Close'][symbol]
                        if not close_series.empty:
                            current_price = close_series.iloc[-1]
                            if current_price >= threshold:
                                # Use root.after to ensure GUI updates occur in main thread
                                self.root.after(0, lambda s=symbol, p=current_price: self.show_info(
                                    "Alerte de Prix", f"Le prix de {s} a atteint {p:.2f} USD."))
                                with self.alert_lock:
                                    if alert in self.alert_thresholds:
                                        index = self.alert_thresholds.index(alert)
                                        self.alerts_listbox.delete(index)
                                        del self.alert_thresholds[index]
                with self.alert_lock:
                    if not self.alert_thresholds:
                        self.alert_active = False
                        break
            except Exception as e:
                logging.error(f"Erreur lors de la vérification des alertes: {e}")
            # Adjusted polling interval to 30 seconds
            self.stop_event.wait(30)  # Check every 30 seconds or until stop_event

    def on_closing(self):
        if messagebox.askokcancel("Quitter", "Voulez-vous vraiment quitter l'application?"):
            self.alert_active = False  # Deactivate alert thread
            self.stop_event.set()  # Signal threads to stop
            self.root.destroy()

    def show_error(self, title, message):
        # Encapsulate messagebox for consistency and thread safety
        self.root.after(0, lambda: messagebox.showerror(title, message))

    def show_info(self, title, message):
        # Encapsulate messagebox for consistency and thread safety
        self.root.after(0, lambda: messagebox.showinfo(title, message))

    def set_status(self, message):
        # Allows setting any custom status messages
        self.root.after(0, lambda: self.status_var.set(message))


def main():
    root = tk.Tk()

    # Apply a theme
    style = ttk.Style()
    style.theme_use('clam')  # Options: 'default', 'clam', 'alt', 'classic'

    app = FinanceApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()