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


# -*- coding: utf-8 -*-
# gui/indicator_manager.py

import os
import random
import tkinter as tk
from tkinter import filedialog, messagebox, ttk

from fpdf import FPDF
from sqlalchemy import Integer, create_engine, func
from sqlalchemy.orm import sessionmaker

# Импортируем из модуля базы данных
from modules.database_module import Indicator
from modules.database_module import Session as DBSession
from modules.database_module import (
    delete_indicator_by_code,
    get_all_indicators_by_group,
    insert_indicator,
    list_groups,
)

DEBUG_LOGGING = False


class IndicatorManager(tk.Toplevel):

    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent
        self.title("Менеджер показателей")
        self.geometry("600x450")

        # --- Инициализация и доступ к базе данных ---
        if not hasattr(parent, "current_user"):
            raise AttributeError("Ошибка: Родительское окно не авторизовано.")

        if not self.check_role_access(parent):
            messagebox.showwarning(
                "Доступ запрещён", "Нужен статус администратора или эксперта."
            )
            self.destroy()
            return

        self.use_master_db = self.parent.is_master_db_active

        # Создаем движок (engine) и сессию ОДИН РАЗ при создании окна
        db_path = "masterdata.db" if self.use_master_db.get() else "database.db"
        self.engine = create_engine(f"sqlite:///{db_path}", echo=False)
        # <-- ЕДИНАЯ СЕССИЯ для всего окна
        self.session = DBSession(bind=self.engine)

        # Привязываем закрытие окна к методу закрытия сессии
        self.protocol("WM_DELETE_WINDOW", self.on_close)

        # Словарь для связи названий групп с их кодами
        self.group_names_to_codes = {}

        # --- Создание интерфейса ---
        self.create_widgets()

        # Первоначальное заполнение данных
        self.update_group_combobox()
        self.update_indicators_list()

    def create_widgets(self):
        """Метод для создания всех виджетов интерфейса."""
        top_panel = tk.Frame(self)
        top_panel.pack(padx=10, pady=10, fill="x")

        # Выбор группы
        tk.Label(top_panel, text="Группа:").pack(anchor="w")
        self.group_var = tk.StringVar()
        self.group_combobox = ttk.Combobox(
            top_panel, textvariable=self.group_var, state="readonly"
        )
        self.group_combobox.pack(fill="x", pady=5)
        self.group_combobox.bind("<<ComboboxSelected>>", self.update_indicators_list)

        # Поле ввода названия
        tk.Label(top_panel, text="Название показателя:").pack(anchor="w")
        self.name_entry = tk.Text(top_panel, height=3, width=40)
        self.name_entry.pack(fill="x", pady=5)

        # Кнопка вставки из буфера (упрощенная логика)
        paste_button = tk.Button(
            top_panel,
            text="Вставить",
            command=lambda: self.name_entry.insert(tk.END, self.clipboard_get()),
        )
        paste_button.pack(anchor="e")

        # Кнопки действий
        buttons_frame = tk.Frame(top_panel)
        buttons_frame.pack(fill="x", pady=5)

        add_btn = tk.Button(buttons_frame, text="Добавить", command=self.add_indicator)
        add_btn.pack(side="left", padx=2)

        del_btn = tk.Button(
            buttons_frame, text="Удалить", command=self.delete_indicator
        )
        del_btn.pack(side="left", padx=2)

        print_btn = tk.Button(
            buttons_frame, text="Печать PDF", command=self.print_to_pdf
        )
        print_btn.pack(side="left", padx=2)

        # Список показателей со скроллбаром
        middle_panel = tk.Frame(self)
        middle_panel.pack(fill="both", expand=True, padx=10, pady=5)

        scrollbar = tk.Scrollbar(middle_panel)
        scrollbar.pack(side="right", fill="y")

        self.indicators_list = tk.Text(
            middle_panel,
            wrap="word",
            yscrollcommand=scrollbar.set,
            font=("Arial", 10),
            state="disabled",
        )
        self.indicators_list.pack(fill="both", expand=True)

        scrollbar.config(command=self.indicators_list.yview)

        # Биндинг событий для списка
        self.indicators_list.bind("<Double-Button-1>", self.edit_indicator)

    def on_close(self):
        """Корректно закрывает сессию и окно."""
        try:
            self.session.close()
            print("Сессия базы данных закрыта.")
        except Exception as e:
            print(f"Ошибка при закрытии сессии: {e}")
        finally:
            self.destroy()

    def add_indicator(self):
        group_name = self.group_var.get()
        group_code = self.group_names_to_codes.get(group_name)
        indicator_name = self.name_entry.get("1.0", tk.END).rstrip("\n").strip()

        if not group_code or not indicator_name:
            messagebox.showwarning("Ошибка", "Выберите группу и введите название!")
            return

        new_code = self.generate_unique_code(group_code)

        new_indicator = Indicator(
            code=new_code, name=indicator_name, group_code=group_code
        )
        self.session.add(new_indicator)
        self.session.commit()  # Сохраняем в главной сессии

        self.name_entry.delete("1.0", tk.END)
        self.update_indicators_list()

    def generate_unique_code(self, group_code):
        """Генерирует гарантированно уникальный код."""
        index = 1
        # Цикл будет выполняться, пока не найдется свободный код
        while True:
            new_code = f"{group_code}-{index}"

            # Проверяем в базе, есть ли запись с таким кодом
            exists = (
                self.session.query(Indicator.code).filter_by(code=new_code).first()
                is not None
            )

            if not exists:
                return new_code  # Возвращаем первый же свободный код

            # Если код занят, пробуем следующий номер по порядку
            index += 1

    def update_group_combobox(self):
        """Заполняет выпадающий список групп."""
        groups = list_groups()  # Предполагаем, что эта функция работает с текущей базой
        self.group_names_to_codes.clear()
        for group in groups:
            self.group_names_to_codes[group.name] = group.code

        group_names = [group.name for group in groups]
        self.group_combobox["values"] = group_names

        if group_names:
            self.group_combobox.current(0)
            self.update_indicators_list()  # Обновить список при первой загрузке

    def update_indicators_list(self, event=None):
        """Обновляет список индикаторов в виджете Text."""
        selected_group_name = self.group_var.get()
        selected_group_code = self.group_names_to_codes.get(selected_group_name)

        if not selected_group_code:
            return

        # Используем главную сессию self.session!
        indicators = (
            self.session.query(Indicator)
            .filter_by(group_code=selected_group_code)
            .all()
        )

        self.indicators_list.configure(state="normal")
        self.indicators_list.delete("1.0", tk.END)

        for i, ind in enumerate(indicators):
            self.indicators_list.insert(tk.END, f"{i + 1}. {ind.name}\n")
            self.indicators_list.insert(tk.END, f"Код: {ind.code}\n\n")

        self.indicators_list.configure(state="disabled")

    def edit_indicator(self, event):
        """Открывает диалог для редактирования выбранного индикатора."""
        try:
            cursor_pos = self.indicators_list.index(tk.INSERT)
            line_num = int(cursor_pos.split(".")[0])

            # Вычисляем индекс выбранного элемента (3 строки на элемент:
            # название, код, пустая строка)
            index = (line_num - 1) // 3

            # Получаем текст из виджета и парсим его
            text_content = self.indicators_list.get("1.0", "end-1c")
            lines = text_content.split("\n")

            name_line = lines[index * 3]
            code_line = lines[index * 3 + 1]

            name = name_line.split(". ", 1)[1]
            code = code_line.split(": ", 1)[1]

            edit_win = tk.Toplevel(self)
            edit_win.title("Редактировать")
            edit_win.geometry("350x150")

            tk.Label(edit_win, text="Новое название:").pack(pady=5)
            new_name_entry = tk.Text(edit_win, height=2, width=40)
            new_name_entry.insert(tk.END, name)
            new_name_entry.pack(pady=5)

            save_btn = tk.Button(
                edit_win,
                text="Сохранить",
                command=lambda: self.save_changes(
                    code, new_name_entry.get("1.0", tk.END), edit_win
                ),
            )
            save_btn.pack(pady=5)

        except (IndexError, ValueError):
            pass  # Кликнули не по элементу списка

    def save_changes(self, code, new_name_raw, edit_win):
        """Сохраняет изменения в базу данных."""
        new_name = new_name_raw.rstrip("\n").strip()

        if not new_name:
            messagebox.showwarning("Ошибка", "Название не может быть пустым.")
            return

        # Используем главную сессию!
        indicator = self.session.query(Indicator).filter_by(code=code).first()
        if indicator:
            indicator.name = new_name
            self.session.commit()  # Сохраняем в главной сессии
            self.update_indicators_list()
            messagebox.showinfo("Готово", "Изменения сохранены.")
            edit_win.destroy()
        else:
            messagebox.showwarning("Ошибка", "Индикатор не найден.")

    def delete_indicator(self):
        try:
            cursor_pos = self.indicators_list.index(tk.INSERT)
            line_num = int(cursor_pos.split(".")[0])
            index = (line_num - 1) // 3

            text_content = self.indicators_list.get("1.0", "end-1c")
            lines = text_content.split("\n")

            code_line = lines[index * 3 + 1]
            code = code_line.split(": ", 1)[1]

            if messagebox.askyesno(
                "Подтверждение", f"Удалить индикатор с кодом {code}?"
            ):
                success = delete_indicator_by_code(code)
                if success:
                    messagebox.showinfo("Готово", "Удалено успешно.")
                    self.update_indicators_list()
                else:
                    messagebox.showerror("Ошибка", "Не удалось удалить.")
        # Исправлено: отступ такой же, как у 'try'
        except (IndexError, ValueError):
            messagebox.showwarning("Ошибка", "Выберите индикатор для удаления.")

    def print_to_pdf(self):
        """Экспортирует список индикаторов в PDF."""
        group_name = self.group_var.get()
        if not group_name:
            messagebox.showwarning("Ошибка", "Выберите группу.")
            return

        group_code = self.group_names_to_codes.get(group_name)
        indicators = (
            self.session.query(Indicator).filter_by(group_code=group_code).all()
        )

        if not indicators:
            messagebox.showwarning("Ошибка", "В группе нет показателей.")
            return

        pdf = FPDF()
        try:
            pdf.add_font("DejaVuSans", "", "DejaVuSans.ttf", uni=True)
        except BaseException:
            pass  # Шрифт может быть уже добавлен или отсутствовать

        pdf.add_page()
        pdf.set_font("DejaVuSans", size=12)

        pdf.cell(0, 10, f"Группа: {group_name}", ln=True, align="C")
        pdf.ln(5)

        for idx, ind in enumerate(indicators):
            pdf.cell(0, 8, f"{idx + 1}. {ind.name}", ln=True)
            pdf.cell(0, 8, f"Код: {ind.code}", ln=True)
            pdf.ln(4)

        filename = f"{group_name.replace(' ', '_')}_indicators.pdf"
        path = filedialog.asksaveasfilename(
            initialfile=filename,
            defaultextension=".pdf",
            filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")],
        )

        if path:
            try:
                pdf.output(path)
                messagebox.showinfo(
                    "Готово", f"Файл сохранен как:\n{os.path.basename(path)}"
                )
            except Exception as e:
                messagebox.showerror("Ошибка сохранения", str(e))

    def check_role_access(self, parent):
        user_roles = {"Администратор", "Эксперт"}
        current_user = getattr(parent, "current_user", {})
        role = current_user.get("role")
        return bool(role) and role in user_roles


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

    # Для теста создаем фиктивного родителя с нужными атрибутами
    class FakeParent:

        def __init__(self):
            self.current_user = {"role": "Администратор"}
            self.is_master_db_active = tk.BooleanVar(value=False)

    fake_parent = FakeParent()

    manager = IndicatorManager(fake_parent)
    root.mainloop()