import os
import pandas as pd
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QVBoxLayout,
    QHBoxLayout,
    QWidget,
    QPushButton,
    QListWidget,
    QLabel,
    QFileDialog,
    QMessageBox,
    QTextEdit,
)
from PyQt6.QtCore import QThread, pyqtSignal
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.utils import ImageReader
import logging

# Настройка логирования
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def register_fonts():
    try:
        pdfmetrics.registerFont(TTFont('DejaVuSans', 'DejaVuSans.ttf'))
        pdfmetrics.registerFont(TTFont('DejaVuSans-Bold', 'DejaVuSans-Bold.ttf'))
    except Exception as e:
        logging.error(f"Ошибка при регистрации шрифтов: {e}")
        raise

register_fonts()

custom_page_size = A4

class PDFGeneratorThread(QThread):
    log_signal = pyqtSignal(str)

    def __init__(self, folder_paths, save_path, photo_files):
        super().__init__()
        self.folder_paths = folder_paths
        self.save_path = save_path
        self.photo_files = photo_files

    def run(self):
        try:
            data = self.aggregate_data()
            if data.empty:
                self.log_signal.emit("Нет данных для обработки.")
                return
            photo_data = self.aggregate_photo_data()
            self.generate_pdfs(data, photo_data)
        except Exception as e:
            self.log_signal.emit(f"Ошибка при выполнении: {e}")

    def aggregate_data(self):
        data = pd.DataFrame()
        for folder in self.folder_paths:
            if not os.path.isdir(folder):
                self.log_signal.emit(f"Предупреждение: {folder} — не существует.")
                continue
            for file in os.listdir(folder):
                if file.endswith(('.xlsx', '.xls')):
                    file_path = os.path.join(folder, file)
                    try:
                        xls = pd.ExcelFile(file_path)
                        for sheet_name in xls.sheet_names:
                            file_data = pd.read_excel(file_path, sheet_name=sheet_name)
                            self.log_signal.emit(
                                f"Прочитан файл: {file_path}, лист: {sheet_name}, столбцы: {file_data.columns.tolist()}"
                            )
                            data = pd.concat([data, file_data], ignore_index=True)
                    except Exception as e:
                        self.log_signal.emit(f"Ошибка при чтении файла {file_path}: {e}")

        return self.check_columns(data)

    def check_columns(self, data):
        required_columns = [
            "Период с", "Период по", "Код ТТ", "Название ТТ", "Тип оборудования",
            "Наименование проекта", "Наименование оборудования", "Полный период размещения",
            "Ссылка на ПГ", "Зона размещения",
            "Действия ТТ по окончанию размещения", "Код позиции",
            "Наименование позиции", "Загрузка в шт"
        ]
        optional_columns = ["Комментарий", "Место выкладки товара (адрес)"]

        missing_required = [col for col in required_columns if col not in data.columns]
        if missing_required:
            self.log_signal.emit(f"Ошибка: отсутствуют столбцы {missing_required} в данных.")
            return pd.DataFrame()

        for col in optional_columns:
            if col not in data.columns:
                data[col] = "-"
                self.log_signal.emit(f"Информация: столбец '{col}' не найден и был добавлен с дефолтными значениями.")

        data["Период с"] = pd.to_datetime(data["Период с"], format="%d.%m.%Y", errors='coerce')
        data["Период по"] = pd.to_datetime(data["Период по"], format="%d.%m.%Y", errors='coerce')
        return data

    def aggregate_photo_data(self):
        photo_data = pd.DataFrame()
        for file_path in self.photo_files:
            if not os.path.exists(file_path):
                self.log_signal.emit(f"Предупреждение: Файл {file_path} не существует.")
                continue
            try:
                file_data = pd.read_excel(file_path)
                self.log_signal.emit(
                    f"Прочитан файл с фотографиями: {file_path}, столбцы: {file_data.columns.tolist()}"
                )

                required_photo_columns = ["Название проекта", "Путь"]
                missing_photo_columns = [col for col in required_photo_columns if col not in file_data.columns]
                if missing_photo_columns:
                    self.log_signal.emit(f"Ошибка: отсутствуют столбцы {missing_photo_columns} в файле с фотографиями {file_path}.")
                    continue
                photo_data = pd.concat([photo_data, file_data], ignore_index=True)
            except Exception as e:
                self.log_signal.emit(f"Ошибка при чтении файла {file_path}: {e}")
        return photo_data

    def generate_pdfs(self, data, photo_data):
        data_by_tt = data.groupby("Код ТТ")
        for tt_code, tt_data in data_by_tt:
            tt_name = tt_data["Название ТТ"].iloc[0]
            period_start = tt_data["Период с"].min()
            period_end = tt_data["Период по"].max()

            period_start_str = period_start.strftime('%Y-%m-%d') if pd.notnull(period_start) else "unknown_start"
            period_end_str = period_end.strftime('%Y-%m-%d') if pd.notnull(period_end) else "unknown_end"

            safe_tt_name = "".join([c if c.isalnum() or c in (' ', '_', '-') else "_" for c in tt_name])
            pdf_name = f"{tt_code}_{safe_tt_name}_{period_start_str}_{period_end_str}.pdf"
            pdf_path = os.path.join(self.save_path, pdf_name)

            c = canvas.Canvas(pdf_path, pagesize=custom_page_size)
            width, height = custom_page_size

            y_position = height - 50

            self.draw_text(c, "Администратору необходимо осуществить выкладку товара на ДМП:", 30, y_position, "DejaVuSans", 10)
            y_position -= 20

            equipment_types = [
                "Магнит-хит", "Оборудование КА", "Навесное оборудование",
                "Оборудование сети", "Желтый акционный стеллаж"
            ]
            for equipment_type in equipment_types:
                eq_data = tt_data[tt_data["Тип оборудования"] == equipment_type]
                if not eq_data.empty:
                    y_position = self.start_new_page_if_needed(c, y_position, height)
                    self.draw_text(c, f"Оборудование: {equipment_type}", 30, y_position, "DejaVuSans-Bold", 10)
                    y_position -= 20

                    y_position = self.process_projects(c, eq_data, equipment_type, photo_data, width, height, y_position)

            c.save()
            self.log_signal.emit(f"PDF создан: {pdf_path}")

    def process_projects(self, c, eq_data, equipment_type, photo_data, width, height, y_position):
        for project, project_data in eq_data.groupby("Наименование проекта"):
            self.draw_text(c, f"Проект: {project}", 30, y_position, "DejaVuSans", 8)
            y_position -= 20

            details = self.get_details(equipment_type, project_data)
            for detail in details:
                y_position = self.start_new_page_if_needed(c, y_position, height)
                self.draw_text(c, detail, 40, y_position, "DejaVuSans", 8)
                y_position -= 18

            table_columns = self.get_table_columns(equipment_type)
            if table_columns and all(col in project_data.columns for col in table_columns):
                table_data = project_data[table_columns].drop_duplicates().fillna("-")
                y_position = self.draw_table(c, table_data, table_columns, width, height, y_position, project)
                y_position = self.add_photos_to_pdf(c, photo_data, project, width, height, y_position, table_columns)
            else:
                self.log_signal.emit(f"Табличные данные отсутствуют для проекта '{project}' или необходимые столбцы отсутствуют.")
        return y_position

    def start_new_page_if_needed(self, c, y_position, height):
        if y_position < 100:
            c.showPage()
            y_position = height - 50
        return y_position

    def get_details(self, equipment_type, project_data):
        if equipment_type in ["Магнит-хит", "Оборудование сети"]:
            return [
                f"Название оборудования: {project_data['Наименование оборудования'].iloc[0]}",
                f"Полный период размещения: {project_data['Полный период размещения'].iloc[0]}",
                f"Ссылка на планограмму: {project_data['Ссылка на ПГ'].iloc[0] if pd.notnull(project_data['Ссылка на ПГ'].iloc[0]) else '-'}",
                f"Комментарий: {project_data['Комментарий'].iloc[0] if pd.notnull(project_data['Комментарий'].iloc[0]) else '-'}",
            ]
        elif equipment_type in ["Навесное оборудование", "Оборудование КА"]:
            return [
                f"Зона размещения: {project_data['Зона размещения'].iloc[0] if pd.notnull(project_data['Зона размещения'].iloc[0]) else '-'}",
                f"Название оборудования: {project_data['Наименование оборудования'].iloc[0]}",
                f"Полный период размещения: {project_data['Полный период размещения'].iloc[0]}",
                f"Действия ТТ по окончанию размещения: {project_data['Действия ТТ по окончанию размещения'].iloc[0]}",
                f"Комментарий: {project_data['Комментарий'].iloc[0] if pd.notnull(project_data['Комментарий'].iloc[0]) else '-'}",
            ]
        elif equipment_type == "Желтый акционный стеллаж":
            return [
                f"Зона размещения: {project_data['Зона размещения'].iloc[0]}",
                f"Ссылка на планограмму: {project_data['Ссылка на ПГ'].iloc[0] if pd.notnull(project_data['Ссылка на ПГ'].iloc[0]) else '-'}",
                f"Комментарий: {project_data['Комментарий'].iloc[0] if pd.notnull(project_data['Комментарий'].iloc[0]) else '-'}",
            ]
        else:
            return []

    def get_table_columns(self, equipment_type):
        if equipment_type in ["Магнит-хит", "Оборудование сети", "Желтый акционный стеллаж"]:
            return ["Код позиции", "Наименование позиции", "Загрузка в шт", "Место выкладки товара (адрес)"]
        elif equipment_type in ["Навесное оборудование", "Оборудование КА"]:
            return ["Код позиции", "Наименование позиции", "Загрузка в шт"]
        return []

    def draw_table(self, c, table_data, table_columns, width, height, y_position, project):
        self.draw_text(c, "Товарные позиции:", 40, y_position, "DejaVuSans", 8)
        y_position -= 20

        col_widths = [self.calculate_column_width(table_data[col], col) for col in table_columns]
        max_table_width = 140 * 2.83465
        table_width = sum(col_widths)
        scaling_factor = 1.0
        if table_width > max_table_width:
            scaling_factor = max_table_width / table_width
            col_widths = [w * scaling_factor for w in col_widths]

        current_x = 40
        c.setFont("DejaVuSans-Bold", 7)
        for i, col in enumerate(table_columns):
            c.drawString(current_x + 2, y_position, col)
            c.rect(current_x, y_position - 2, col_widths[i], 12)
            current_x += col_widths[i]
        y_position -= 14

        c.setFont("DejaVuSans", 7)
        for _, row in table_data.iterrows():
            current_x = 40
            for i, col in enumerate(table_columns):
                c.drawString(current_x + 2, y_position, str(row[col]))
                c.rect(current_x, y_position - 2, col_widths[i], 12)
                current_x += col_widths[i]
            y_position -= 14
            if y_position < 100:
                c.showPage()
                y_position = height - 50

        return y_position

    def add_photos_to_pdf(self, c, photo_data, project, width, height, y_position, table_columns):
        photo_zone_x = 40 + (sum(self.calculate_column_width(photo_data[col], col) for col in table_columns)) + (10 * 2.83465)
        photo_zone_width = width - photo_zone_x - (10 * 2.83465)

        photo_rows = photo_data.loc[photo_data['Название проекта'] == project]
        if not photo_rows.empty:
            for _, photo_row in photo_rows.iterrows():
                photo_path = photo_row['Путь']
                if os.path.exists(photo_path):
                    try:
                        img = ImageReader(photo_path)
                        img_width, img_height = img.getSize()
                        scale_factor = min((photo_zone_width - 20) / img_width, (y_position - 50) / img_height, 1)
                        display_width = img_width * scale_factor
                        display_height = img_height * scale_factor

                        if y_position - display_height < 50:
                            c.showPage()
                            y_position = height - 50

                        c.drawImage(
                            img,
                            photo_zone_x,
                            y_position - display_height,
                            width=display_width,
                            height=display_height,
                            preserveAspectRatio=True,
                            mask='auto'
                        )
                        y_position -= (display_height + 10)
                    except Exception as img_e:
                        self.log_signal.emit(f"Ошибка при добавлении фотографии {photo_path}: {img_e}")
                else:
                    self.log_signal.emit(f"Фотография для проекта {project} не найдена: {photo_path}")
        else:
            self.log_signal.emit(f"Фотографии для проекта '{project}' не найдены.")
        return y_position

    def calculate_column_width(self, series, col_name, font_name='DejaVuSans', font_size=7):
        max_width_data = series.astype(str).apply(lambda x: self.string_width(x, font_name, font_size)).max()
        col_header_width = self.string_width(col_name, "DejaVuSans-Bold", font_size)
        return max(max_width_data, col_header_width) + 10

    def string_width(self, text, font_name, font_size):
        return pdfmetrics.stringWidth(text, font_name, font_size)

    def draw_text(self, canvas_obj, text, x, y, font_name, font_size):
        canvas_obj.setFont(font_name, font_size)
        canvas_obj.drawString(x, y, text)


class PDFGeneratorApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Excel to PDF Converter")
        self.folder_paths = []
        self.save_path = ""
        self.photo_files = []

        main_widget = QWidget()
        self.setCentralWidget(main_widget)

        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        self.folder_listbox = QListWidget()
        main_layout.addWidget(self.folder_listbox)

        folder_buttons_layout = QHBoxLayout()
        main_layout.addLayout(folder_buttons_layout)

        self.add_folder_button = QPushButton("Добавить папку")
        self.add_folder_button.clicked.connect(self.add_folder)
        folder_buttons_layout.addWidget(self.add_folder_button)

        self.remove_folder_button = QPushButton("Удалить папку")
        self.remove_folder_button.clicked.connect(self.remove_folder)
        folder_buttons_layout.addWidget(self.remove_folder_button)

        self.save_path_button = QPushButton("Выбрать путь для сохранения PDF")
        self.save_path_button.clicked.connect(self.choose_save_path)
        main_layout.addWidget(self.save_path_button)

        self.save_path_label = QLabel("Путь для сохранения PDF не выбран")
        main_layout.addWidget(self.save_path_label)

        self.photo_files_button = QPushButton("Выбрать файлы с фотографиями")
        self.photo_files_button.clicked.connect(self.choose_photo_files)
        main_layout.addWidget(self.photo_files_button)

        self.photo_files_label = QLabel("Файлы с фотографиями не выбраны")
        main_layout.addWidget(self.photo_files_label)

        self.start_button = QPushButton("Запуск")
        self.start_button.clicked.connect(self.start_processing)
        main_layout.addWidget(self.start_button)

        self.close_button = QPushButton("Закрыть")
        self.close_button.clicked.connect(self.close)
        main_layout.addWidget(self.close_button)

        self.log_text_edit = QTextEdit()
        self.log_text_edit.setReadOnly(True)
        main_layout.addWidget(self.log_text_edit)

        self.apply_styles()

    def apply_styles(self):
        stylesheet = """
        QMainWindow {
            background-color: #f0f0f0;
        }
        QPushButton {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            font-size: 14px;
            margin: 5px;
        }
        QPushButton:hover {
            background-color: #45a049;
        }
        QListWidget {
            background-color: white;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            font-size: 14px;
        }
        QLabel {
            font-size: 14px;
            color: #333;
        }
        QTextEdit {
            background-color: white;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            font-size: 12px;
        }
        """
        self.setStyleSheet(stylesheet)

    def add_folder(self):
        folder_path = QFileDialog.getExistingDirectory(self, "Выберите папку")
        if folder_path:
            if folder_path not in self.folder_paths:
                self.folder_paths.append(folder_path)
                self.folder_listbox.addItem(folder_path)
                self.log_info(f"Добавлена папка: {folder_path}")
            else:
                QMessageBox.information(self, "Информация", "Эта папка уже добавлена.")
                self.log_info(f"Попытка добавить уже существующую папку: {folder_path}")

    def remove_folder(self):
        selected_items = self.folder_listbox.selectedItems()
        if not selected_items:
            QMessageBox.information(self, "Информация", "Пожалуйста, выберите папку для удаления.")
            return
        for item in selected_items:
            self.folder_paths.remove(item.text())
            self.folder_listbox.takeItem(self.folder_listbox.row(item))
            self.log_info(f"Удалена папка: {item.text()}")

    def choose_save_path(self):
        save_path = QFileDialog.getExistingDirectory(self, "Выберите путь для сохранения PDF")
        if save_path:
            self.save_path = save_path
            self.save_path_label.setText(f"Сохранение в: {self.save_path}")
            self.log_info(f"Выбран путь для сохранения PDF: {self.save_path}")

    def choose_photo_files(self):
        file_paths, _ = QFileDialog.getOpenFileNames(
            self, "Выберите файлы с фотографиями", "", "Excel files (*.xlsx *.xls)"
        )
        if file_paths:
            self.photo_files = file_paths
            display_paths = ', '.join([os.path.basename(path) for path in file_paths])
            self.photo_files_label.setText(f"Выбраны файлы: {display_paths}")
            self.log_info(f"Выбраны файлы с фотографиями: {display_paths}")

    def start_processing(self):
        if not self.folder_paths:
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, выберите хотя бы одну папку с файлами Excel.")
            self.log_error("Ошибка: не выбрана ни одна папка с файлами Excel.")
            return
        if not self.save_path:
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, выберите путь для сохранения PDF файлов.")
            self.log_error("Ошибка: не выбран путь для сохранения PDF файлов.")
            return
        if not self.photo_files:
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, выберите файлы с фотографиями.")
            self.log_error("Ошибка: не выбраны файлы с фотографиями.")
            return

        self.start_button.setEnabled(False)
        self.pdf_thread = PDFGeneratorThread(self.folder_paths, self.save_path, self.photo_files)
        self.pdf_thread.log_signal.connect(self.log_info)
        self.pdf_thread.finished.connect(self.processing_finished)
        self.pdf_thread.start()
        self.log_info("Начато выполнение процесса генерации PDF.")

    def processing_finished(self):
        self.start_button.setEnabled(True)
        self.log_info("Процесс генерации PDF завершен.")

    def log_info(self, message):
        self.log_text_edit.append(f"INFO: {message}")
        logging.info(message)

    def log_error(self, message):
        self.log_text_edit.append(f"ERROR: {message}")
        logging.error(message)


if __name__ == "__main__":
    app = QApplication([])
    window = PDFGeneratorApp()
    window.show()
    app.exec() - INFO: Добавлена папка: e:/users/Dubinin_i_v/Desktop/Информирование/Своды
INFO: Выбран путь для сохранения PDF: e:/users/Dubinin_i_v/Desktop/Информирование/PDF
INFO: Выбраны файлы с фотографиями: Свод по оборудованию КА 13.11.2024-19.11.2024 ДМП ММ.xlsx
INFO: Начато выполнение процесса генерации PDF.
INFO: Прочитан файл: e:/users/Dubinin_i_v/Desktop/Информирование/Своды\Свод ДМП ММ Оборудование КА 45 нед.xlsx, лист: Стойка, столбцы: ['Код ТТ', 'Название ТТ', 'Код позиции', 'Наименование позиции', 'Наименование проекта', 'Тип акции', 'Тип оборудования', 'Наименование оборудования', 'Размер оборудования целевой', 'Номер целевого места', 'Пакет', 'Адрес пакета', 'Филиал', 'Период с', 'Период по', 'Загрузка в шт', 'Формировка допместа', 'Действия ТТ по окончанию размещения', 'Полный период размещения', 'Зона размещения', 'закрытые ТТ']
INFO: Прочитан файл: e:/users/Dubinin_i_v/Desktop/Информирование/Своды\Свод ДМП ММ Оборудование КА 45 нед.xlsx, лист: Навески 1, столбцы: ['Код ТТ', 'Название ТТ', 'Код позиции', 'Наименование позиции', 'Наименование проекта', 'Тип акции', 'Тип оборудования', 'Наименование оборудования', 'Размер оборудования целевой', 'Номер целевого места', 'Пакет', 'Адрес пакета', 'Филиал', 'Период с', 'Период по', 'Загрузка в шт', 'Формировка допместа', 'Действия ТТ по окончанию размещения', 'Полный период размещения', 'Зона размещения', 'закрытые ТТ']
INFO: Прочитан файл: e:/users/Dubinin_i_v/Desktop/Информирование/Своды\Свод ДМП ММ Оборудование КА 45 нед.xlsx, лист: Навески 2, столбцы: ['Код ТТ', 'Название ТТ', 'Код позиции', 'Наименование позиции', 'Наименование проекта', 'Тип акции', 'Тип оборудования', 'Наименование оборудования', 'Размер оборудования целевой', 'Номер целевого места', 'Пакет', 'Адрес пакета', 'Филиал', 'Период с', 'Период по', 'Загрузка в шт', 'Формировка допместа', 'Действия ТТ по окончанию размещения', 'Полный период размещения', 'Зона размещения', 'закрытые ТТ']
INFO: Прочитан файл: e:/users/Dubinin_i_v/Desktop/Информирование/Своды\Свод ДМП ММ Оборудование КА 45 нед.xlsx, лист: Навески 3, столбцы: ['Код ТТ', 'Название ТТ', 'Код позиции', 'Наименование позиции', 'Наименование проекта', 'Тип акции', 'Тип оборудования', 'Наименование оборудования', 'Размер оборудования целевой', 'Номер целевого места', 'Пакет', 'Адрес пакета', 'Филиал', 'Период с', 'Период по', 'Загрузка в шт', 'Формировка допместа', 'Действия ТТ по окончанию размещения', 'Полный период размещения', 'Зона размещения', 'закрытые ТТ']
INFO: Прочитан файл: e:/users/Dubinin_i_v/Desktop/Информирование/Своды\Свод ДМП ММ Сеть и Препак напольный 45 нед.xlsx, лист: Сеть1, столбцы: ['Код ТТ', 'Название ТТ', 'Код позиции', 'Наименование позиции', 'Наименование проекта', 'Тип акции', 'Тип оборудования', 'Наименование оборудования', 'Размер оборудования целевой', 'Номер целевого места', 'Пакет', 'Адрес пакета', 'Филиал', 'Период с', 'Период по', 'Загрузка в шт', 'Формировка допместа', 'Действия ТТ по окончанию размещения', 'Полный период размещения', 'Зона размещения', 'закрытые ТТ', 'Ссылка на ПГ']
INFO: Прочитан файл: e:/users/Dubinin_i_v/Desktop/Информирование/Своды\Свод ДМП ММ Сеть и Препак напольный 45 нед.xlsx, лист: Сеть2, столбцы: ['Код ТТ', 'Название ТТ', 'Код позиции', 'Наименование позиции', 'Наименование проекта', 'Тип акции', 'Тип оборудования', 'Наименование оборудования', 'Размер оборудования целевой', 'Номер целевого места', 'Пакет', 'Адрес пакета', 'Филиал', 'Период с', 'Период по', 'Загрузка в шт', 'Формировка допместа', 'Действия ТТ по окончанию размещения', 'Полный период размещения', 'Зона размещения', 'закрытые ТТ', 'Ссылка на ПГ']
INFO: Информация: столбец 'Комментарий' не найден и был добавлен с дефолтными значениями.
INFO: Информация: столбец 'Место выкладки товара (адрес)' не найден и был добавлен с дефолтными значениями.
INFO: Прочитан файл с фотографиями: e:/users/Dubinin_i_v/Desktop/Информирование/Свод по оборудованию КА 13.11.2024-19.11.2024 ДМП ММ.xlsx, столбцы: ['Название проекта', 'Период размещения с', 'Период размещения по', 'Действия с оборудованием после окончания акции', 'Вид оборудования', 'Группа 20', 'Зона размещения', 'Путь']
INFO: Ошибка при выполнении: 'Код позиции'
INFO: Процесс генерации PDF завершен.