"""Проверка ответа GraphQL place: структура и значения полей results[ ] + Allure-отчёт."""

from __future__ import annotations

import json
from typing import Any, Final

import allure  # pyright: ignore[reportMissingImports]
import pytest
from allure_commons.types import AttachmentType  # pyright: ignore[reportMissingImports]

from worklib.findplaceinfo.find_place_data import fetch_place_members
from worklib.QueryData import query_data, query_data_place_id_variables

# Ожидаемый фрагмент ответа (ключи и значения должны совпадать хотя бы в одной записи results).
_EXPECTED_RESULT: Final[dict[str, str]] = {
    "id": "682b071a163ac2a0995355be",
    "place_type": "street",
    "name": "ул. Мебельная",
}

ALLURE_EPIC = "Place API"
ALLURE_FEATURE = "GraphQL place"
ALLURE_STORY = "Формат ответа и значения полей"


@allure.epic(ALLURE_EPIC)
@allure.feature(ALLURE_FEATURE)
@allure.story(ALLURE_STORY)
@pytest.mark.integration
class TestFindPlaceInfo:
    """Интеграционный тест: реальный запрос и сверка формата/значений."""

    @pytest.fixture(scope="class")
    def place_response(self) -> dict[str, Any]:
        with allure.step("Запрос place через GraphQL (fetch_place_members)"):
            payload = fetch_place_members(
                query=query_data()["query"],
                variables=query_data_place_id_variables()["variables"],
            )
            allure.attach(
                json.dumps(payload, ensure_ascii=False, indent=2),
                name="Ответ GraphQL (raw JSON)",
                attachment_type=AttachmentType.JSON,
            )
            return payload

    @allure.title("Структура: data → place → results[] с полями id, place_type, name")
    @allure.severity(allure.severity_level.NORMAL)
    def test_response_has_place_shape(self, place_response: dict[str, Any]) -> None:
        with allure.step("Проверка вложенности data.place.results"):
            results = _extract_results(place_response)
            allure.attach(
                json.dumps(results, ensure_ascii=False, indent=2),
                name="results (извлечено)",
                attachment_type=AttachmentType.JSON,
            )
        assert len(results) >= 1, "results должен содержать хотя бы один элемент"
        for key in ("id", "place_type", "name"):
            assert key in results[0], f'В элементе results[0] должен быть ключ "{key}"'

    @allure.title("Значения id, place_type, name совпадают с эталоном")
    @allure.severity(allure.severity_level.CRITICAL)
    def test_expected_id_place_type_name_match(self, place_response: dict[str, Any]) -> None:
        allure.attach(
            json.dumps(_EXPECTED_RESULT, ensure_ascii=False, indent=2),
            name="Ожидаемый объект в results",
            attachment_type=AttachmentType.JSON,
        )
        with allure.step("Поиск записи с полным совпадением id, place_type, name"):
            results = _extract_results(place_response)
            match = _find_matching_result(results, _EXPECTED_RESULT)
            if match is None:
                allure.attach(
                    json.dumps(results, ensure_ascii=False, indent=2),
                    name="Фактический results (для отладки)",
                    attachment_type=AttachmentType.JSON,
                )
            assert match is not None, (
                f"Ни одна запись в results не совпадает с ожидаемым набором {_EXPECTED_RESULT}. "
                f"Получено: {json.dumps(results, ensure_ascii=False, indent=2)[:2000]}"
            )
            allure.attach(
                json.dumps(match, ensure_ascii=False, indent=2),
                name="Найденная запись",
                attachment_type=AttachmentType.JSON,
            )
        assert match["id"] == _EXPECTED_RESULT["id"]
        assert match["place_type"] == _EXPECTED_RESULT["place_type"]
        assert match["name"] == _EXPECTED_RESULT["name"]


def _extract_results(payload: dict[str, Any]) -> list[dict[str, Any]]:
    assert "data" in payload, f'В ответе нет ключа "data": {json.dumps(payload, ensure_ascii=False)[:500]}'
    data = payload["data"]
    assert isinstance(data, dict), '"data" должен быть объектом'

    assert "place" in data, f'В data нет "place": keys={list(data.keys())}'
    place = data["place"]
    assert place is not None, '"place" не должен быть null'
    assert isinstance(place, dict), '"place" должен быть объектом'

    assert "results" in place, f'В place нет "results": keys={list(place.keys())}'
    results = place["results"]
    assert isinstance(results, list), '"results" должен быть массивом'

    for i, item in enumerate(results):
        assert isinstance(item, dict), f'results[{i}] должен быть объектом, получено {type(item)}'
    return results


def _find_matching_result(results: list[dict[str, Any]], expected: dict[str, str]) -> dict[str, Any] | None:
    for item in results:
        if all(item.get(k) == v for k, v in expected.items()):
            return item
    return None
