Загрузка данных


# -*- coding: utf-8 -*-
# main.py

import os
import sys
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk

from PIL import Image, ImageTk

from gui.group_manager import GroupManager
from gui.indicator_manager import IndicatorManager

# --- Импорты из папки gui ---
from gui.interface_expert import InterfaceExpert
from gui.project_manager import ProjectManager
from modules.adminmanager import DatabaseWindow, UsersWindow
from modules.Aggregate_Excel import AggregateFilesWindow

# --- Импорты из папки modules ---
from modules.Export_Excel import ExportMatrixWindow
from modules.login import ChangeRoleWindow, LoginWindow
from modules.project_archive import ArchiveWindow
from modules.project_create import CreateProjectWindow
from modules.project_event import ProjectEventWindow
from modules.database_module import initialize_database
from modules.project_list import ProjectListWindow

# --- Константы цветов ---
HEADER_FOOTER_BG_COLOR = "#007BFF"
BUTTON_TEXT_COLOR = "black"
ORANGE_TEXT_COLOR = "#FFA500"
COMMON_FRAME_BG_COLOR = "#EEEEEE"


class MainWindow(tk.Tk):

    def __init__(self):
        super().__init__()
        self.title("ДОЗОР -- Heri-Hodie-Cras")
        self.geometry("450x360")
        self.resizable(False, False)
        self.configure(bg=COMMON_FRAME_BG_COLOR)

        self.current_user = {}
        self.project_manager = ProjectManager()
        self.is_master_db_active = tk.BooleanVar(value=False)

        # --- ОСНОВНОЙ КОНТЕЙНЕР (как в тестовом файле) ---
        # Убираем сложный main_container и используем один main_frame
        main_frame = tk.Frame(self, bg=COMMON_FRAME_BG_COLOR)
        main_frame.pack(padx=2, pady=2, fill='both', expand=True)

        # --- КОНФИГУРАЦИЯ СЕТКИ ---
        for i in range(6):
            main_frame.grid_rowconfigure(i, weight=0)
        for j in range(2):
            main_frame.grid_columnconfigure(j, weight=0)

        # --- НАСТРОЙКА ШРИФТА И РАЗМЕРА КНОПОК ---
        button_width = 20
        button_height = 2
        button_font = ("Arial", 12, "bold")


        # --- ЛЕВАЯ ПАНЕЛЬ: КНОПКИ 1-5 ---
        # Кнопка 1: Администратор
        btn1 = tk.Button(main_frame,
                         text="Администратор",
                         width=button_width,
                         height=button_height,
                         font=button_font,
                         relief="raised",
                         borderwidth=2,
                         command=lambda: self.open_admin_menu())
        btn1.grid(row=0, column=0, padx=5, pady=5)

        # Кнопка 2: Менеджер базы
        btn2 = tk.Button(main_frame,
                         text="Менеджер базы",
                         width=button_width,
                         height=button_height,
                         font=button_font,
                         relief="raised",
                         borderwidth=2,
                         command=lambda: self.open_base_manager())
        btn2.grid(row=1, column=0, padx=5, pady=5)

        # Кнопка 3: Эксперты
        btn3 = tk.Button(main_frame,
                         text="Эксперты",
                         width=button_width,
                         height=button_height,
                         font=button_font,
                         relief="raised",
                         borderwidth=2,
                         command=lambda: self.open_experts_menu())
        btn3.grid(row=2, column=0, padx=5, pady=5)

        # Кнопка 4: Проект
        btn4 = tk.Button(main_frame,
                         text="Проект",
                         width=button_width,
                         height=button_height,
                         font=button_font,
                         relief="raised",
                         borderwidth=2,
                         command=lambda: self.create_file())
        btn4.grid(row=3, column=0, padx=5, pady=5)

        # Кнопка 5: Прогнозирование
        btn5 = tk.Button(main_frame,
                         text="Прогнозирование",
                         width=button_width,
                         height=button_height,
                         font=button_font,
                         relief="raised",
                         borderwidth=2,
                         command=lambda: self.open_export_matrix_window())
        btn5.grid(row=4, column=0, padx=5, pady=(5, 15)) # Добавили pady внизу для красоты


        # --- ПРАВАЯ ПАНЕЛЬ: КНОПКИ 6-7 И КАРТИНКА ---

        # Картинка (Кнопка 6 и 7 будут под ней)
        picture_frame = tk.Frame(main_frame) # Убрали фон bg="#d9ead3"
        picture_frame.grid(row=0, column=1, rowspan=3, sticky="nsew", ipadx=5, ipady=5)

        current_dir = os.path.dirname(os.path.abspath(__file__))
        image_path = os.path.join(current_dir, "images", "Сова1.png")

        if hasattr(self, "load_and_resize_image"):
            self.tk_image = self.load_and_resize_image(image_path, 300, 170)
            if self.tk_image:
                label = tk.Label(picture_frame, image=self.tk_image)
                label.image = self.tk_image # Сохраняем ссылку
                label.pack()

        # Кнопка 6: Руководство
        btn6 = tk.Button(main_frame,
                        text="Руководство",
                        width=button_width,
                        height=button_height,
                        font=button_font,
                        relief="raised",
                        borderwidth=2)
        btn6.grid(row=3, column=1, padx=5, pady=(5, 0)) # pady сверху для отступа от картинки

        # Кнопка 7: О программе
        btn7 = tk.Button(main_frame,
                        text="О программе",
                        width=button_width,
                        height=button_height,
                        font=button_font,
                        relief="raised",
                        borderwidth=2)
        btn7.grid(row=4, column=1, padx=5, pady=(5, 15)) # pady для симметрии с левой панелью


        # --- НИЖНЯЯ ЧАСТЬ: ПОДВАЛ ---
        footer_frame = tk.Frame(main_frame, bg="#0078D7")
        footer_frame.grid(row=5, column=0, columnspan=2, sticky="ew", pady=(5,0))

        footer_label = tk.Label(
            footer_frame,
            text="Copyright © Минск, НИИ, 2027 год",
            fg="#FF8C00",
            bg="#0078D7",
            font=( "Arial", 12, "bold")
        )
        footer_label.pack(expand=True)

    # --- МЕТОДЫ ДЛЯ ОТКРЫТИЯ ОКОН ---

    def load_and_resize_image(self, path, max_width, max_height):
        """Загружает и изменяет размер изображения."""
        try:
            original_img = Image.open(path)
            ratio = min(
                max_width / original_img.width, max_height / original_img.height
            )
            resized_img = original_img.resize(
                (int(original_img.width * ratio), int(original_img.height * ratio)),
                resample=Image.LANCZOS,
            )
            return ImageTk.PhotoImage(resized_img)
        except FileNotFoundError:
            print(f"[WARNING] Изображение не найдено по пути: {path}")
            # Возвращаем None или заглушку, чтобы программа не упала
            return None

    def open_create_project_window(self):
        CreateProjectWindow(self.project_manager)

    def open_archive_window(self):
        ArchiveWindow()

    def open_group_manager(self):
        if self.current_user.get("role") != "Администратор":
            messagebox.showwarning(
                "Доступ запрещён", "Только администратор может управлять группами."
            )
            return
        group_manager = GroupManager(self)
        group_manager.grab_set()
        group_manager.wait_window()

    def open_indicator_manager(self):
        indicator_manager = IndicatorManager(self)

        self.update_idletasks()
        parent_x = self.winfo_x()
        parent_y = self.winfo_y()
        parent_w = self.winfo_width()
        parent_h = self.winfo_height()

        w = indicator_manager.winfo_width()
        h = indicator_manager.winfo_height()

        x = parent_x + (parent_w - w) // 2
        y = parent_y + (parent_h - h) // 2

        indicator_manager.geometry(f"+{x}+{y}")

        indicator_manager.grab_set()
        indicator_manager.wait_window()

    def open_admin_menu(self):
        admin_menu = tk.Menu(self, tearoff=0)
        admin_menu.add_command(label="Пользователи", command=self.open_users_window)
        admin_menu.add_command(label="Базы данных", command=self.open_database_window)

        root_x = self.winfo_rootx()
        root_y = self.winfo_rooty()

        menu_x = root_x + 100
        menu_y = root_y + 100

        admin_menu.post(menu_x, menu_y)

    def open_users_window(self):
        # Проверка роли пользователя остается без изменений
        if not self.current_user.get("role"):
            messagebox.showwarning(
                "Ошибка", "Вы не авторизованы. Пожалуйста, выполните вход."
            )
            return

        success = self.propose_admin_access()
        
        if success or self.current_user.get("role") == "Администратор":
            
            # --- НОВЫЙ КОД ---
            # Создаем окно UsersWindow БЕЗ передачи функций или ссылок на logout.
            # Окно будет работать само по себе.
            users_window = UsersWindow(parent=self)
             
            # Блокируем главное окно и ждем закрытия окна пользователей
            users_window.grab_set()
            users_window.wait_window()
            
            # --- ИЗМЕНЕНИЕ В КЛАССЕ USERS WINDOW ---
            # Теперь логика выхода находится ТАМ.
            # Когда пользователь нажмет кнопку там, оно вызовет logout() у своего родителя.

    def open_database_window(self):
        if self.current_user.get("role"):
            success = self.propose_admin_access()
            if success or self.current_user.get("role") == "Администратор":
                db_window = DatabaseWindow(self)
                db_window.grab_set()
                db_window.wait_window()
        else:
            messagebox.showwarning(
                "Ошибка", "Вы не авторизованы. Пожалуйста, выполните вход."
            )

    def propose_admin_access(self):
        if self.current_user.get("role") != "Администратор":
            confirm_dialog = ChangeRoleWindow(self)
            confirm_dialog.grab_set()
            confirm_dialog.wait_window()

    def create_file(self):
        """Открывает меню управления проектами при нажатии кнопки 'Проект'."""
        project_menu = tk.Menu(self, tearoff=0)
        project_menu.add_command(
            label="Создать проект", command=self.open_create_project_window
        )
        project_menu.add_command(
            label="Редактировать проект", command=self.open_edit_project_window
        )
        project_menu.add_command(
            label="Архив проектов", command=self.open_archive_window
        )

        root_x = self.winfo_rootx()
        root_y = self.winfo_rooty()

        menu_x = root_x + 100  # Смещение по X от левого верхнего угла главного окна
        menu_y = root_y + 100  # Смещение по Y

        project_menu.post(menu_x, menu_y)

    def open_base_manager(self):
        base_menu = tk.Menu(self, tearoff=0)
        base_menu.add_command(label="Менеджер групп", command=self.open_group_manager)
        base_menu.add_command(
            label="Менеджер показателей", command=self.open_indicator_manager
        )

        root_x = self.winfo_rootx()
        root_y = self.winfo_rooty()

        menu_x = root_x + 100
        menu_y = root_y + 100

        base_menu.post(menu_x, menu_y)

    def open_experts_menu(self):
        experts_menu = tk.Menu(self, tearoff=0)
        experts_menu.add_command(
            label="Создание файла для эксперта", command=self.open_create_file_window
        )

        experts_menu.add_command(
            label="Агрегирование файлов", command=self.open_aggregation_window
        )

        root_x = self.winfo_rootx()
        root_y = self.winfo_rooty()

        menu_x = root_x + 100
        menu_y = root_y + 100

        experts_menu.post(menu_x, menu_y)

    def open_create_file_window(self):
        create_file_window = ExportMatrixWindow(self)

        self.update_idletasks()
        parent_x = self.winfo_x()
        parent_y = self.winfo_y()
        parent_w = self.winfo_width()
        parent_h = self.winfo_height()

        w = create_file_window.winfo_width()
        h = create_file_window.winfo_height()

        x = parent_x + (parent_w - w) // 2
        y = parent_y + (parent_h - h) // 2

        create_file_window.geometry(f"+{x}+{y}")

        create_file_window.grab_set()
        create_file_window.wait_window()

    def open_aggregation_window(self):
        aggregation_window = AggregateFilesWindow(self)

        self.update_idletasks()
        parent_x = self.winfo_x()
        parent_y = self.winfo_rooty()
        parent_w = self.winfo_width()
        parent_h = self.winfo_height()

        w = aggregation_window.winfo_width()
        h = aggregation_window.winfo_height()

        x = parent_x + (parent_w - w) // 2
        y = parent_y + (parent_h - h) // 2

        aggregation_window.geometry(f"+{x}+{y}")

        aggregation_window.grab_set()
        aggregation_window.wait_window()

    def on_exit(self):
        """Корректно закрывает приложение."""
        self.destroy()

    def start_login_process(self):
        login_window = LoginWindow(self)
        login_window.grab_set()
        login_window.wait_window()

        # --- ДОБАВЛЕНИЕ ТЕСТОВЫХ ПРОЕКТОВ ---
        try:
            from gui.project_manager import Project

            test_project_1_name = "Тестовый проект №1"
            test_project_2_name = "Тестовый проект №2"

            existing_names = [p.name for p in self.project_manager.list_projects()]

            # Добавляем проект 1, если его нет
            if test_project_1_name not in existing_names:
                new_project_1_obj = Project(name=test_project_1_name)
                new_project_1_obj.id = "TEST-001"
                self.project_manager.add_project(new_project_1_obj)

            # Добавляем проект 2, если его нет
            if test_project_2_name not in existing_names:
                new_project_2_obj = Project(name=test_project_2_name)
                new_project_2_obj.id = "TEST-002"
                self.project_manager.add_project(new_project_2_obj)

        except Exception as e:

            pass  # Лучше ничего не делать, если не удалось добавить тесты

    # --- РЕДАКТИРОВАНИЕ ПРОЕКТА ---
    def open_edit_project_window(self):
        """Открывает окно для выбора проекта."""
        # print("DEBUG: Открываем окно выбора проекта...")
        # Просто создаем окно выбора.
        # Нам не нужно его хранить или ждать.
        list_window = ProjectListWindow(self.project_manager, root_window=self)

        # Добавляем кнопку
        open_event_btn = tk.Button(
            list_window.window,
            text="Открыть ввод событий",
            width=25,
            bg="#4CAF50",
            fg="white",
            command=lambda: self.open_event_window_from_list(list_window),
        )
        open_event_btn.pack(pady=10)

    def return_from_event_window(self):
        """Этот метод вызывается из окна ProjectEventWindow при закрытии.
        Он показывает обратно скрытое окно со списками."""
        print("DEBUG: Пользователь возвращается в окно выбора параметров.")
        try:
            # Проверяем, что ссылка на окно со списками существует
            if hasattr(self, "list_window_instance"):
                # Снимаем блокировку с главного окна
                self.list_window_instance.window.grab_release()

                # Показываем окно со списками обратно
                self.list_window_instance.window.deiconify()

                # Принудительно выводим его на передний план
                self.list_window_instance.window.attributes("-topmost", 1)
                self.list_window_instance.window.after(
                    100,
                    lambda: self.list_window_instance.window.attributes("-topmost", 0),
                )

        except Exception as e:
            print(f"ОШИБКА при возврате в окно выбора: {e}")

    def open_event_window_from_list(self, list_window_instance):
        """Получает данные и открывает окно событий."""
        # --- НОВОЕ: Блокируем кнопку, чтобы избежать двойного клика ---
        try:
            # Получаем кнопку из переданного окна и блокируем ее
            # Предполагается, что кнопка имеет имя или мы ее находим
            # Если кнопка это переменная в list_window_instance, используй ее.
            # Если нет, найди ее по тексту или положи в переменную при создании.
            # Здесь я покажу универсальный способ через поиск по тексту.
            for widget in list_window_instance.window.winfo_children():
                if isinstance(
                    widget, tk.Button
                ) and "Открыть ввод событий" in widget.cget("text"):
                    widget.config(state="disabled")
                    break

            # Теперь основная логика
            selected_data = list_window_instance.get_selected_data()

            if not selected_data.get("project_name"):
                # Если кнопка заблокирована, вернем ей состояние NORMAL
                for widget in list_window_instance.window.winfo_children():
                    if isinstance(widget, tk.Button):
                        widget.config(state="normal")
                messagebox.showwarning("Ошибка", "Сначала выберите проект.")
                return

            # Уничтожаем окно выбора.
            list_window_instance.window.destroy()

            # Создаем окно событий.
            event_window = ProjectEventWindow(
                manager=list_window_instance.manager,
                selected_data=selected_data,
                root_window=self,
            )
            event_window.window.after(100, event_window.load_events)

        except Exception as e:
            pass  # Логирование убрано для чистоты

    def return_from_event_window(self):
        """Этот метод вызывается из окна ProjectEventWindow при закрытии."""
        try:
            if hasattr(self, "list_window_instance"):
                self.list_window_instance.window.grab_release()

            # --- НОВОЕ: Разблокируем кнопки в главном окне ---
            self.enable_all_buttons()

        except Exception as e:
            pass

    def show_selection_window(self):
        """Показывает окно выбора проектов обратно."""
        try:
            if (
                hasattr(self, "list_window_instance")
                and self.list_window_instance.window.winfo_exists()
            ):
                self.list_window_instance.window.deiconify()
            else:
                self.open_edit_project_window()
        except Exception as e:
            pass

    def enable_all_buttons(self):
        """Проходит по всем виджетам и включает все кнопки (снимает блокировку)."""
        for frame in self.winfo_children():
            for widget in frame.winfo_children():
                if isinstance(widget, tk.Button):
                    widget.config(state="normal")

    def open_export_matrix_window(self):
        """
        Открывает окно прогнозирования.
        Создает новый экземпляр ForecastWindow каждый раз.
        """
        # Проверяем, не было ли окно уже открыто, и закрываем его, если нужно
        if hasattr(self, 'forecast_window'):
            try:
                self.forecast_window.window.destroy()
            except Exception as e:
                print(f"Окно уже закрыто: {e}")
        
        # --- ИЗМЕНЕНИЕ ---
        # Перед созданием нового окна, проверим наличие виджета для вывода текста
        # Если его нет (при первом запуске), создадим его в главном окне
        if not hasattr(self, 'result_frame'):
            
            # Фрейм для блока с результатами
            self.result_frame = ttk.LabelFrame(self, text="Результаты прогнозирования", padding="5")
            self.result_frame.pack(side="bottom", fill="x") # Размещаем внизу главного окна

            # Текстовое поле с прокруткой для вывода логов и результатов
            self.result_text = tk.Text(self.result_frame, height=8, width=70, wrap='word', state='disabled')
            
            scrollbar = ttk.Scrollbar(self.result_frame, command=self.result_text.yview)
            scrollbar.pack(side="right", fill="y")
            self.result_text.configure(yscrollcommand=scrollbar.set)
            
            self.result_text.pack(padx=10, pady=5, fill="both", expand=True)
        
        # Теперь создаем само окно прогнозирования
        from gui.forecast_window import ForecastWindow
        self.forecast_window = ForecastWindow(self)


if __name__ == "__main__":
    # 1. Инициализируем базу данных в самом начале
    initialize_database()

    try:
        import sys
        import os
        import logging # Импортируем логгинг здесь, чтобы использовать в блоке

        # --- 1. ВРЕМЕННО ОТКЛЮЧАЕМ ВЫВОД В КОНСОЛЬ ---
        old_stdout = sys.stdout
        old_stderr = sys.stderr
        devnull = open(os.devnull, "w")
        sys.stdout = devnull
        sys.stderr = devnull

        # --- 2. ЗАПУСКАЕМ ПРИЛОЖЕНИЕ ---
        app = MainWindow()

        # --- 3. ВОЗВРАЩАЕМ ВЫВОД ОБРАТНО ---
        sys.stdout = old_stdout
        sys.stderr = old_stderr
        devnull.close()

        # --- 4. ЗАПУСК ОКНА ЛОГИНА ---
        app.withdraw()

        login_window = LoginWindow(app)

        # Привязываем обработчик закрытия к окну логина
        login_window.protocol("WM_DELETE_WINDOW", login_window.on_close)

        login_window.grab_set()
        login_window.wait_window()


        # --- 5. ПРОВЕРКА ПОСЛЕ ЗАКРЫТИЯ ЛОГИНА ---
        # Если пользователь не вошел в систему, закрываем приложение
        if not app.current_user:
            app.destroy()
            sys.exit(0)

        # --- 6. ОКОНЧАТЕЛЬНО ОТКЛЮЧАЕМ ЛОГИ ---
        logging.disable(logging.INFO)


        # --- 7. ЗАПУСК ОСНОВНОГО ЦИКЛА ПРИЛОЖЕНИЯ ---
        app.mainloop()

    except Exception as e:
        print(f"Возникла критическая ошибка при запуске приложения: {e}")