#!/usr/bin/env python
"""
Скрипт для визуального тестирования процесса чанкинга и сборки документа.

Этот скрипт:
1. Считывает test_input/test.docx с помощью UniversalParser
2. Чанкит документ через Destructurer с fixed_size-стратегией
3. Сохраняет результат чанкинга в test_output/test.csv
4. Выбирает 20-30 случайных чанков из CSV
5. Создает InjectionBuilder с InMemoryEntityRepository
6. Собирает текст из выбранных чанков
7. Сохраняет результат в test_output/test_builded.txt
"""

import logging
import os
import random
from pathlib import Path
from typing import List

import pandas as pd
from ntr_fileparser import UniversalParser

from ntr_text_fragmentation.chunking.specific_strategies.fixed_size_chunking import \
    FixedSizeChunkingStrategy
from ntr_text_fragmentation.core.destructurer import Destructurer
from ntr_text_fragmentation.core.entity_repository import \
    InMemoryEntityRepository
from ntr_text_fragmentation.core.injection_builder import InjectionBuilder
from ntr_text_fragmentation.models.linker_entity import LinkerEntity


def setup_logging() -> None:
    """Настройка логгирования."""
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    )


def ensure_directories() -> None:
    """Проверка наличия необходимых директорий."""
    for directory in ["test_input", "test_output"]:
        Path(directory).mkdir(parents=True, exist_ok=True)


def save_entities_to_csv(entities: List[LinkerEntity], csv_path: str) -> None:
    """
    Сохраняет сущности в CSV файл.

    Args:
        entities: Список сущностей
        csv_path: Путь для сохранения CSV файла
    """
    data = []
    for entity in entities:
        # Базовые поля для всех типов сущностей
        entity_dict = {
            "id": str(entity.id),
            "type": entity.type,
            "name": entity.name,
            "text": entity.text,
            "metadata": str(entity.metadata),
            "in_search_text": entity.in_search_text,
            "source_id": entity.source_id,
            "target_id": entity.target_id,
            "number_in_relation": entity.number_in_relation,
        }

        data.append(entity_dict)

    df = pd.DataFrame(data)
    df.to_csv(csv_path, index=False)
    logging.info(f"Сохранено {len(entities)} сущностей в {csv_path}")


def load_entities_from_csv(csv_path: str) -> List[LinkerEntity]:
    """
    Загружает сущности из CSV файла.

    Args:
        csv_path: Путь к CSV файлу

    Returns:
        Список сущностей
    """
    df = pd.read_csv(csv_path)
    entities = []

    for _, row in df.iterrows():
        # Обработка метаданных
        metadata_str = row.get("metadata", "{}")
        try:
            metadata = (
                eval(metadata_str) if metadata_str and not pd.isna(metadata_str) else {}
            )
        except:
            metadata = {}

        # Общие поля для всех типов сущностей
        common_args = {
            "id": row["id"],
            "name": row["name"] if not pd.isna(row.get("name", "")) else "",
            "text": row["text"] if not pd.isna(row.get("text", "")) else "",
            "metadata": metadata,
            "in_search_text": row["in_search_text"],
            "type": row["type"],
        }

        # Добавляем поля связи, если они есть
        if not pd.isna(row.get("source_id", "")):
            common_args["source_id"] = row["source_id"]
            common_args["target_id"] = row["target_id"]
            if not pd.isna(row.get("number_in_relation", "")):
                common_args["number_in_relation"] = int(row["number_in_relation"])

        entity = LinkerEntity(**common_args)
        entities.append(entity)

    logging.info(f"Загружено {len(entities)} сущностей из {csv_path}")
    return entities


def main() -> None:
    """Основная функция скрипта."""
    setup_logging()
    ensure_directories()

    # Пути к файлам
    input_doc_path = "test_input/test.docx"
    output_csv_path = "test_output/test.csv"
    output_text_path = "test_output/test_builded.txt"

    # Проверка наличия входного файла
    if not os.path.exists(input_doc_path):
        logging.error(f"Файл {input_doc_path} не найден!")
        return

    logging.info(f"Парсинг документа {input_doc_path}")

    try:
        # Шаг 1: Парсинг документа дважды, как если бы это были два разных документа
        parser = UniversalParser()
        document1 = parser.parse_by_path(input_doc_path)
        document2 = parser.parse_by_path(input_doc_path)
        
        # Меняем название второго документа, чтобы отличить его
        document2.name = document2.name + "_copy" if document2.name else "copy_doc"

        # Шаг 2: Чанкинг обоих документов с использованием fixed_size-стратегии
        all_entities = []
        
        # Обработка первого документа
        destructurer1 = Destructurer(
            document1, strategy_name="fixed_size", words_per_chunk=50, overlap_words=25
        )
        logging.info("Начало процесса чанкинга первого документа")
        entities1 = destructurer1.destructure()
        
        # Добавляем метаданные о документе к каждой сущности
        for entity in entities1:
            if not hasattr(entity, 'metadata') or entity.metadata is None:
                entity.metadata = {}
            entity.metadata['doc_name'] = "document1"
        
        logging.info(f"Получено {len(entities1)} сущностей из первого документа")
        all_entities.extend(entities1)
        
        # Обработка второго документа
        destructurer2 = Destructurer(
            document2, strategy_name="fixed_size", words_per_chunk=50, overlap_words=25
        )
        logging.info("Начало процесса чанкинга второго документа")
        entities2 = destructurer2.destructure()
        
        # Добавляем метаданные о документе к каждой сущности
        for entity in entities2:
            if not hasattr(entity, 'metadata') or entity.metadata is None:
                entity.metadata = {}
            entity.metadata['doc_name'] = "document2"
        
        logging.info(f"Получено {len(entities2)} сущностей из второго документа")
        all_entities.extend(entities2)
        
        logging.info(f"Всего получено {len(all_entities)} сущностей из обоих документов")

        # Шаг 3: Сохранение результатов чанкинга в CSV
        save_entities_to_csv(all_entities, output_csv_path)

        # Шаг 4: Загрузка сущностей из CSV и выбор случайных чанков
        loaded_entities = load_entities_from_csv(output_csv_path)

        # Фильтрация только чанков
        chunks = [e for e in loaded_entities if e.in_search_text is not None]

        # Выбор случайных чанков (от 20 до 30)
        num_chunks_to_select = min(random.randint(20, 30), len(chunks))
        selected_chunks = random.sample(chunks, num_chunks_to_select)

        logging.info(f"Выбрано {len(selected_chunks)} случайных чанков для сборки")
        
        # Дополнительная статистика по документам
        doc1_chunks = [c for c in selected_chunks if hasattr(c, 'metadata') and c.metadata.get('doc_name') == "document1"]
        doc2_chunks = [c for c in selected_chunks if hasattr(c, 'metadata') and c.metadata.get('doc_name') == "document2"]
        logging.info(f"Из них {len(doc1_chunks)} чанков из первого документа и {len(doc2_chunks)} из второго")

        # Шаг 5: Создание InjectionBuilder с InMemoryEntityRepository
        repository = InMemoryEntityRepository(loaded_entities)
        builder = InjectionBuilder(repository=repository)

        # Регистрация стратегии
        builder.register_strategy("fixed_size", FixedSizeChunkingStrategy)

        # Шаг 6: Сборка текста из выбранных чанков
        logging.info("Начало сборки текста из выбранных чанков")
        assembled_text = builder.build(selected_chunks)

        # Шаг 7: Сохранение результата в файл
        with open(output_text_path, "w", encoding="utf-8") as f:
            f.write(assembled_text)

        logging.info(f"Результат сборки сохранен в {output_text_path}")

        # Вывод статистики
        logging.info(f"Общее количество сущностей: {len(loaded_entities)}")
        logging.info(f"Количество чанков: {len(chunks)}")
        logging.info(f"Выбрано для сборки: {len(selected_chunks)}")
        logging.info(f"Длина собранного текста: {len(assembled_text)} символов")

    except Exception as e:
        logging.error(f"Произошла ошибка: {e}", exc_info=True)


if __name__ == "__main__":
    main()