import os
import sys
import logging
import configparser
from pathlib import Path
from typing import List, Dict, Optional
import requests
from docx import Document
import re
import sqlite3
import time

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler("automation.log", encoding='utf-8')
    ]
)
logger = logging.getLogger(__name__)

##############################
# SQLite База Данных Функции #
##############################

DB_PATH = "automation.db"

def initialize_db():
    """Инициализирует базу данных и создает необходимые таблицы."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        # Включение поддержки внешних ключей
        cursor.execute("PRAGMA foreign_keys = ON;")
        # Создание таблицы модулей
        cursor.execute("""
        CREATE TABLE IF NOT EXISTS modules (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT UNIQUE NOT NULL
        )
        """)
        # Создание таблицы лекций
        cursor.execute("""
        CREATE TABLE IF NOT EXISTS lectures (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            module_id INTEGER,
            name TEXT,
            content TEXT,
            is_complete BOOLEAN DEFAULT FALSE,
            FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE
        )
        """)
        # Создание таблицы тестов
        cursor.execute("""
        CREATE TABLE IF NOT EXISTS tests (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            module_id INTEGER,
            name TEXT,
            content TEXT,
            is_final BOOLEAN DEFAULT FALSE,
            FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE SET NULL
        )
        """)
    logger.info("База данных и таблицы инициализированы.")

def add_module(module_name: str) -> Optional[int]:
    """Добавляет модуль в базу данных или возвращает существующий ID."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        cursor.execute("INSERT OR IGNORE INTO modules (name) VALUES (?)", (module_name,))
        conn.commit()
        cursor.execute("SELECT id FROM modules WHERE name = ?", (module_name,))
        result = cursor.fetchone()
        module_id = result[0] if result else None
    if module_id:
        logger.debug(f"Модуль '{module_name}' имеет ID {module_id}.")
    return module_id

def add_lecture(module_id: int, lecture_name: str) -> Optional[int]:
    """Добавляет лекцию в базу данных и возвращает её ID."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            INSERT INTO lectures (module_id, name) VALUES (?, ?)
        """, (module_id, lecture_name))
        conn.commit()
        lecture_id = cursor.lastrowid
    logger.debug(f"Добавлена лекция '{lecture_name}' с ID {lecture_id}.")
    return lecture_id

def update_lecture_content(lecture_id: int, content: str, is_complete: bool = True):
    """Обновляет содержимое лекции и статус завершенности."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            UPDATE lectures SET content = ?, is_complete = ? WHERE id = ?
        """, (content, is_complete, lecture_id))
        conn.commit()
    logger.debug(f"Лекция ID {lecture_id} обновлена. Завершена: {is_complete}.")

def add_test(module_id: Optional[int], test_name: str, content: str, is_final: bool = False) -> Optional[int]:
    """Добавляет тест в базу данных и возвращает его ID."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        if is_final:
            module_id = None  # Использование NULL для итогового теста
        cursor.execute("""
            INSERT INTO tests (module_id, name, content, is_final) VALUES (?, ?, ?, ?)
        """, (module_id, test_name, content, is_final))
        conn.commit()
        test_id = cursor.lastrowid
    logger.debug(f"Добавлен тест '{test_name}' с ID {test_id}.")
    return test_id

def get_incomplete_lectures(module_id: int) -> List[Dict]:
    """Возвращает список незавершенных лекций для указанного модуля."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            SELECT id, name FROM lectures WHERE module_id = ? AND is_complete = FALSE
        """, (module_id,))
        lectures = cursor.fetchall()
    return [{"id": row[0], "name": row[1]} for row in lectures]

def get_all_complete_lectures_content() -> str:
    """Возвращает содержимое всех завершенных лекций."""
    with sqlite3.connect(DB_PATH) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            SELECT content FROM lectures WHERE is_complete = TRUE
        """)
        lecture_contents = [row[0] for row in cursor.fetchall()]
    return "\n\n".join(lecture_contents)

####################################
# Функции для Обработки Файлов    #
####################################

def load_config(config_path: str) -> Dict[str, str]:
    """Загружает конфигурацию из файла."""
    config = configparser.ConfigParser()
    if not os.path.exists(config_path):
        logger.critical(f"Файл конфигурации '{config_path}' не найден.")
        sys.exit(1)
    config.read(config_path, encoding='utf-8')
    try:
        openrouter_api_key = config['API']['openrouter_api_key']
        openrouter_endpoint = config['API']['openrouter_endpoint']
        cloud_model = config['API']['cloud_model']
        if not openrouter_api_key or not openrouter_endpoint or not cloud_model:
            raise ValueError
        logger.info("API-ключ, эндпоинт и модель OpenRouter успешно загружены из конфигурации.")
        return {
            "api_key": openrouter_api_key,
            "endpoint": openrouter_endpoint,
            "model": cloud_model
        }
    except (KeyError, ValueError):
        logger.critical("API-ключ, эндпоинт или модель OpenRouter не найдены в файле конфигурации.")
        sys.exit(1)

def sanitize_filename(name: str) -> str:
    """Удаляет недопустимые символы из имени файла."""
    return re.sub(r'[\\/:"*?<>|]+', '_', name)

def read_file(file_path: Path) -> str:
    """Читает содержимое файла в зависимости от его расширения."""
    try:
        if file_path.suffix.lower() == '.docx':
            document = Document(file_path)
            text = "\n".join([para.text for para in document.paragraphs])
        else:
            with open(file_path, 'r', encoding='utf-8') as file:
                text = file.read()
        logger.debug(f"Файл '{file_path}' успешно прочитан.")
        return text
    except Exception as e:
        logger.error(f"Ошибка при чтении файла '{file_path}': {e}")
        return ""

def get_file(directory: Path, keywords: List[str]) -> Optional[Path]:
    """Ищет файл в директории, имя которого содержит все ключевые слова."""
    for file in directory.iterdir():
        if file.is_file() and all(keyword.lower() in file.name.lower() for keyword in keywords):
            logger.info(f"Найден файл: {file}")
            return file
    logger.warning(f"Файл с ключевыми словами {keywords} не найден в директории '{directory}'")
    return None

def list_directory_contents(directory: Path):
    """Логирует содержимое указанной директории."""
    logger.info(f"Содержимое директории '{directory}':")
    for item in directory.iterdir():
        logger.info(f"  {item.name}")

def parse_course_plan(plan_text: str) -> Dict[str, List[str]]:
    """Парсит текст плана курса на модули и лекции."""
    modules = {}
    current_module = None
    for line in plan_text.split('\n'):
        module_match = re.match(r"Модуль\s+\d+[:\-]\s*(.+)", line.strip(), re.IGNORECASE)
        if module_match:
            module_name = module_match.group(1).strip()
            current_module = module_name
            modules[current_module] = []
            logger.debug(f"Найден модуль: {module_name}")
        elif current_module and line.strip():
            modules[current_module].append(line.strip())
            logger.debug(f"Добавлена лекция: {line.strip()} в модуль '{current_module}'")
    return modules

####################################
# Функции для Генерации Контента  #
####################################

def generate_content(api_key: str, endpoint: str, prompt: str, model: str, max_tokens: int = 1500, temperature: float = 0.7) -> Dict:
    """Отправляет запрос к OpenRouter API и получает сгенерированный контент."""
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": max_tokens,
        "temperature": temperature
    }
    try:
        response = requests.post(endpoint, headers=headers, json=payload, timeout=30)
        response.raise_for_status()
        data = response.json()
        logger.debug("Контент успешно сгенерирован.")
        return {
            "content": data.get('choices', [{}])[0].get('message', {}).get('content', '').strip(),
            "finish_reason": data.get('choices', [{}])[0].get('finish_reason', '')
        }
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 401:
            logger.error("Ошибка авторизации. Проверьте правильность API-ключа.")
        else:
            logger.error(f"HTTP ошибка при запросе к OpenRouter API: {e}")
        return {"content": "", "finish_reason": "error"}
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка при запросе к OpenRouter API: {e}")
        return {"content": "", "finish_reason": "error"}

def save_docx(content: str, file_path: Path) -> None:
    """Сохраняет текстовый контент в .docx файл."""
    try:
        document = Document()
        for line in content.split('\n'):
            document.add_paragraph(line)
        document.save(file_path)
        logger.info(f"Сохранено: '{file_path}'")
    except Exception as e:
        logger.error(f"Ошибка при сохранении файла '{file_path}': {e}")

def load_instructions(instructions_dir: Path) -> str:
    """Загружает инструкции из файлов в указанной директории."""
    instructions = ""
    instruction_files = [
        get_file(instructions_dir, ["правила", "написание", "лекции"]),
        get_file(instructions_dir, ["корректное", "отображение", "формул"])
    ]
    for file in instruction_files:
        if file:
            instructions += read_file(file) + "\n\n"
    if not instructions:
        logger.warning("Файлы инструкций не найдены. Используются дефолтные инструкции.")
        instructions = "Пожалуйста, следуйте стандартным правилам написания лекций и корректного отображения формул."
    return instructions.strip()

def generate_lecture(api_key: str, endpoint: str, lecture_request: str, course_plan: str, instructions: str, lecture_name: str, model: str, is_continuation: bool = False) -> str:
    """Генерирует контент для лекции."""
    prompt = f"""Запрос на написание лекции:
{lecture_request}

План курса:
{course_plan}

Инструкции:
{instructions}

{'Продолжите написание лекции:' if is_continuation else 'Напишите лекцию:'} {lecture_name}
"""
    response = generate_content(api_key, endpoint, prompt, model)
    return response["content"]

def generate_test(api_key: str, endpoint: str, test_request: str, module_content: str, model: str, is_final: bool = False) -> str:
    """Генерирует тест на основе содержимого модуля или всего курса."""
    prompt = f"""Запрос на написание {'итогового' if is_final else 'теста'}:
{test_request}

{'Содержание модуля:' if not is_final else 'Содержание курса:'}
{module_content}

Напишите {'итоговый' if is_final else 'тест'}.
"""
    response = generate_content(api_key, endpoint, prompt, model)
    return response["content"]

####################################
# Основной Рабочий Цикл Скрипта    #
####################################

def main():
    # Инициализация базы данных
    initialize_db()
    
    base_dir = Path(__file__).parent.resolve()
    input_dir = base_dir / "Входные данные"
    output_dir = base_dir / "Папка с готовыми лекциями"
    config_path = base_dir / "config.ini"

    # Проверка существования входной директории
    if not input_dir.exists():
        logger.critical(f"Директория '{input_dir}' не существует.")
        sys.exit(1)

    list_directory_contents(input_dir)

    # Загрузка конфигурации
    config = load_config(str(config_path))
    api_key = config["api_key"]
    endpoint = config["endpoint"]
    model = config["model"]

    # Поиск файлов запросов и плана курса
    lecture_request_file = get_file(input_dir, ["запрос", "написание", "лекции"])
    course_plan_file = get_file(input_dir, ["план", "лекций"])  # Изменено ключевые слова
    test_request_file = get_file(input_dir, ["запрос", "написание", "теста"])

    # Проверка наличия необходимых файлов
    if not course_plan_file:
        logger.critical("Файл с планом курса не найден. Завершение работы.")
        sys.exit(1)

    if not lecture_request_file:
        logger.warning("Файл с запросом на написание лекции не найден. Будет использован файл с запросом на написание теста.")
        lecture_request_file = test_request_file

    if not test_request_file:
        logger.critical("Файл с запросом на написание теста не найден. Завершение работы.")
        sys.exit(1)

    # Чтение содержимого файлов
    lecture_request = read_file(lecture_request_file) if lecture_request_file else ""
    course_plan = read_file(course_plan_file)
    instructions = load_instructions(input_dir)
    test_request = read_file(test_request_file)

    # Парсинг плана курса
    modules = parse_course_plan(course_plan)

    if not modules:
        logger.critical("Не удалось разобрать план курса. Завершение работы.")
        sys.exit(1)

    # Обработка каждого модуля
    for module, lectures in modules.items():
        module_id = add_module(module)
        if not module_id:
            logger.error(f"Не удалось добавить модуль '{module}'. Пропуск.")
            continue

        module_dir = output_dir / sanitize_filename(module)
        module_dir.mkdir(parents=True, exist_ok=True)
        module_content = ""

        for lecture in lectures:
            # Проверка, если лекция уже завершена
            with sqlite3.connect(DB_PATH) as conn:
                cursor = conn.cursor()
                cursor.execute("""
                    SELECT is_complete FROM lectures WHERE module_id = ? AND name = ?
                """, (module_id, lecture))
                existing = cursor.fetchone()
            if existing and existing[0]:
                logger.info(f"Лекция '{lecture}' уже завершена. Пропуск.")
                continue

            # Добавление лекции в базу данных
            lecture_id = add_lecture(module_id, lecture)
            lecture_content = generate_lecture(api_key, endpoint, lecture_request, course_plan, instructions, lecture, model)
            
            if not lecture_content:
                logger.error(f"Не удалось сгенерировать контент для лекции '{lecture}'. Пропуск.")
                continue

            # Сохранение лекции в файл
            sanitized_lecture = sanitize_filename(lecture)
            lecture_filename = f"{sanitized_lecture}.docx"
            save_docx(lecture_content, module_dir / lecture_filename)
            # Обновление лекции в базе данных
            update_lecture_content(lecture_id, lecture_content)
            module_content += lecture_content + "\n\n"

            # Если лекция не завершена, продолжаем написание
            while "продолжение следует" in lecture_content.lower():
                lecture_content = generate_lecture(api_key, endpoint, lecture_request, course_plan, instructions, lecture, model, True)
                if not lecture_content:
                    logger.error(f"Не удалось сгенерировать продолжение для лекции '{lecture}'. Пропуск.")
                    break
                continuation_filename = f"{sanitized_lecture} (Продолжение).docx"
                save_docx(lecture_content, module_dir / continuation_filename)
                module_content += lecture_content + "\n\n"
                # Обновление существующей лекции с добавлением продолжения
                with sqlite3.connect(DB_PATH) as conn:
                    cursor = conn.cursor()
                    cursor.execute("""
                        UPDATE lectures SET content = content || '\n\n' || ? WHERE id = ?
                    """, (lecture_content, lecture_id))
                    conn.commit()
                # Проверка на необходимость дальнейшего продолжения
                if "продолжение следует" not in lecture_content.lower():
                    break

        # Генерация теста для модуля
        module_test = generate_test(api_key, endpoint, test_request, module_content, model)
        if module_test:
            sanitized_module = sanitize_filename(module)
            test_filename = f"Тест {sanitized_module}.docx"
            save_docx(module_test, module_dir / test_filename)
            add_test(module_id, test_filename.stem, module_test)
        else:
            logger.error(f"Не удалось сгенерировать тест для модуля '{module}'.")

    # Генерация итогового теста
    final_test_dir = output_dir / "Итоговый тест"
    final_test_dir.mkdir(parents=True, exist_ok=True)
    full_course_content = get_all_complete_lectures_content()
    final_test = generate_test(api_key, endpoint, test_request, full_course_content, model, True)
    if final_test:
        final_test_path = final_test_dir / "Итоговый тест.docx"
        save_docx(final_test, final_test_path)
        add_test(None, "Итоговый тест", final_test, is_final=True)
    else:
        logger.error("Не удалось сгенерировать итоговый тест.")

    logger.info("Автоматизация завершена.")

if __name__ == "__main__":
    main() исправь ошибки