#!/usr/bin/env python3.11
# -*- coding: utf-8 -*-
"""
ocr_full_text_old_ru.py

Полное распознавание текста для PDF-файлов, определённых классификатором
языка как "русский дореформенный", с использованием дообученной модели
rus_old.

ВАЖНО (после инцидента с переполнением /tmp 20.08.2026):
  - Все временные рабочие файлы OCR (ocrmypdf) и наши логи/манифесты
    хранятся в подпапках РЯДОМ СО СКРИПТОМ, никогда в /tmp.
  - Итоговые файлы (OCR-копия PDF + постраничные .txt) сохраняются прямо
    в Хранилище библиотеки, рядом с оригиналом, в понятно названной
    подпапке "<имя файла> (распознанный текст)" - оригинал не трогаем.

Резюмируемый - при повторном запуске пропускает уже обработанные PDF.
"""

import os
import re
import csv
import time
import shutil
import subprocess
import traceback
from concurrent.futures import ProcessPoolExecutor, as_completed

import fitz  # pymupdf

os.environ["OMP_THREAD_LIMIT"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"

# ======================= НАСТРОЙКИ =======================

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

# Рабочие/временные файлы - ТОЛЬКО рядом со скриптом, никогда в /tmp
WORK_DIR = os.path.join(SCRIPT_DIR, "Рабочие_файлы")
TMP_DIR = os.path.join(WORK_DIR, "tmp")           # сюда ocrmypdf кладёт свои временные файлы
LOG_DIR = os.path.join(WORK_DIR, "Логи_и_манифест")

MANIFEST_FILE = os.path.join(LOG_DIR, "manifest_pages.csv")
LOG_FILE = os.path.join(LOG_DIR, "processed_files.csv")

# Через сколько часов считать оставшуюся временную папку "осиротевшей" и удалять при старте
STALE_TMP_HOURS = 6

LANGUAGE_CSVS = [
    "/home/dayhanbiz/public_html/3-damja.science/Обслуживание библиотеки водного, сельского и мелиоративного хозяйства и ресурсов стран бывшего Туркестана и сопредельных стран/Скрипты/Регистрация языка документов/language_detection_VAM.csv",
]

TARGET_LANGUAGE = "русский дореформенный"

# Суффикс папки с результатом - создаётся РЯДОМ С ОРИГИНАЛОМ в Хранилище
OUTPUT_SUFFIX = " (распознанный текст)"

TESSDATA_DIR = os.path.join(SCRIPT_DIR, "tessdata")

MAX_WORKERS = 5


def cleanup_stale_tmp():
    """Удаляет временные подпапки ocrmypdf старше STALE_TMP_HOURS - защита от
    повторения инцидента с переполнением диска из-за 'осиротевших' процессов."""
    if not os.path.isdir(TMP_DIR):
        return
    cutoff = time.time() - STALE_TMP_HOURS * 3600
    removed = 0
    for name in os.listdir(TMP_DIR):
        path = os.path.join(TMP_DIR, name)
        try:
            if os.path.isdir(path) and os.path.getmtime(path) < cutoff:
                shutil.rmtree(path, ignore_errors=True)
                removed += 1
        except Exception:
            continue
    if removed:
        print(f"Очистка: удалено устаревших временных папок: {removed}", flush=True)


def load_target_files():
    files = []
    for csv_path in LANGUAGE_CSVS:
        if not os.path.exists(csv_path):
            print(f"Внимание: не найден {csv_path}, пропускаю")
            continue
        with open(csv_path, encoding="utf-8", newline="") as f:
            reader = csv.DictReader(f, delimiter=";")
            for row in reader:
                if row.get("detected_language") == TARGET_LANGUAGE and row.get("status") == "ok":
                    files.append(row["absolute_path"])
    return files


def load_processed():
    processed = set()
    if os.path.exists(LOG_FILE):
        with open(LOG_FILE, encoding="utf-8", newline="") as f:
            reader = csv.DictReader(f, delimiter=";")
            for row in reader:
                processed.add(row["pdf_path"])
    return processed


def process_one(pdf_path):
    result = {
        "pdf_path": pdf_path,
        "ocr_pdf_path": "",
        "pages": 0,
        "status": "ok",
        "error_message": "",
        "processed_at": time.strftime("%Y-%m-%d %H:%M:%S"),
    }
    page_rows = []

    try:
        original_dir = os.path.dirname(pdf_path)
        base_name = os.path.splitext(os.path.basename(pdf_path))[0]
        original_filename = os.path.basename(pdf_path)

        # итоговая папка - РЯДОМ С ОРИГИНАЛОМ, прямо в Хранилище
        output_dir = os.path.join(original_dir, base_name + OUTPUT_SUFFIX)
        os.makedirs(output_dir, exist_ok=True)

        ocr_pdf_path = os.path.join(output_dir, original_filename)
        tmp_output = ocr_pdf_path + ".tmp"

        os.makedirs(TMP_DIR, exist_ok=True)

        env = os.environ.copy()
        env["TESSDATA_PREFIX"] = TESSDATA_DIR
        # ocrmypdf/tempfile используют TMPDIR - направляем все рабочие файлы
        # OCR сюда, никогда в системный /tmp
        env["TMPDIR"] = TMP_DIR
        env["TMP"] = TMP_DIR
        env["TEMP"] = TMP_DIR

        cmd = [
            "ocrmypdf",
            "--force-ocr",
            "--language", "rus_old",
            "--jobs", "1",
            "--output-type", "pdf",
            pdf_path,
            tmp_output,
        ]
        proc = subprocess.run(cmd, capture_output=True, text=True, timeout=None, env=env)
        if proc.returncode != 0:
            result["status"] = "ошибка ocrmypdf"
            result["error_message"] = proc.stderr.strip()[:500]
            if os.path.exists(tmp_output):
                os.remove(tmp_output)
            return result, page_rows

        shutil.move(tmp_output, ocr_pdf_path)
        result["ocr_pdf_path"] = ocr_pdf_path

        doc = fitz.open(ocr_pdf_path)
        result["pages"] = doc.page_count
        for i in range(doc.page_count):
            page_text = doc.load_page(i).get_text()
            txt_filename = f"page_{i+1:04d}.txt"
            txt_path = os.path.join(output_dir, txt_filename)
            with open(txt_path, "w", encoding="utf-8") as f:
                f.write(page_text)
            page_rows.append({
                "pdf_path": pdf_path,
                "ocr_pdf_path": ocr_pdf_path,
                "page_number": i + 1,
                "txt_path": txt_path,
                "char_count": len(page_text),
            })
        doc.close()

    except Exception as e:
        result["status"] = "ошибка"
        result["error_message"] = f"{e}\n{traceback.format_exc(limit=1)}"

    return result, page_rows


def main():
    os.makedirs(WORK_DIR, exist_ok=True)
    os.makedirs(TMP_DIR, exist_ok=True)
    os.makedirs(LOG_DIR, exist_ok=True)

    cleanup_stale_tmp()

    all_files = load_target_files()
    processed = load_processed()
    to_process = [p for p in all_files if p not in processed]

    print(f"Всего файлов с языком '{TARGET_LANGUAGE}': {len(all_files)}")
    print(f"Уже обработано ранее: {len(processed)}")
    print(f"К обработке: {len(to_process)}")

    log_exists = os.path.exists(LOG_FILE)
    log_f = open(LOG_FILE, mode="a", encoding="utf-8", newline="")
    log_writer = csv.DictWriter(log_f, fieldnames=["pdf_path", "ocr_pdf_path", "pages", "status", "error_message", "processed_at"], delimiter=";")
    if not log_exists:
        log_writer.writeheader()
        log_f.flush()

    manifest_exists = os.path.exists(MANIFEST_FILE)
    manifest_f = open(MANIFEST_FILE, mode="a", encoding="utf-8", newline="")
    manifest_writer = csv.DictWriter(manifest_f, fieldnames=["pdf_path", "ocr_pdf_path", "page_number", "txt_path", "char_count"], delimiter=";")
    if not manifest_exists:
        manifest_writer.writeheader()
        manifest_f.flush()

    done = 0
    total = len(to_process)
    start_time = time.time()

    with ProcessPoolExecutor(max_workers=MAX_WORKERS) as executor:
        futures = {executor.submit(process_one, p): p for p in to_process}
        for fut in as_completed(futures):
            result, page_rows = fut.result()
            log_writer.writerow(result)
            log_f.flush()
            for row in page_rows:
                manifest_writer.writerow(row)
            manifest_f.flush()

            done += 1
            elapsed = time.time() - start_time
            print(f"[{done}/{total}] {os.path.basename(result['pdf_path'])} -> "
                  f"страниц={result['pages']}, статус={result['status']} [{elapsed:.0f}s]", flush=True)
            if result["status"] != "ok":
                print(f"    {result['error_message']}", flush=True)

    log_f.close()
    manifest_f.close()
    print(f"\n🎉 Готово! Обработано в этом запуске: {done}")
    print(f"📁 Результаты сохранены рядом с оригиналами в Хранилище (папки '<имя книги>{OUTPUT_SUFFIX}')")
    print(f"📋 Манифест страниц: {MANIFEST_FILE}")


if __name__ == "__main__":
    main()
