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


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

import os
import sqlite3
from pathlib import Path
from typing import Optional

# Константы для имен таблиц. Это удобно для рефакторинга.
TABLE_PROJECT_DATA = "project_data"
TABLE_EVENT_LOGS = "event_logs"


class ProjectDatabase:
    """
    Класс для управления базой данных проекта.
    Создает файл БД с именем на основе кода проекта в указанной директории.
    """

    def __init__(self, project_code: str, base_dir: str = "project_dbs"):
        """
        Инициализирует объект для работы с БД проекта.

        :param project_code: Уникальный код проекта (например, 'PRJ-001').
        :param base_dir: Директория, где будут храниться файлы баз данных.
        """
        self.project_code = self._sanitize_filename(project_code)
        self.base_dir = base_dir

        # Формируем путь к файлу БД, используя pathlib для кроссплатформенности
        self.db_path = Path(base_dir) / f"{self.project_code}.db"

        # Создаем директорию, если ее нет. Блок try/except здесь не нужен,
        # так как exist_ok=True подавляет ошибки, если директория уже есть.
        self.db_path.parent.mkdir(parents=True, exist_ok=True)

        self.connection: Optional[sqlite3.Connection] = None

    @staticmethod
    def _sanitize_filename(name: str) -> str:
        """Очищает строку от символов, недопустимых в именах файлов."""
        # Оставляем только буквы, цифры, подчеркивание, дефис и точку.
        return "".join(c for c in name if c.isalnum() or c in "_-.").rstrip()

    def connect(self) -> sqlite3.Connection:
        """Устанавливает соединение с базой данных. Создает файл, если его нет."""
        if self.connection is None:
            try:
                self.connection = sqlite3.connect(self.db_path)
            # print(f"Подключено к базе данных проекта: {self.db_path}")
            except sqlite3.Error as e:
                # print(f"Ошибка при подключении к БД: {e}")
                raise
        return self.connection

    def close(self):
        """Закрывает соединение с базой данных."""
        if self.connection:
            self.connection.close()
            self.connection = None
            print(f"Соединение с {self.db_path} закрыто.")

    def create_tables(self):
        """
        Создает все необходимые таблицы.
        Гарантирует наличие базовых колонок и добавляет новые поля для текущей логики.
        """
        conn = self.connect()
        cursor = conn.cursor()

        # --- 1. СОЗДАЕМ ТАБЛИЦУ event_logs ---
        cursor.execute("""CREATE TABLE IF NOT EXISTS event_logs (
                                id INTEGER PRIMARY KEY AUTOINCREMENT,
                                project_code TEXT NOT NULL,
                                event_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                                event_text TEXT NOT NULL
                            )""")

        # --- 2. СОЗДАЕМ/ОБНОВЛЯЕМ ТАБЛИЦУ project_data ---
        cursor.execute("""CREATE TABLE IF NOT EXISTS project_data (
                                id INTEGER PRIMARY KEY AUTOINCREMENT,
                                project_code TEXT NOT NULL,
                                name TEXT,
                                created_at TEXT NOT NULL,
                                description TEXT DEFAULT '',
                                date_opened TEXT DEFAULT '',
                                responsible TEXT DEFAULT '',
                                group_name TEXT,
                                group_kov REAL,
                                indicator_name TEXT,
                                indicator_code TEXT,
                                ivent_data DATE,
                                ivent_sign INTEGER,
                                ivent_code TEXT,
                                event_data DATE,
                                event_sign INTEGER,
                                event_code TEXT,
                                event_text_content TEXT,
                                group_code TEXT
                            )""")

        # --- 3. ГАРАНТИРУЕМ НАЛИЧИЕ НОВЫХ КОЛОНОК (МИГРАЦИЯ) ---
        cursor.execute(f"PRAGMA table_info({TABLE_PROJECT_DATA})")
        existing_columns = [col[1] for col in cursor.fetchall()]
        new_columns = ["group_code"]

        for col_name in new_columns:
            if col_name not in existing_columns:
                cursor.execute(
                    f"ALTER TABLE {TABLE_PROJECT_DATA} ADD COLUMN {col_name} TEXT"
                )

        self._rename_ivent_columns_to_event(conn)
        conn.commit()

    def _rename_ivent_columns_to_event(self, conn: sqlite3.Connection):
        """
        Внутренний метод для переименования столбцов ivent_* в event_*.
        """
        cursor = conn.cursor()
        columns_to_rename = [
            ("ivent_data", "event_data"),
            ("ivent_sign", "event_sign"),
            ("ivent_code", "event_code"),
        ]

        for old_name, new_name in columns_to_rename:
            try:
                cursor.execute(
                    f"ALTER TABLE {TABLE_PROJECT_DATA} ADD COLUMN {new_name} TEXT"
                )
                cursor.execute(
                    f"UPDATE {TABLE_PROJECT_DATA} SET {new_name} = {old_name}"
                )
                cursor.execute(
                    f"ALTER TABLE {TABLE_PROJECT_DATA} DROP COLUMN {old_name}"
                )
                conn.commit()
            except sqlite3.Error:
                conn.rollback()

    def _rename_ivent_columns_to_event(self, conn: sqlite3.Connection):
        """
        Внутренний метод для переименования столбцов ivent_* в event_*.
        SQLite не поддерживает ALTER COLUMN, поэтому нужно создавать новую колонку,
        копировать данные и удалять старую.
        """
        cursor = conn.cursor()

        # Список кортежей (старая_колонка, новая_колонка)
        columns_to_rename = [
            ("ivent_data", "event_data"),
            ("ivent_sign", "event_sign"),
            ("ivent_code", "event_code"),
        ]

        for old_name, new_name in columns_to_rename:
            try:
                # 1. Добавляем новую колонку
                cursor.execute(
                    f"ALTER TABLE {TABLE_PROJECT_DATA} ADD COLUMN {new_name} {
                        self._get_column_type(
                            cursor, TABLE_PROJECT_DATA, old_name)}"
                )

                # 2. Копируем данные из старой в новую (если тип данных
                # совпадает)
                cursor.execute(
                    f"UPDATE {TABLE_PROJECT_DATA} SET {new_name} = {old_name}"
                )

                # 3. Удаляем старую колонку
                cursor.execute(
                    f"ALTER TABLE {TABLE_PROJECT_DATA} DROP COLUMN {old_name}"
                )

                conn.commit()
                print(
                    f"[РЕФАКТОРИНГ] Колонка '{old_name}' успешно переименована в '{new_name}'."
                )

            except sqlite3.Error as e:
                # Если колонки нет или другая ошибка, просто продолжаем
                print(
                    f"[ПРЕДУПРЕЖДЕНИЕ] Не удалось переименовать '{old_name}': {e}. Возможно, колонка уже была переименована или отсутствует."
                )
                conn.rollback()

    @staticmethod
    def _get_column_type(cursor, table_name, column_name):
        """Вспомогательный метод для получения типа колонки (в данном случае всегда TEXT/DATE/INTEGER)."""
        # В этом конкретном случае мы знаем типы, но метод оставлен для примера.
        # Для простоты возвращаем пустую строку, так как ALTER TABLE в SQLite
        # не требует тип при ADD COLUMN.
        return ""

    def delete_database_file(self):
        """
        Полностью удаляет файл базы данных с диска.
        Используется при завершении или архивации проекта.
        """
        self.close()  # Сначала закрываем соединение

        if self.db_path.exists():
            try:
                self.db_path.unlink()  # Используем метод Path.unlink()
                # print(f"Файл базы данных {self.db_path} удален.")
            except OSError as e:
                print(f"Ошибка при удалении файла БД: {e}")