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.units import mm
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()

# Задаем размер страницы 297 мм x 420 мм (формат A3)
custom_page_size = (297 * mm, 420 * mm)

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)
                            # Нормализация названий столбцов
                            file_data.columns = file_data.columns.str.strip()
                            if not file_data.empty:
                                self.log_signal.emit(
                                    f"Прочитан файл: {file_path}, лист: {sheet_name}, столбцы: {file_data.columns.tolist()}"
                                )
                                data = pd.concat([data, file_data], ignore_index=True)
                            else:
                                self.log_signal.emit(f"Предупреждение: Лист {sheet_name} в файле {file_path} пустой.")
                    except Exception as e:
                        self.log_signal.emit(f"Ошибка при чтении файла {file_path}: {e}")
        # Нормализация названий столбцов в основном DataFrame
        if not data.empty:
            data.columns = data.columns.str.strip()
        return self.check_columns(data)

    def check_columns(self, data):
        # Преобразование дат в формат datetime, если есть соответствующие столбцы
        if 'Период с' in data.columns:
            data["Период с"] = pd.to_datetime(data["Период с"], dayfirst=True, errors='coerce')
        if 'Период по' in data.columns:
            data["Период по"] = pd.to_datetime(data["Период по"], dayfirst=True, 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)
                # Нормализация названий столбцов
                file_data.columns = file_data.columns.str.strip()
                if not file_data.empty:
                    self.log_signal.emit(
                        f"Прочитан файл с фотографиями: {file_path}, столбцы: {file_data.columns.tolist()}"
                    )
                    photo_data = pd.concat([photo_data, file_data], ignore_index=True)
                else:
                    self.log_signal.emit(f"Предупреждение: Файл с фотографиями {file_path} пустой.")
            except Exception as e:
                self.log_signal.emit(f"Ошибка при чтении файла {file_path}: {e}")
        return photo_data

    def generate_pdfs(self, data, photo_data):
        if "Код ТТ" in data.columns:
            data_by_tt = data.groupby("Код ТТ")
        else:
            self.log_signal.emit("Отсутствует столбец 'Код ТТ' в данных. Невозможно сгенерировать PDF.")
            return
        for tt_code, tt_data in data_by_tt:
            if tt_data.empty:
                continue
            if "Название ТТ" in tt_data.columns:
                tt_name_series = tt_data["Название ТТ"].dropna()
                if not tt_name_series.empty:
                    tt_name = tt_name_series.iloc[0]
                else:
                    tt_name = "Unknown TT Name"
            else:
                tt_name = "Unknown TT Name"

            period_start = tt_data["Период с"].min() if "Период с" in tt_data.columns else None
            period_end = tt_data["Период по"].max() if "Период по" in tt_data.columns else None

            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:
                if "Тип оборудования" in tt_data.columns:
                    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):
        left_margin = 30
        right_margin = 30
        column_gap = 10
        max_page_width = width - left_margin - right_margin
        left_column_width = max_page_width * 0.6
        right_column_width = max_page_width * 0.4

        for project, project_data in eq_data.groupby("Наименование проекта"):
            if project_data.empty:
                continue
            # Определяем высоту, необходимую для вывода деталей проекта
            font_size = 8
            detail_lines = self.get_details(equipment_type, project_data)
            detail_height = len(detail_lines) * (font_size + 10)  # приблизительная высота деталей
            project_title_height = font_size + 10  # высота строки "Проект:..."

            # Проверяем, хватит ли места на текущей странице для деталей проекта
            required_height = project_title_height + detail_height + 50  # +50 для запаса
            if y_position - required_height < 100:
                # Места не хватает, переносим весь проект на новую страницу
                c.showPage()
                y_position = height - 50

            # Начинаем с левого столбца
            current_x = left_margin

            project_title_y = y_position  # Запоминаем y_position для верхней части фото

            self.draw_text(c, f"Проект: {project}", current_x, y_position, "DejaVuSans", font_size)
            y_position -= 20

            details = detail_lines
            for detail in details:
                y_position -= 18
                self.draw_text(c, detail, current_x + 10, y_position, "DejaVuSans", font_size)

            y_position -= 10  # Отступ после деталей

            # Добавляем изображения в правый столбец, выравнивая по проекту и ограничивая высоту
            # Определяем доступное пространство для изображения
            y_bottom = y_position  # Нижняя граница (после деталей)
            y_top = project_title_y  # Верхняя граница (где начинается проект)
            self.add_photos_to_pdf(
                c, photo_data, project, right_column_width, y_top, y_bottom,
                left_margin + left_column_width + column_gap
            )

            table_columns = self.get_table_columns(equipment_type, project_data)
            if table_columns:
                table_data = project_data[table_columns].drop_duplicates().fillna("-")
                y_position_after_table = self.draw_table(c, table_data, table_columns, left_column_width, y_position, current_x)
            else:
                y_position_after_table = y_position
                self.log_signal.emit(f"Табличные данные отсутствуют или необходимые столбцы отсутствуют для проекта '{project}'.")

            y_position = y_position_after_table - 20  # Отступ перед следующим проектом

        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):
        details = []

        def add_detail(label, column_name):
            if column_name in project_data.columns:
                value_series = project_data[column_name].dropna()
                if not value_series.empty:
                    value = value_series.iloc[0]
                else:
                    value = '-'
                details.append(f"{label}: {value}")
            else:
                details.append(f"{label}: -")

        if equipment_type in ["Магнит-хит", "Оборудование сети"]:
            add_detail("Название оборудования", "Наименование оборудования")
            add_detail("Полный период размещения", "Полный период размещения")
            add_detail("Ссылка на планограмму", "Ссылка на ПГ")
            add_detail("Комментарий", "Комментарий")
        elif equipment_type in ["Навесное оборудование", "Оборудование КА"]:
            add_detail("Зона размещения", "Зона размещения")
            add_detail("Название оборудования", "Наименование оборудования")
            add_detail("Полный период размещения", "Полный период размещения")
            add_detail("Действия ТТ по окончанию размещения", "Действия ТТ по окончанию размещения")
            add_detail("Комментарий", "Комментарий")
        elif equipment_type == "Желтый акционный стеллаж":
            add_detail("Зона размещения", "Зона размещения")
            add_detail("Ссылка на планограмму", "Ссылка на ПГ")
            add_detail("Комментарий", "Комментарий")
        return details

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

        # Проверяем наличие столбцов в данных проекта
        table_columns = [col for col in base_columns if col in project_data.columns]
        missing_columns = [col for col in base_columns if col not in project_data.columns]
        if missing_columns:
            self.log_signal.emit(f"Предупреждение: В проекте отсутствуют столбцы {missing_columns}.")
        return table_columns

    def draw_table(self, c, table_data, table_columns, max_table_width, y_position, x_start):
        if table_data.empty:
            self.log_signal.emit(f"Табличные данные отсутствуют.")
            return y_position

        self.draw_text(c, "Товарные позиции:", x_start + 10, y_position, "DejaVuSans", 8)
        y_position -= 20

        font_size = 8  # Размер шрифта 8
        col_widths = [self.calculate_column_width(table_data[col], col, font_size=font_size) for col in table_columns]
        total_width = sum(col_widths)
        scaling_factor = 1.0
        if total_width > max_table_width - 20:
            scaling_factor = (max_table_width - 20) / total_width
            col_widths = [w * scaling_factor for w in col_widths]

        current_x = x_start + 10
        c.setFont("DejaVuSans-Bold", font_size)
        row_height = font_size + 6  # Высота строки таблицы
        c.setLineWidth(0.5)
        height = c._pagesize[1]

        # Заголовок таблицы
        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], row_height)
            current_x += col_widths[i]
        y_position -= row_height

        c.setFont("DejaVuSans", font_size)
        for index, row in table_data.iterrows():
            if y_position < 100:
                c.showPage()
                y_position = height - 50
                # Рисуем заголовок таблицы на новой странице
                current_x = x_start + 10
                c.setFont("DejaVuSans-Bold", font_size)
                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], row_height)
                    current_x += col_widths[i]
                y_position -= row_height
                c.setFont("DejaVuSans", font_size)

            current_x = x_start + 10
            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], row_height)
                current_x += col_widths[i]
            y_position -= row_height

        return y_position

    def add_photos_to_pdf(self, c, photo_data, project, photo_zone_width, y_top, y_bottom, x_start):
        if 'Название проекта' in photo_data.columns:
            photo_rows = photo_data.loc[photo_data['Название проекта'] == project]
            if not photo_rows.empty:
                for _, photo_row in photo_rows.iterrows():
                    if 'Путь' in photo_row:
                        photo_path = photo_row['Путь']
                        if os.path.exists(photo_path):
                            try:
                                img = ImageReader(photo_path)
                                img_width, img_height = img.getSize()

                                # Доступная высота для изображения
                                available_height = y_top - y_bottom - 10  # 10 - небольшой отступ
                                # Доступная ширина
                                available_width = photo_zone_width - 20

                                # Проверяем, что доступная высота положительная
                                if available_height <= 0 or available_width <= 0:
                                    self.log_signal.emit(
                                        f"Предупреждение: недостаточно места для размещения изображения для проекта '{project}'."
                                    )
                                    continue

                                # Проверяем, помещается ли изображение в оригинальном размере
                                if img_width <= available_width and img_height <= available_height:
                                    display_width = img_width
                                    display_height = img_height
                                else:
                                    # Масштабируем изображение пропорционально, чтобы оно поместилось в доступное пространство
                                    scale_factor = min(available_width / img_width, available_height / img_height)
                                    display_width = img_width * scale_factor
                                    display_height = img_height * scale_factor

                                # Позиция изображения по вертикали
                                y_position = y_top - display_height

                                c.drawImage(
                                    img,
                                    x_start + 10,
                                    y_position,
                                    width=display_width,
                                    height=display_height,
                                    preserveAspectRatio=True,
                                    mask='auto'
                                )
                                # Изображение не должно перекрывать таблицу и текст
                                break
                            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"Предупреждение: В данных фотографий отсутствует столбец 'Путь'.")
            else:
                self.log_signal.emit(f"Фотографии для проекта '{project}' не найдены.")
        else:
            self.log_signal.emit("Предупреждение: В данных фотографий отсутствует столбец 'Название проекта'.")

    def calculate_column_width(self, series, col_name, font_name='DejaVuSans', font_size=8):
        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 в PDF")
        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() - Фото должно занимать пространство справа от информации о проекте и таблицы относящейся к таблице, а не только справа информации о проекте. Только если фото не помещается, оно должно уменьшаться