import tkinter as tk
from tkinter import messagebox, filedialog, scrolledtext, ttk
from tkhtmlview import HTMLLabel
from PIL import Image, ImageTk
import requests
from bs4 import BeautifulSoup
import feedparser
import threading
import re
import logging
from urllib.parse import urlparse
import queue
from logging.handlers import RotatingFileHandler
import hashlib
import os
import sqlite3
import time
from datetime import datetime
import gettext
import locale

# Настройка локализации
def setup_localization():
    current_locale, encoding = locale.getdefaultlocale()
    try:
        lang = gettext.translation('newsparser', localedir='locales', languages=[current_locale])
        lang.install()
        _ = lang.gettext
    except FileNotFoundError:
        gettext.install('newsparser')  # По умолчанию
        _ = gettext.gettext
    return _

_ = setup_localization()

# Настройка логирования с ротацией файлов
logger = logging.getLogger("NewsParser")
logger.setLevel(logging.DEBUG)  # Для отладки
handler = RotatingFileHandler(
    'news_parser.log',
    maxBytes=5 * 1024 * 1024,
    backupCount=5,
    encoding='utf-8'
)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

class ParserModule:
    """
    Модуль, отвечающий за парсинг HTML и RSS контента.
    """
    def __init__(self, cache_manager):
        self.cache_manager = cache_manager

    def parse_html(self, url, tags, filter_keywords, exclude_keywords, exact, case, regex, limit, cancel_event):
        """
        Парсит HTML страницу по заданному URL и тегам.
        """
        html = self.cache_manager.load_from_cache(url)
        if not html:
            try:
                response = requests.get(url, timeout=15)
                response.raise_for_status()
                html = response.text
                self.cache_manager.save_to_cache(url, html)
            except requests.exceptions.RequestException as e:
                raise ConnectionError(_("Не удалось загрузить страницу: ") + str(e))

        soup = BeautifulSoup(html, 'html.parser')
        tag_list = [t.strip() for t in tags.split(",") if t.strip()]
        results = []
        count = 0

        for tag in tag_list:
            for item in soup.find_all(tag):
                if cancel_event.is_set():
                    logger.info("Парсинг отменён пользователем.")
                    return results
                if limit != 0 and count >= limit:
                    break

                text = item.get_text(separator=' ', strip=True)
                if FilterModule.filter_text(text, filter_keywords, exclude_keywords, exact, case, regex):
                    content = str(item)
                    results.append({"type": "html", "content": content, "text": text})
                    count += 1

        return results

    def parse_rss(self, url, filter_keywords, exclude_keywords, exact, case, regex, limit, date_from, date_to, cancel_event):
        """
        Парсит RSS ленту по заданному URL.
        """
        feed = feedparser.parse(url)
        if feed.bozo:
            raise ValueError(_("Не удалось парсить RSS ленту: ") + str(feed.bozo_exception))

        results = []
        count = 0

        for entry in feed.entries:
            if cancel_event.is_set():
                logger.info("Парсинг отменён пользователем.")
                return results
            if limit != 0 and count >= limit:
                break

            title = entry.get('title', _('Нет заголовка'))
            description = entry.get('description', _('Нет описания'))
            link = entry.get('link', '#')
            published = entry.get('published', _('Нет даты публикации'))

            # Парсинг даты публикации
            try:
                published_date = datetime(*entry.published_parsed[:6])
            except:
                published_date = None

            # Фильтрация по дате
            if date_from and published_date and published_date < date_from:
                continue
            if date_to and published_date and published_date > date_to:
                continue

            # Обработка описания
            soup = BeautifulSoup(description, 'html.parser')
            for script in soup(["script", "style"]):
                script.extract()
            cleaned_description = str(soup)
            combined_text = f"{title} {soup.get_text(separator=' ', strip=True)}"

            if FilterModule.filter_text(combined_text, filter_keywords, exclude_keywords, exact, case, regex):
                content = {
                    "type": "rss",
                    "title": title,
                    "description": cleaned_description,
                    "link": link,
                    "published": published
                }
                results.append(content)
                count += 1

        return results

class FilterModule:
    """
    Модуль, отвечающий за фильтрацию текста по ключевым словам.
    """
    @staticmethod
    def filter_text(text, include_keywords, exclude_keywords, exact, case, regex=False):
        """
        Фильтрует текст по включающим и исключающим ключевым словам.

        :param text: Текст для фильтрации.
        :param include_keywords: Список включающих ключевых слов.
        :param exclude_keywords: Список исключающих ключевых слов.
        :param exact: Флаг точного совпадения.
        :param case: Флаг чувствительности к регистру.
        :param regex: Флаг использования регулярных выражений.
        :return: True, если текст удовлетворяет критериям фильтрации, иначе False.
        """
        flags = 0 if case else re.IGNORECASE

        # Фильтрация по включающим ключевым словам
        if include_keywords:
            include = False
            for keyword in include_keywords:
                pattern = keyword if regex else re.escape(keyword)
                if exact:
                    if re.search(rf'\b{pattern}\b', text, flags):
                        include = True
                        break
                else:
                    if re.search(pattern, text, flags):
                        include = True
                        break
            if not include:
                return False

        # Фильтрация по исключающим ключевым словам
        if exclude_keywords:
            for keyword in exclude_keywords:
                pattern = keyword if regex else re.escape(keyword)
                if re.search(pattern, text, flags):
                    return False

        return True

class DatabaseModule:
    """
    Модуль, отвечающий за взаимодействие с базой данных SQLite для хранения истории парсингов.
    """
    def __init__(self, db_path='news_parser.db'):
        self.db_path = db_path
        self.conn = None
        self.cursor = None
        self.connect()
        self.setup_database()

    def connect(self):
        """
        Устанавливает соединение с базой данных.
        """
        try:
            self.conn = sqlite3.connect(self.db_path)
            self.cursor = self.conn.cursor()
            logger.info(_("Подключено к базе данных."))
        except sqlite3.Error as e:
            logger.error(f"{_('Ошибка подключения к базе данных')}: {e}")
            messagebox.showerror(_("Ошибка"), _("Не удалось подключиться к базе данных.") + f" {e}")

    def setup_database(self):
        """
        Создает таблицу истории парсингов, если она не существует.
        """
        try:
            self.cursor.execute('''
                CREATE TABLE IF NOT EXISTS history (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp TEXT,
                    url TEXT,
                    tags TEXT,
                    filter_keywords TEXT,
                    exclude_keywords TEXT,
                    results_count INTEGER
                )
            ''')
            self.conn.commit()
            logger.info(_("База данных настроена."))
        except sqlite3.Error as e:
            logger.error(f"{_('Ошибка настройки базы данных')}: {e}")
            messagebox.showerror(_("Ошибка"), _("Не удалось настроить базу данных.") + f" {e}")

    def save_history(self, url, tags, filter_keywords, exclude_keywords, results_count):
        """
        Сохраняет запись истории парсинга в базе данных.
        """
        try:
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
            self.cursor.execute('''
                INSERT INTO history (timestamp, url, tags, filter_keywords, exclude_keywords, results_count)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (timestamp, url, tags, ", ".join(filter_keywords), ", ".join(exclude_keywords), results_count))
            self.conn.commit()
            logger.info(_("История сохранена."))
        except sqlite3.Error as e:
            logger.error(f"{_('Ошибка сохранения истории')}: {e}")
            messagebox.showerror(_("Ошибка"), _("Не удалось сохранить историю.") + f" {e}")

    def fetch_history(self):
        """
        Извлекает все записи истории парсинга из базы данных.
        """
        try:
            self.cursor.execute('SELECT * FROM history ORDER BY id DESC')
            return self.cursor.fetchall()
        except sqlite3.Error as e:
            logger.error(f"{_('Ошибка извлечения истории')}: {e}")
            messagebox.showerror(_("Ошибка"), _("Не удалось извлечь историю.") + f" {e}")
            return []

    def close(self):
        """
        Закрывает соединение с базой данных.
        """
        if self.conn:
            self.conn.close()
            logger.info(_("Соединение с базой данных закрыто."))

class CacheModule:
    """
    Модуль, отвечающий за кэширование загруженных страниц для ускорения парсинга.
    """
    def __init__(self, cache_dir='cache'):
        self.cache_dir = cache_dir
        os.makedirs(self.cache_dir, exist_ok=True)

    def get_cache_path(self, url):
        """
        Возвращает путь к закэшированной версии страницы по URL.
        """
        hashed_url = hashlib.md5(url.encode()).hexdigest()
        return os.path.join(self.cache_dir, hashed_url + '.html')

    def load_from_cache(self, url):
        """
        Загружает содержимое страницы из кэша, если оно существует.
        """
        path = self.get_cache_path(url)
        try:
            if os.path.exists(path):
                with open(path, 'r', encoding='utf-8') as file:
                    logger.info(_("Загружено из кэша."))
                    return file.read()
        except Exception as e:
            logger.error(f"{_('Ошибка загрузки из кэша')}: {e}")
        return None

    def save_to_cache(self, url, content):
        """
        Сохраняет содержимое страницы в кэш.
        """
        path = self.get_cache_path(url)
        try:
            with open(path, 'w', encoding='utf-8') as file:
                file.write(content)
            logger.info(_("Контент сохранён в кэше."))
        except Exception as e:
            logger.error(f"{_('Ошибка сохранения в кэш')}: {e}")

class NotificationModule:
    """
    Модуль, отвечающий за отправку уведомлений через Telegram.
    """
    def send_telegram_message(self, token, chat_id, message):
        """
        Отправляет сообщение в Telegram по заданному токену и chat_id.
        """
        if not token or not chat_id:
            logger.warning(_("Telegram Token или Chat ID не заданы."))
            return
        try:
            url = f"https://api.telegram.org/bot{token}/sendMessage"
            payload = {
                'chat_id': chat_id,
                'text': message
            }
            response = requests.post(url, data=payload)
            if response.status_code != 200:
                logger.error(f"{_('Не удалось отправить сообщение в Telegram')}: {response.text}")
        except Exception as e:
            logger.error(f"{_('Ошибка при отправке сообщения в Telegram')}: {e}")

class UIModule:
    """
    Модуль, отвечающий за пользовательский интерфейс.
    """
    def __init__(self, master, parser_module, db_module, cache_module, notification_module):
        self.master = master
        self.master.configure(bg='white')  # Задний фон по умолчанию
        # Интеграция с модулями
        self.parser_module = parser_module
        self.db_module = db_module
        self.cache_module = cache_module
        self.notification_module = notification_module

        # Основной фрейм с панелью для лучшей масштабируемости
        self.paned_window = ttk.PanedWindow(master, orient=tk.VERTICAL)
        self.paned_window.pack(fill=tk.BOTH, expand=True)

        # Верхняя панель для ввода параметров
        self.input_frame = ttk.Frame(self.paned_window, padding="10")
        self.paned_window.add(self.input_frame, weight=1)

        # Нижняя панель для отображения результатов
        self.output_paned = ttk.PanedWindow(self.paned_window, orient=tk.HORIZONTAL)
        self.paned_window.add(self.output_paned, weight=3)

        # Левый фрейм для HTML результатов
        self.html_frame = ttk.Frame(self.output_paned, padding="5")
        self.output_paned.add(self.html_frame, weight=2)

        ttk.Label(self.html_frame, text=_("HTML результаты:"), font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(0,5))
        self.result_label = HTMLLabel(self.html_frame, html="", background='white')
        self.result_label.pack(fill=tk.BOTH, expand=True)

        # Правый фрейм для текстовых результатов
        self.text_frame = ttk.Frame(self.output_paned, padding="5")
        self.output_paned.add(self.text_frame, weight=1)

        ttk.Label(self.text_frame, text=_("Готовый текст:"), font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(0,5))
        self.output_text = scrolledtext.ScrolledText(
            self.text_frame, wrap=tk.WORD, font=('Arial', 10)
        )
        self.output_text.pack(fill=tk.BOTH, expand=True)

        # Настройка ввода параметров
        self.setup_input_widgets()

        # Кнопки управления
        self.setup_buttons()

        # Индикатор прогресса
        self.progress = ttk.Progressbar(master, mode='indeterminate')
        self.progress.pack(fill=tk.X, side=tk.BOTTOM, pady=5, padx=10)

        # Очередь для взаимодействия с потоками
        self.queue = queue.Queue()
        self.master.after(100, self.process_queue)

    def setup_input_widgets(self):
        # Ввод URL
        ttk.Label(self.input_frame, text=_("Введите URL новостной ленты:"), font=('Arial', 10)).grid(
            row=0, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.url_entry = ttk.Entry(self.input_frame, width=80)
        self.url_entry.grid(row=0, column=1, sticky=tk.EW, pady=5, padx=5)

        # Ввод тегов (несколько)
        ttk.Label(self.input_frame, text=_("Введите теги для парсинга (через запятую, например, 'h2, h3'):"), font=('Arial', 10)).grid(
            row=1, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.tag_entry = ttk.Entry(self.input_frame, width=80)
        self.tag_entry.grid(row=1, column=1, sticky=tk.EW, pady=5, padx=5)

        # Ввод ключевых слов для включения
        ttk.Label(self.input_frame, text=_("Введите ключевые слова для фильтрации (через запятую):"), font=('Arial', 10)).grid(
            row=2, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.filter_entry = ttk.Entry(self.input_frame, width=80)
        self.filter_entry.grid(row=2, column=1, sticky=tk.EW, pady=5, padx=5)

        # Ввод ключевых слов для исключения
        ttk.Label(self.input_frame, text=_("Исключить ключевые слова (через запятую):"), font=('Arial', 10)).grid(
            row=3, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.exclude_entry = ttk.Entry(self.input_frame, width=80)
        self.exclude_entry.grid(row=3, column=1, sticky=tk.EW, pady=5, padx=5)

        # Опции фильтрации
        self.case_sensitive = tk.BooleanVar()
        self.exact_match = tk.BooleanVar()
        self.regex_var = tk.BooleanVar()

        ttk.Checkbutton(
            self.input_frame,
            text=_("Чувствительность к регистру"),
            variable=self.case_sensitive
        ).grid(row=4, column=1, sticky=tk.W, pady=2, padx=5)

        ttk.Checkbutton(
            self.input_frame,
            text=_("Точный поиск фраз"),
            variable=self.exact_match
        ).grid(row=5, column=1, sticky=tk.W, pady=2, padx=5)

        ttk.Checkbutton(
            self.input_frame,
            text=_("Использовать регулярные выражения"),
            variable=self.regex_var
        ).grid(row=6, column=1, sticky=tk.W, pady=2, padx=5)

        # Лимит результатов
        ttk.Label(self.input_frame, text=_("Лимит результатов (0 - без ограничений):"), font=('Arial', 10)).grid(
            row=7, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.limit_var = tk.StringVar(value='0')
        self.limit_entry = ttk.Entry(
            self.input_frame, width=10, textvariable=self.limit_var
        )
        self.limit_entry.grid(row=7, column=1, sticky=tk.W, pady=5, padx=5)
        self.limit_entry.bind("<FocusOut>", self.validate_limit)

        # Интервал автоматического парсинга
        ttk.Label(self.input_frame, text=_("Интервал автоматического парсинга (минут):"), font=('Arial', 10)).grid(
            row=8, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.interval_var = tk.StringVar(value='0')
        self.interval_entry = ttk.Entry(
            self.input_frame, width=10, textvariable=self.interval_var
        )
        self.interval_entry.grid(row=8, column=1, sticky=tk.W, pady=5, padx=5)
        self.interval_entry.bind("<FocusOut>", self.validate_interval)

        # Выбор темы оформления
        ttk.Label(self.input_frame, text=_("Выберите тему оформления:"), font=('Arial', 10)).grid(
            row=9, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.style = ttk.Style()
        self.theme_var = tk.StringVar(value=self.style.theme_use())
        themes = self.style.theme_names()
        self.theme_combobox = ttk.Combobox(
            self.input_frame,
            values=themes,
            textvariable=self.theme_var,
            state='readonly'
        )
        self.theme_combobox.grid(row=9, column=1, sticky=tk.W, pady=5, padx=5)
        self.theme_combobox.bind("<<ComboboxSelected>>", self.change_theme)

        # Выбор кодировки при сохранении
        ttk.Label(self.input_frame, text=_("Выберите кодировку для сохранения:"), font=('Arial', 10)).grid(
            row=10, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.encoding_var = tk.StringVar(value='utf-8')
        encodings = ['utf-8', 'windows-1251', 'ISO-8859-1', 'cp1252', 'ascii']
        self.encoding_combobox = ttk.Combobox(
            self.input_frame,
            values=encodings,
            textvariable=self.encoding_var,
            state='readonly'
        )
        self.encoding_combobox.grid(row=10, column=1, sticky=tk.W, pady=5, padx=5)

        # Telegram уведомления
        ttk.Label(self.input_frame, text=_("Telegram Bot Token:"), font=('Arial', 10)).grid(
            row=11, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.telegram_token_entry = ttk.Entry(self.input_frame, width=80)
        self.telegram_token_entry.grid(row=11, column=1, sticky=tk.EW, pady=5, padx=5)

        ttk.Label(self.input_frame, text=_("Telegram Chat ID:"), font=('Arial', 10)).grid(
            row=12, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.telegram_chat_id_entry = ttk.Entry(self.input_frame, width=80)
        self.telegram_chat_id_entry.grid(row=12, column=1, sticky=tk.EW, pady=5, padx=5)

        # Поля для диапазона дат (только для RSS)
        ttk.Label(self.input_frame, text=_("Дата с (YYYY-MM-DD):"), font=('Arial', 10)).grid(
            row=13, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.date_from_entry = ttk.Entry(self.input_frame, width=20)
        self.date_from_entry.grid(row=13, column=1, sticky=tk.W, pady=5, padx=5)

        ttk.Label(self.input_frame, text=_("Дата по (YYYY-MM-DD):"), font=('Arial', 10)).grid(
            row=14, column=0, sticky=tk.W, pady=5, padx=5
        )
        self.date_to_entry = ttk.Entry(self.input_frame, width=20)
        self.date_to_entry.grid(row=14, column=1, sticky=tk.W, pady=5, padx=5)

        # Настройка растяжения столбцов
        self.input_frame.columnconfigure(0, weight=0)
        self.input_frame.columnconfigure(1, weight=1)

    def setup_buttons(self):
        """
        Настройка кнопок управления парсингом.
        """
        # Кнопка "Парсить" с иконкой (опционально)
        try:
            parse_icon = ImageTk.PhotoImage(Image.open("parse_icon.png").resize((20, 20)))
            self.parse_button = ttk.Button(self.input_frame, text=_("Парсить"), image=parse_icon, compound=tk.LEFT, command=self.start_parse)
            self.parse_button.image = parse_icon
        except Exception:
            self.parse_button = ttk.Button(self.input_frame, text=_("Парсить"), command=self.start_parse)
        self.parse_button.grid(row=15, column=0, columnspan=2, pady=10, padx=5, sticky=tk.E+tk.W)

        # Кнопка "Отменить"
        self.cancel_button = ttk.Button(
            self.input_frame,
            text=_("Отменить"),
            command=self.cancel_parse
        )
        self.cancel_button.grid(row=16, column=0, columnspan=2, pady=5, padx=5, sticky=tk.E+tk.W)
        self.cancel_button.config(state=tk.DISABLED)

    def validate_limit(self, event=None):
        """
        Валидирует ввод лимита результатов.
        """
        limit_str = self.limit_var.get().strip()
        if not limit_str.isdigit():
            messagebox.showwarning(
                _("Ввод лимита"),
                _("Пожалуйста, введите допустимое числовое значение для лимита.")
            )
            self.limit_var.set('0')

    def validate_interval(self, event=None):
        """
        Валидирует ввод интервала автоматического парсинга.
        """
        interval_str = self.interval_var.get().strip()
        if not interval_str.isdigit() or int(interval_str) < 0:
            messagebox.showwarning(
                _("Ввод интервала"),
                _("Пожалуйста, введите допустимое числовое значение для интервала (в минутах).")
            )
            self.interval_var.set('0')

    def start_parse(self):
        """
        Запускает процесс парсинга в отдельном потоке.
        """
        # Проверка лимита перед запуском парсинга
        self.validate_limit()
        # Запуск парсинга в отдельном потоке
        self.parse_event = threading.Event()
        parse_thread = threading.Thread(target=self.parse)
        parse_thread.daemon = True
        parse_thread.start()
        self.parse_button.config(state=tk.DISABLED)
        self.cancel_button.config(state=tk.NORMAL)

    def parse(self):
        """
        Основной метод парсинга, вызываемый в отдельном потоке.
        """
        # Запуск индикатора прогресса
        self.queue.put(('start_progress', None))

        url = self.url_entry.get().strip()
        tags = self.tag_entry.get().strip()
        filter_keywords_input = self.filter_entry.get().strip()
        exclude_keywords_input = self.exclude_entry.get().strip()
        limit_str = self.limit_var.get().strip()

        try:
            limit = int(limit_str) if limit_str.isdigit() else 0
        except ValueError:
            limit = 0

        if limit < 0:
            limit = 0  # 0 - без ограничений

        # Валидация URL
        if not self.validate_url(url):
            self.queue.put(('show_error', (_("Ошибка"), _("Введен некорректный URL."))))
            self.queue.put(('stop_progress', None))
            self.queue.put(('enable_button', None))
            return

        filter_keywords = [kw.strip() for kw in filter_keywords_input.split(",") if kw.strip()]
        exclude_keywords = [kw.strip() for kw in exclude_keywords_input.split(",") if kw.strip()]
        exact = self.exact_match.get()
        case = self.case_sensitive.get()
        regex = self.regex_var.get()

        # Очистка полей результатов
        self.queue.put(('clear_results', None))

        # Проверка интернет-соединения
        if not self.has_internet_connection():
            self.queue.put(('show_error', (_("Ошибка"), _("Нет интернет-соединения."))))
            self.queue.put(('stop_progress', None))
            self.queue.put(('enable_button', None))
            return

        # Определение типа контента
        try:
            response = requests.head(url, allow_redirects=True, timeout=10)
            content_type = response.headers.get('Content-Type', '').lower()
            is_rss = any(ct in content_type for ct in [
                'application/rss+xml',
                'application/xml',
                'text/xml'
            ]) or url.lower().endswith(('.rss', '.xml'))
        except requests.exceptions.RequestException as e:
            logger.error(f"Ошибка при определении типа контента: {e}")
            self.queue.put((
                'show_error',
                (_("Ошибка"), _("Не удалось определить тип контента: ") + str(e))
            ))
            self.queue.put(('stop_progress', None))
            self.queue.put(('enable_button', None))
            return

        if not tags and not is_rss:
            self.queue.put((
                'show_warning',
                (_("Предупреждение"), _("Пожалуйста, заполните хотя бы поле URL или тег."))
            ))
            self.queue.put(('stop_progress', None))
            self.queue.put(('enable_button', None))
            return

        # Парсинг
        try:
            if is_rss:
                date_from_str = self.date_from_entry.get().strip()
                date_to_str = self.date_to_entry.get().strip()
                date_from = self.parse_date(date_from_str) if date_from_str else None
                date_to = self.parse_date(date_to_str) if date_to_str else None

                results = self.parser_module.parse_rss(
                    url=url,
                    filter_keywords=filter_keywords,
                    exclude_keywords=exclude_keywords,
                    exact=exact,
                    case=case,
                    regex=regex,
                    limit=limit,
                    date_from=date_from,
                    date_to=date_to,
                    cancel_event=self.parse_event
                )
                self.display_rss_results(results)
            else:
                results = self.parser_module.parse_html(
                    url=url,
                    tags=tags,
                    filter_keywords=filter_keywords,
                    exclude_keywords=exclude_keywords,
                    exact=exact,
                    case=case,
                    regex=regex,
                    limit=limit,
                    cancel_event=self.parse_event
                )
                self.display_html_results(results)
        except ConnectionError as ce:
            logger.error(f"Ошибка при загрузке страницы: {ce}")
            self.queue.put(('show_error', (_("Ошибка"), str(ce))))
        except ValueError as ve:
            logger.error(f"Ошибка парсинга RSS: {ve}")
            self.queue.put(('show_error', (_("Ошибка"), str(ve))))
        except Exception as e:
            logger.error(f"Неизвестная ошибка: {e}")
            self.queue.put(('show_error', (_("Ошибка"), _("Неизвестная ошибка: ") + str(e))))

        # Сохранение истории парсинга
        results_count = len(results)
        self.db_module.save_history(url, tags, filter_keywords, exclude_keywords, results_count)

        # Отправка уведомления в Telegram
        telegram_token = self.telegram_token_entry.get().strip()
        telegram_chat_id = self.telegram_chat_id_entry.get().strip()
        if telegram_token and telegram_chat_id:
            message = f"{_('Парсинг завершён для')} {url}.\n{_('Найдено результатов')}: {results_count}."
            self.notification_module.send_telegram_message(telegram_token, telegram_chat_id, message)

        # Остановка индикатора прогресса и включение кнопки
        self.queue.put(('stop_progress', None))
        self.queue.put(('enable_button', None))
        self.queue.put(('disable_cancel', None))

    def display_html_results(self, results):
        """
        Отображает результаты парсинга HTML в интерфейсе.
        """
        formatted_results = []
        for item in results:
            title = item['text']
            content = item['content']
            html_content = self.parser_module.format_html_content(title=title, content=content)
            formatted_results.append(html_content)
            self.queue.put(('insert_text', title))
        if formatted_results:
            full_html = "<br>".join(formatted_results)
            full_html = self.parser_module.add_html_structure(full_html)
            self.queue.put(('update_html', full_html))
        else:
            self.queue.put(('update_html', f"<p>{_('Нет новостей, соответствующих критериям.')}</p>"))

    def display_rss_results(self, results):
        """
        Отображает результаты парсинга RSS в интерфейсе.
        """
        formatted_results = []
        for entry in results:
            title = entry['title']
            description = entry['description']
            link = entry['link']
            published = entry['published']
            html_content = self.parser_module.format_html_content_rss(title, description, link, published)
            formatted_results.append(html_content)
            text_content = (
                f"{title}\n"
                f"{BeautifulSoup(description, 'html.parser').get_text(separator=' ', strip=True)}\n"
                f"{link}\n{_('Опубликовано')}: {published}\n\n"
            )
            self.queue.put(('insert_text_full', text_content))
        if formatted_results:
            full_html = "<br>".join(formatted_results)
            full_html = self.parser_module.add_html_structure(full_html)
            self.queue.put(('update_html', full_html))
        else:
            self.queue.put(('update_html', f"<p>{_('Нет новостей, соответствующих критериям.')}</p>"))

    def copy_text(self):
        """
        Копирует готовый текст в буфер обмена.
        """
        text = self.output_text.get(1.0, tk.END).strip()
        if text:
            self.master.clipboard_clear()
            self.master.clipboard_append(text)
            messagebox.showinfo(_("Копирование"), _("Текст скопирован в буфер обмена."))
        else:
            messagebox.showwarning(_("Предупреждение"), _("Нет текста для копирования."))

    def save_to_file(self, format='html'):
        """
        Сохраняет результаты парсинга в файл.

        :param format: Формат сохранения ('html' или 'txt').
        """
        encoding = self.encoding_var.get()
        if format == 'html':
            content = self.result_label.get_html()
            default_ext = ".html"
            filetypes = [(_("HTML files"), "*.html"), (_("All files"), "*.*")]
        elif format == 'txt':
            content = self.output_text.get(1.0, tk.END)
            default_ext = ".txt"
            filetypes = [(_("Text files"), "*.txt"), (_("All files"), "*.*")]
        else:
            messagebox.showerror(_("Ошибка"), _("Неподдерживаемый формат сохранения."))
            return

        if not content.strip():
            messagebox.showwarning(_("Предупреждение"), _("Нет данных для сохранения."))
            return

        file_path = filedialog.asksaveasfilename(
            defaultextension=default_ext,
            filetypes=filetypes,
            title=_("Сохранить файл")
        )
        if file_path:
            try:
                if format == 'html':
                    # Добавляем базовую структуру HTML, если её нет
                    if not re.search(r'<html.*?>', content, re.IGNORECASE):
                        content = self.parser_module.add_html_structure(content)
                with open(file_path, 'w', encoding=encoding) as file:
                    file.write(content)
                messagebox.showinfo(_("Сохранение"), _("Данные успешно сохранены."))
            except Exception as e:
                logger.error(f"{_('Ошибка при сохранении файла')}: {e}")
                messagebox.showerror(_("Ошибка"), _("Не удалось сохранить файл.") + f" {e}")

    def cancel_parse(self):
        """
        Отменяет текущий процесс парсинга.
        """
        if hasattr(self, 'parse_event'):
            self.parse_event.set()
            self.queue.put(('stop_progress', None))
            self.queue.put(('enable_button', None))
            messagebox.showinfo(_("Отмена"), _("Процесс парсинга был отменён."))
            self.cancel_button.config(state=tk.DISABLED)

    def show_history(self):
        """
        Показывает окно с историей парсингов.
        """
        history_window = tk.Toplevel(self.master)
        history_window.title(_("История парсинга"))
        history_window.geometry("900x500")

        columns = ('id', 'timestamp', 'url', 'tags', 'filter_keywords', 'exclude_keywords', 'results_count')
        tree = ttk.Treeview(history_window, columns=columns, show='headings')
        for col in columns:
            header = _("ID") if col == 'id' else _(col.replace('_', ' ').capitalize())
            tree.heading(col, text=header)
            tree.column(col, width=100, anchor=tk.W)
        tree.pack(fill=tk.BOTH, expand=True)

        history_records = self.db_module.fetch_history()
        for record in history_records:
            tree.insert('', tk.END, values=record)

        export_button = ttk.Button(history_window, text=_("Экспортировать в CSV"), command=lambda: self.export_history(tree))
        export_button.pack(pady=10)

    def export_history(self, tree):
        """
        Экспортирует историю парсингов в CSV файл.
        """
        file_path = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[(_("CSV files"), "*.csv"), (_("All files"), "*.*")],
            title=_("Сохранить историю как")
        )
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    headers = ['ID', 'Timestamp', 'URL', 'Tags', 'Filter Keywords', 'Exclude Keywords', 'Results Count']
                    f.write(','.join(headers) + '\n')
                    self.db_module.cursor.execute('SELECT * FROM history ORDER BY id DESC')
                    for row in self.db_module.cursor.fetchall():
                        row = [str(item).replace(',', ';') for item in row]  # Избегаем конфликтов с запятыми
                        f.write(','.join(row) + '\n')
                messagebox.showinfo(_("Экспорт"), _("История успешно экспортирована."))
            except Exception as e:
                logger.error(f"{_('Ошибка при экспорте истории')}: {e}")
                messagebox.showerror(_("Ошибка"), _("Не удалось экспортировать историю.") + f" {e}")

    def highlight_keywords(self):
        """
        Подсвечивает ключевые слова в текстовом поле.
        """
        self.output_text.tag_remove('highlight', '1.0', tk.END)
        keywords = [kw.strip() for kw in self.filter_entry.get().split(",") if kw.strip()]
        case = self.case_sensitive.get()
        regex = self.regex_var.get()

        flags = 0 if case else re.IGNORECASE
        for kw in keywords:
            pattern = kw if regex else re.escape(kw)
            start = '1.0'
            while True:
                pos = self.output_text.search(pattern, start, nocase=not case, stopindex=tk.END, regexp=regex)
                if not pos:
                    break
                end = f"{pos}+{len(kw)}c"
                self.output_text.tag_add('highlight', pos, end)
                start = end
        self.output_text.tag_config('highlight', background='yellow')

    def add_html_structure(self, body_content):
        """
        Добавляет базовую структуру HTML к контенту.

        :param body_content: Внутренний контент HTML.
        :return: Полный HTML.
        """
        return f"""
            <html>
            <head>
                <style>
                    body {{
                        font-family: Arial, sans-serif;
                        line-height: 1.6;
                    }}
                    h2 {{
                        color: #2E86C1;
                    }}
                    a {{
                        color: #117A65;
                        text-decoration: none;
                    }}
                    a:hover {{
                        text-decoration: underline;
                    }}
                    hr {{
                        border: 0;
                        height: 1px;
                        background: #ccc;
                        margin: 20px 0;
                    }}
                </style>
            </head>
            <body>
                {body_content}
            </body>
            </html>
        """

    def validate_url(self, url):
        """
        Валидирует введенный URL.

        :param url: Введенный пользователем URL.
        :return: True, если URL валиден, иначе False.
        """
        try:
            result = urlparse(url)
            return all([result.scheme, result.netloc])
        except Exception:
            return False

    def process_queue(self):
        """
        Обрабатывает задачи из очереди потоков.
        """
        try:
            while True:
                task, data = self.queue.get_nowait()
                if task == 'start_progress':
                    self.start_progress()
                elif task == 'stop_progress':
                    self.stop_progress()
                elif task == 'enable_button':
                    self.parse_button.config(state=tk.NORMAL)
                elif task == 'disable_cancel':
                    self.cancel_button.config(state=tk.DISABLED)
                elif task == 'clear_results':
                    self.clear_results()
                elif task == 'insert_text':
                    self.insert_text(data)
                elif task == 'insert_text_full':
                    self.insert_text_full(data)
                elif task == 'update_html':
                    self.update_html(data)
                elif task == 'show_error':
                    self.show_error(data)
                elif task == 'show_warning':
                    self.show_warning(data)
        except queue.Empty:
            pass
        finally:
            self.master.after(100, self.process_queue)

    def start_progress(self):
        """
        Запускает индикатор прогресса.
        """
        self.progress.start()

    def stop_progress(self):
        """
        Останавливает индикатор прогресса.
        """
        self.progress.stop()

    def clear_results(self):
        """
        Очищает поля результатов.
        """
        self.result_label.set_html("")
        self.output_text.delete(1.0, tk.END)

    def show_error(self, message):
        """
        Показывает окно с ошибкой.
        """
        title, text = message
        messagebox.showerror(title, text)

    def show_warning(self, message):
        """
        Показывает окно с предупреждением.
        """
        title, text = message
        messagebox.showwarning(title, text)

    def run(self):
        """
        Запускает обработку очереди задач.
        """
        self.master.after(100, self.process_queue)

class NewsParserApp:
    """
    Основной класс приложения NewsParser.
    """
    def __init__(self, master):
        # Инициализация модулей
        self.cache_module = CacheModule()
        self.db_module = DatabaseModule()
        self.notification_module = NotificationModule()
        self.parser_module = ParserModule(self.cache_module)
        self.ui_module = UIModule(master, self.parser_module, self.db_module, self.cache_module, self.notification_module)
        self.ui_module.run()

    def on_closing(self):
        """
        Обработчик закрытия приложения.
        """
        if messagebox.askokcancel(_("Выход"), _("Вы действительно хотите выйти?")):
            self.db_module.close()
            self.master.destroy()

if __name__ == "__main__":
    # Создание и запуск приложения
    root = tk.Tk()
    app = NewsParserApp(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()