import tkinter as tk
from tkinter import messagebox, filedialog, scrolledtext, ttk
import requests
from bs4 import BeautifulSoup
import feedparser
from tkhtmlview import HTMLLabel
import threading
import re
import logging
from urllib.parse import urlparse
import queue
from logging.handlers import RotatingFileHandler

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

class NewsParser:
    def __init__(self, master):
        self.master = master
        master.title("Парсер новостных лент")
        master.geometry("1000x800")
        master.resizable(True, True)

        # Настройка стилей
        style = ttk.Style()
        style.configure("TLabel", font=('Arial', 10))
        style.configure("TButton", font=('Arial', 10))
        style.configure("TCheckbutton", font=('Arial', 10))
        style.configure("TEntry", font=('Arial', 10))

        # Основной фрейм
        self.main_frame = ttk.Frame(master, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # Ввод URL
        ttk.Label(self.main_frame, text="Введите URL новостной ленты:").grid(
            row=0, column=0, sticky=tk.W, pady=5
        )
        self.url_entry = ttk.Entry(self.main_frame, width=80)
        self.url_entry.grid(row=0, column=1, sticky=tk.W, pady=5)

        # Ввод тега (для HTML)
        ttk.Label(self.main_frame, text="Введите тег для парсинга (например, 'h2'):").grid(
            row=1, column=0, sticky=tk.W, pady=5
        )
        self.tag_entry = ttk.Entry(self.main_frame, width=80)
        self.tag_entry.grid(row=1, column=1, sticky=tk.W, pady=5)

        # Фильтрация по ключевым словам
        ttk.Label(self.main_frame, text="Введите ключевые слова для фильтрации (через запятую):").grid(
            row=2, column=0, sticky=tk.W, pady=5
        )
        self.filter_entry = ttk.Entry(self.main_frame, width=80)
        self.filter_entry.grid(row=2, column=1, sticky=tk.W, pady=5)

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

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

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

        # Кнопки управления
        self.button_frame = ttk.Frame(self.main_frame)
        self.button_frame.grid(row=5, column=0, columnspan=2, pady=10)

        self.parse_button = ttk.Button(self.button_frame, text="Парсить", command=self.start_parse)
        self.parse_button.grid(row=0, column=0, padx=5)

        self.save_html_button = ttk.Button(
            self.button_frame,
            text="Сохранить как HTML",
            command=lambda: self.save_to_file(format='html')
        )
        self.save_html_button.grid(row=0, column=1, padx=5)

        self.save_txt_button = ttk.Button(
            self.button_frame,
            text="Сохранить как TXT",
            command=lambda: self.save_to_file(format='txt')
        )
        self.save_txt_button.grid(row=0, column=2, padx=5)

        self.copy_button = ttk.Button(
            self.button_frame,
            text="Копировать текст",
            command=self.copy_text
        )
        self.copy_button.grid(row=0, column=3, padx=5)

        # Индикатор прогресса
        self.progress = ttk.Progressbar(self.main_frame, mode='indeterminate')
        self.progress.grid(row=6, column=0, columnspan=2, sticky=tk.W + tk.E, pady=5)

        # Вывод HTML результатов
        ttk.Label(self.main_frame, text="HTML результаты:").grid(
            row=7, column=0, sticky=tk.W, pady=5
        )
        self.result_label = HTMLLabel(self.main_frame, width=120, height=20, html="")
        self.result_label.grid(row=8, column=0, columnspan=2, pady=5)

        # Вывод готового текста
        ttk.Label(self.main_frame, text="Готовый текст:").grid(
            row=9, column=0, sticky=tk.W, pady=5
        )
        self.output_text = scrolledtext.ScrolledText(
            self.main_frame, width=120, height=20, wrap=tk.WORD
        )
        self.output_text.grid(row=10, column=0, columnspan=2, pady=5)

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

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

    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 start_parse(self):
        # Проверка лимита перед запуском парсинга
        self.validate_limit()
        # Запуск парсинга в отдельном потоке
        parse_thread = threading.Thread(target=self.parse)
        parse_thread.daemon = True
        parse_thread.start()

    def parse(self):
        # Запуск индикатора прогресса
        self.queue.put(('start_progress', None))
        self.parse_button.config(state=tk.DISABLED)

        url = self.url_entry.get().strip()
        tag = self.tag_entry.get().strip()
        filter_keywords_input = self.filter_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()]
        exact = self.exact_match.get()
        case = self.case_sensitive.get()

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

        # Определение типа контента
        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',
                ("Ошибка", f"Не удалось определить тип контента: {e}")
            ))
            self.queue.put(('stop_progress', None))
            self.queue.put(('enable_button', None))
            return

        if not tag 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:
                self.parse_rss(url, filter_keywords, exact, case, limit)
            else:
                self.parse_html(url, tag, filter_keywords, exact, case, limit)
        except requests.exceptions.RequestException as e:
            logger.error(f"Ошибка при запросе: {e}")
            self.queue.put(('show_error', ("Ошибка", f"Ошибка при запросе: {e}")))
        except Exception as e:
            logger.error(f"Неизвестная ошибка: {e}")
            self.queue.put(('show_error', ("Ошибка", f"Неизвестная ошибка: {e}")))

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

    def validate_url(self, url):
        try:
            result = urlparse(url)
            return all([result.scheme, result.netloc])
        except Exception:
            return False

    def parse_html(self, url, tag, filter_keywords, exact, case, limit):
        try:
            response = requests.get(url, timeout=15)
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            logger.error(f"Ошибка при загрузке страницы: {e}")
            self.queue.put((
                'show_error',
                ("Ошибка", f"Не удалось загрузить страницу: {e}")
            ))
            return

        soup = BeautifulSoup(response.text, 'html.parser')

        results = []
        count = 0
        for item in soup.find_all(tag):
            if limit != 0 and count >= limit:
                break

            # Получаем полный HTML-текст элемента
            html_content = str(item)
            # Извлекаем текст без тегов для фильтрации
            text = item.get_text(separator=' ', strip=True)

            if self.filter_text(text, filter_keywords, exact, case):
                formatted_html = self.format_html_content(title=text, content=html_content)
                results.append(formatted_html)
                self.queue.put(('insert_text', text))
                count += 1

        self.display_results(results)
        if not results:
            logger.info("Нет новостей, соответствующих критериям.")
            self.queue.put(('update_html', "<p>Нет новостей, соответствующих критериям.</p>"))

    def parse_rss(self, url, filter_keywords, exact, case, limit):
        feed = feedparser.parse(url)
        if feed.bozo:
            logger.error(f"Ошибка при парсинге RSS: {feed.bozo_exception}")
            self.queue.put((
                'show_error',
                ("Ошибка", f"Не удалось парсить RSS ленту: {feed.bozo_exception}")
            ))
            return

        results = []
        count = 0

        for entry in feed.entries:
            if limit != 0 and count >= limit:
                break

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

            # Используем BeautifulSoup для обработки HTML в описании
            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 self.filter_text(combined_text, filter_keywords, exact, case):
                entry_html = self.format_html_content(
                    title=title,
                    description=cleaned_description,
                    link=link,
                    published=published
                )
                results.append(entry_html)
                # Отображение в текстовом поле без HTML
                text_content = (
                    f"{title}\n"
                    f"{BeautifulSoup(cleaned_description, 'html.parser').get_text(separator=' ', strip=True)}\n"
                    f"{link}\nОпубликовано: {published}\n\n"
                )
                self.queue.put(('insert_text_full', text_content))
                count += 1

        self.display_results(results)
        if not results:
            logger.info("Нет новостей, соответствующих критериям.")
            self.queue.put(('update_html', "<p>Нет новостей, соответствующих критериям.</p>"))

    def format_html_content(self, title="", description="", link="#", published="", content=""):
        """
        Форматирует контент для отображения в HTMLLabel.
        Приоритет: если content предоставлен, используется вместо описания.
        """
        if content:
            # Используем предоставленный контент (например, для HTML парсинга)
            formatted = f"""
                <div style="margin-bottom: 20px;">
                    <h2>{title}</h2>
                    <div>{content}</div>
                </div>
                <hr>
            """
        else:
            # Формируем контент для RSS
            formatted = f"""
                <div style="margin-bottom: 20px;">
                    <h2>{title}</h2>
                    <div>{description}</div>
                    <p><a href="{link}" target="_blank">Читать далее</a></p>
                    <p><em>Опубликовано: {published}</em></p>
                </div>
                <hr>
            """
        return formatted

    def filter_text(self, text, keywords, exact, case):
        if not keywords:
            return True

        flags = 0 if case else re.IGNORECASE
        for keyword in keywords:
            if exact:
                # Поиск точной фразы
                pattern = re.escape(keyword)
                if re.search(rf'\b{pattern}\b', text, flags):
                    return True
            else:
                # Поиск подстроки
                if re.search(re.escape(keyword), text, flags):
                    return True
        return False

    def display_results(self, results):
        if results:
            # Соединить все результаты в одну строку HTML
            html_content = "<br>".join(results)
            # Добавляем базовую стилизацию для лучшего отображения
            full_html = 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>
                    {html_content}
                </body>
                </html>
            """
            self.queue.put(('update_html', full_html))
        else:
            self.queue.put(('update_html', "<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'):
        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 = f"""
                            <html>
                            <head>
                                <meta charset="UTF-8">
                                <title>Результаты Парсера Новостей</title>
                            </head>
                            <body>
                                {content}
                            </body>
                            </html>
                        """
                with open(file_path, 'w', encoding='utf-8') as file:
                    file.write(content)
                messagebox.showinfo("Сохранение", "Данные успешно сохранены.")
            except Exception as e:
                logger.error(f"Ошибка при сохранении файла: {e}")
                messagebox.showerror("Ошибка", f"Не удалось сохранить файл: {e}")

    def insert_text(self, text):
        self.output_text.insert(tk.END, text + '\n\n')

    def insert_text_full(self, text):
        self.output_text.insert(tk.END, text)

    def update_html(self, html_content):
        self.result_label.set_html(html_content)

    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 start_progress(self):
        self.progress.start()

    def stop_progress(self):
        self.progress.stop()

    def enable_button(self):
        self.parse_button.config(state=tk.NORMAL)

    def process_queue(self):
        try:
            while True:
                task, data = self.queue.get_nowait()
                if task == 'start_progress':
                    self.progress.start()
                elif task == 'stop_progress':
                    self.progress.stop()
                elif task == 'enable_button':
                    self.enable_button()
                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)


if __name__ == "__main__":
    root = tk.Tk()
    app = NewsParser(root)
    root.mainloop()