#!/usr/bin/env python3
"""
Разовый скрипт-валидатор. Проверяет КАЖДЫЙ файл уровня на:

1. Синтаксическую корректность JSON (лишняя/недостающая запятая,
   незакрытая скобка, кривые кавычки и т.п. — то, что могло сломаться
   при ручном редактировании файла прямо на сервере).
2. Наличие всех обязательных полей и правильные типы данных.
3. Корректность каждого слова в gridWords (row/col/direction — числа
   и допустимые значения, а не что-то случайно стёртое/перепутанное).
4. Конфликты в сетке — если по ошибке у двух слов оказалась разная
   буква в одной и той же клетке.

Ничего не исправляет и не изменяет — только показывает отчёт, что
именно и где не так. Дальше правишь руками осознанно.

Запуск на сервере:
    python3 validate_levels.py
"""

import json
import os

LEVELS_DIR = "/home/WordsGame/levels"

REQUIRED_TOP_KEYS = {"id", "letters", "gridWidth", "gridHeight", "gridWords", "bonusWords"}
VALID_DIRECTIONS = {"HORIZONTAL", "VERTICAL"}


def check_file(path: str) -> list[str]:
    """Возвращает список найденных проблем (пустой список = всё ок)."""
    problems = []
    filename = os.path.basename(path)

    # --- 1. Синтаксис JSON ---
    try:
        with open(path, encoding="utf-8") as f:
            raw = f.read()
    except Exception as e:
        return [f"не удалось прочитать файл: {e}"]

    try:
        level = json.loads(raw)
    except json.JSONDecodeError as e:
        return [f"СЛОМАН JSON — {e.msg} (строка {e.lineno}, символ {e.colno})"]

    # --- 2. Обязательные поля ---
    missing = REQUIRED_TOP_KEYS - set(level.keys())
    if missing:
        problems.append(f"отсутствуют поля: {', '.join(sorted(missing))}")
        return problems  # дальше проверять бессмысленно, структуры нет

    # --- 3. Типы верхнего уровня ---
    if not isinstance(level["id"], int):
        problems.append(f"поле 'id' должно быть числом, а не {type(level['id']).__name__}")
    if not isinstance(level["letters"], list) or not all(isinstance(x, str) for x in level["letters"]):
        problems.append("поле 'letters' должно быть списком строк-букв")
    if not isinstance(level["gridWidth"], int) or not isinstance(level["gridHeight"], int):
        problems.append("поля 'gridWidth'/'gridHeight' должны быть числами")
    if not isinstance(level["gridWords"], list):
        problems.append("поле 'gridWords' должно быть списком")
        return problems
    if not isinstance(level["bonusWords"], list) or not all(isinstance(x, str) for x in level["bonusWords"]):
        problems.append("поле 'bonusWords' должно быть списком строк")

    # --- 4. Каждое слово в gridWords ---
    occupied = {}
    for i, w in enumerate(level["gridWords"]):
        prefix = f"gridWords[{i}]"
        if not isinstance(w, dict):
            problems.append(f"{prefix}: должен быть объектом, а не {type(w).__name__}")
            continue

        for field in ("word", "row", "col", "direction"):
            if field not in w:
                problems.append(f"{prefix}: отсутствует поле '{field}'")

        if "word" in w and not isinstance(w["word"], str):
            problems.append(f"{prefix}: 'word' должно быть строкой")
        if "row" in w and not isinstance(w["row"], int):
            problems.append(f"{prefix}: 'row' должно быть целым числом")
        if "col" in w and not isinstance(w["col"], int):
            problems.append(f"{prefix}: 'col' должно быть целым числом")
        if "direction" in w and w["direction"] not in VALID_DIRECTIONS:
            problems.append(
                f"{prefix}: 'direction' = {w.get('direction')!r}, "
                f"а должно быть HORIZONTAL или VERTICAL"
            )

        # проверка конфликтов клеток (только если всё нужное есть и корректно)
        if all(f in w for f in ("word", "row", "col", "direction")) and \
           isinstance(w.get("word"), str) and isinstance(w.get("row"), int) and \
           isinstance(w.get("col"), int) and w.get("direction") in VALID_DIRECTIONS:
            for idx, ch in enumerate(w["word"]):
                r, c = (w["row"], w["col"] + idx) if w["direction"] == "HORIZONTAL" else (w["row"] + idx, w["col"])
                if (r, c) in occupied and occupied[(r, c)] != ch:
                    problems.append(
                        f"конфликт клетки ({r},{c}): '{occupied[(r, c)]}' vs '{ch}' "
                        f"(слово \"{w['word']}\")"
                    )
                occupied[(r, c)] = ch

    # --- 5. Дубликат между сеткой и бонусными ---
    if isinstance(level.get("bonusWords"), list) and isinstance(level.get("gridWords"), list):
        grid_words_set = {
            w["word"] for w in level["gridWords"]
            if isinstance(w, dict) and isinstance(w.get("word"), str)
        }
        for bw in level["bonusWords"]:
            if isinstance(bw, str) and bw in grid_words_set:
                problems.append(f"слово \"{bw}\" одновременно в сетке и в бонусных")

    return problems


def main():
    files = sorted(f for f in os.listdir(LEVELS_DIR) if f.endswith(".json"))
    print(f"Проверяю {len(files)} файлов...\n")

    broken_files = 0
    for filename in files:
        path = os.path.join(LEVELS_DIR, filename)
        problems = check_file(path)
        if problems:
            broken_files += 1
            print(f"❌ {filename}:")
            for p in problems:
                print(f"     - {p}")
            print()

    print("=" * 60)
    if broken_files == 0:
        print(f"Всё чисто! Проверено {len(files)} файлов, проблем не найдено.")
    else:
        print(f"Найдено проблем в {broken_files} из {len(files)} файлов (см. выше).")
    print("=" * 60)


if __name__ == "__main__":
    main()