import asyncio
import httpx
import json
import logging
from typing import List, Sequence, Union


def _extract_items_templates(search_result: dict) -> list:
    """Достаёт список карточек объявлений из ответа form/extended."""
    if not isinstance(search_result, dict):
        return []
    for template in search_result.get("templates", []) or []:
        if template.get("id") == "items":
            return list(template.get("templates") or [])
    return []


def _ad_id_from_template(ad: dict):
    try:
        return (
            ad.get("component", {})
            .get("advertisementCard", {})
            .get("data", {})
            .get("id")
        )
    except Exception:
        return None


def _normalize_region_ids(region: Union[int, str, Sequence[Union[int, str]], None]) -> List[int]:
    """Приводит region к списку int. Пусто/[0]/0 → [0] (все регионы)."""
    if region is None:
        return [0]

    if isinstance(region, (list, tuple, set)):
        raw = list(region)
    else:
        raw = [region]

    ids: List[int] = []
    for item in raw:
        try:
            value = int(item)
        except (TypeError, ValueError):
            continue
        if value not in ids:
            ids.append(value)

    if not ids:
        return [0]
    # 0 среди других = «все регионы»
    if 0 in ids:
        return [0]
    return ids


async def search_extended(
    year_from: str,
    year_to: str,
    category: int,
    region: Union[int, str, Sequence[Union[int, str]], None],
    price_first: float,
    price_second: float,
    bodystyle: list = None,
    page: int = 0,
    republished_last: int = 2,
):
    """
    Функция для поиска с расширенными параметрами.

    :param year_from: Минимальный год (строка или число)
    :param year_to: Максимальный год (строка или число)
    :param category: ID типа авто (например, 1, 2, 6 и т.д.)
    :param region: ID региона (список для одного вызова не поддерживается API —
                   берётся первый/0; для нескольких регионов используйте search_extended_pages)
    :param price_first: Минимальная цена
    :param price_second: Максимальная цена
    :param bodystyle: Список ID или значений для фильтра кузова (по умолчанию пустой список)
    :param page: Номер страницы выдачи (0-based)
    :param republished_last: глубина свежести (дней); 0 — без фильтра
    :return: Результат запроса в формате JSON
    """
    if bodystyle is None:
        bodystyle = []

    page = max(0, int(page or 0))
    region_ids = _normalize_region_ids(region)
    # Auto.RIA form/extended принимает только один state за запрос
    region_param = region_ids[0] if region_ids else 0
    republished_last = max(0, int(republished_last or 0))

    payload = {
        "searchParams": {
            "search_type": 1,
            "category": category,  # Тип авто
            "bodystyle": bodystyle,  # Фильтр по кузову (если есть)
            "all": [
                {
                    "any": [
                        {"brand": 0, "year": [int(year_from), int(year_to)]},
                        {"brand": 0, "year": [0, 0]}
                    ]
                },
                {"any": [{"state": region_param}]},  # Один регион (или 0 = все)
                {
                    "any": [
                        {"fuel": [], "fuel_consumption_rate": ["city", 0, 0]},
                        {"fuel": [], "power_reserve": [0, 0], "accumulator_capacity": [0, 0]}
                    ]
                },
                {"any": [{"door": []}]},
                {"any": [{"owners_count": []}]}
            ],
            "not": {
                "any": [
                    {"any": [{"brand": 0, "year": [0, 0]}]},
                    {"brand_origin": []},
                    {"was_driven_from": []}
                ]
            },
            "price": [1, float(price_first), float(price_second)],  # Цена: от и до
            "crashed": -1,
            "technical_condition": [],
            "paint_condition": [],
            "gearbox": [],
            "drive": [],
            "eco_standart": [],
            "color": [],
            "mileage": [0],
            "engine_volume": [0, 0],
            "seat": [0, 0],
            "metallic": 0,
            "has_verified_VIN": 0,
            "has_numberplate": 0,
            "is_own_product": 0,
            "is_first_time": 0,
            "has_video_messages": 0,
            "with_vat": 0,
            "is_price_negotiable": 0,
            "with_exchange": 0,
            "is_installment_possible": 0,
            "with_autotest_vin": 0,
            "with_autotest_tech": 0,
            "with_autotest_corporate": 0,
            "is_lending_available": 0,
            "in_stock": 0,
            "exchange": 0,
            "is_address_delivery": 0,
            "with_tradein": 0,
            "smart_id": "",
            "abroad": 0,
            "customs_cleared": 1,
            "brand_origin": [],
            "is_under_credit": -1,
            "is_commercial": -1,
            "was_driven_from": [],
            "options": [],
            "headlight": [],
            "interior_material": [],
            "interior_color": [],
            "seat_adjustment": [],
            "seat_ventilation": [],
            "seat_heating": [],
            "seat_memory": [],
            "window_lifter": [],
            "conditioner": [],
            "power_steering": [],
            "steering_wheel_adjustment": [],
            "spare_wheel": [],
            "order": 0,
            "republished_last": republished_last,
            "is_sold": -1,
            "engine_power": [1, 0, 0],
            "compatible_charging_station": [],
            "owner": 0,
            "company": 0,
            "page": page,
        },
        "extendedData": {
            "params": {
                "changeBlocks": ["BBG"],
                "paramName": "year",
                "valueKey": "all[0].any[0].year"
            },
            "formId": "searchForm"
        },
        "value": [
            {"gte": int(year_from), "lte": int(year_to)}
        ],
        "page": page,
    }

    url = (
        "https://auto.ria.com/bff/search/public/form/extended"
        f"?device=desktop-web&langId=4&page={page}"
    )

    async with httpx.AsyncClient(timeout=40.0) as client:
        response = await client.post(url, json=payload)
        return response.json()


async def _search_pages_for_one_region(
    year_from: str,
    year_to: str,
    category: int,
    region_id: int,
    price_first: float,
    price_second: float,
    bodystyle: list,
    pages: int,
    page_delay_sec: float,
    republished_last: int,
    seen_ids: set,
) -> list:
    """Пагинация для одного региона; seen_ids общий для дедупликации между регионами."""
    region_ads: list = []
    for page in range(pages):
        result = await search_extended(
            year_from=year_from,
            year_to=year_to,
            category=category,
            region=region_id,
            price_first=price_first,
            price_second=price_second,
            bodystyle=bodystyle,
            page=page,
            republished_last=republished_last,
        )
        items = _extract_items_templates(result)
        if not items:
            logging.info(
                "📄 region=%s страница %s пустая — стоп по региону",
                region_id,
                page,
            )
            break

        new_on_page = 0
        for ad in items:
            ad_id = _ad_id_from_template(ad)
            if ad_id is None:
                region_ads.append(ad)
                new_on_page += 1
                continue
            if ad_id in seen_ids:
                continue
            seen_ids.add(ad_id)
            region_ads.append(ad)
            new_on_page += 1

        logging.info(
            "📄 region=%s страница %s/%s: получено %s, новых %s, по региону %s",
            region_id,
            page + 1,
            pages,
            len(items),
            new_on_page,
            len(region_ads),
        )

        if new_on_page == 0:
            logging.info(
                "📄 region=%s страница %s без новых — стоп по региону",
                region_id,
                page,
            )
            break

        if page + 1 < pages and page_delay_sec > 0:
            await asyncio.sleep(page_delay_sec)

    return region_ads


async def search_extended_pages(
    year_from: str,
    year_to: str,
    category: int,
    region: Union[int, str, Sequence[Union[int, str]], None],
    price_first: float,
    price_second: float,
    bodystyle: list = None,
    pages: int = 5,
    page_delay_sec: float = 0.4,
    republished_last: int = 2,
) -> list:
    """
    Загружает несколько страниц выдачи и возвращает список уникальных карточек.

    Если выбрано несколько регионов — для каждого региона отдельный запрос
    (API Auto.RIA не умеет OR по нескольким state в одном form/extended),
    результаты объединяются и дедуплицируются по id.
    """
    pages = max(1, int(pages or 1))
    region_ids = _normalize_region_ids(region)
    all_ads: list = []
    seen_ids = set()

    logging.info(
        "🔍 API search: regions=%s republished_last=%s pages_per_region<=%s",
        region_ids,
        republished_last,
        pages,
    )

    for idx, region_id in enumerate(region_ids):
        if idx > 0 and page_delay_sec > 0:
            await asyncio.sleep(page_delay_sec)
        region_ads = await _search_pages_for_one_region(
            year_from=year_from,
            year_to=year_to,
            category=category,
            region_id=region_id,
            price_first=price_first,
            price_second=price_second,
            bodystyle=bodystyle,
            pages=pages,
            page_delay_sec=page_delay_sec,
            republished_last=republished_last,
            seen_ids=seen_ids,
        )
        all_ads.extend(region_ads)
        logging.info(
            "✅ region=%s готово: +%s объявлений, всего уникальных %s",
            region_id,
            len(region_ads),
            len(all_ads),
        )

    return all_ads


async def main():
    data = await search_extended("2014", "2024", category=1, region=10, price_first=0, price_second=50000)
    print(json.dumps(data, ensure_ascii=False, indent=2)[:2000])


if __name__ == "__main__":
    asyncio.run(main())


# Київська обл. – ID: 10
# Житомирська обл. – ID: 2
# Сумська обл. – ID: 8
# Чернігівська обл. – ID: 6
# Вінницька обл. – ID: 1
# Кіровоградська обл. – ID: 16
# Полтавська обл. – ID: 20
# Черкаська обл. – ID: 24
# Волинська обл. – ID: 18
# Закарпатська обл. – ID: 22
# Івано-Франківська обл. – ID: 15
# Львівська обл. – ID: 5
# Рівненська обл. – ID: 9
# Тернопільська обл. – ID: 3
# Хмельницька обл. – ID: 4
# Чернівецька обл. – ID: 25
# Дніпропетровська обл. – ID: 11
# Донецька обл. – ID: 13
# Запорізька обл. – ID: 14
# Луганська обл. – ID: 17
# Харківська обл. – ID: 7
# Миколаївська обл. – ID: 19
# Одеська обл. – ID: 12
# Херсонська обл. – ID: 23


# Тип транспорта
# 0 – Всі типи транспорту
# 1 – Легкові
# 2 – Мото
# 6 – Вантажівки
# 5 – Причепи
# 4 – Спецтехніка
# 10 – Сільгосптехніка
# 7 – Автобуси
# 3 – Водний транспорт
# 9 – Повітряний транспорт
# 8 – Автобудинки
