# -*- coding: utf-8 -*-
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count
from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse
from django.utils import timezone
from oknardia.models import (
    Win_MountDim,
    PriceOffer,
    Apartment_Type,
    Seria_Info,
    Building_Info,
    LogVisitPriceReport,
    MountDim2Apartment,
)
from oknardia.settings import *
from web.report1 import get_last_all_user_visit_list, get_last_user_visit_cookies, get_last_user_visit_list
from web.add_func import normalize, get_rating_set_for_stars, get_flaps_for_big_pictures, get_flaps_for_mini_pictures, \
    get_geo_distance
import django.utils.dateformat
import time
import os
import re
import json
from types import SimpleNamespace
import pytils


def _slugify_lower(value: str | None) -> str:
    """Транслитерирует строку в slug и всегда приводит к нижнему регистру."""
    return pytils.translit.slugify((value or "").strip()).lower()


def _one_win_price_canonical_path(win_width_mm: int | str, win_height_mm: int | str, win_id: int | str) -> str:
    """Возвращает канонический путь страницы цен для одного типового окна."""
    return f"/catalog/standard_opening/price-{int(win_width_mm)}x{int(win_height_mm)}mm-tip{int(win_id)}/"


def redirect_one_win_price_legacy(request: HttpRequest,
                                  win_width_mm: str | int = DEFAULT_WIN_WIDTH_MM,
                                  win_height_mm: str | int = DEFAULT_WIN_HEIGHT_MM,
                                  win_id: str | int = DEFAULT_WIN_ID) -> HttpResponse:
    """301-редирект со старого URL /tsena-odnogo-okna/... на канонический URL."""
    return redirect(
        _one_win_price_canonical_path(win_width_mm=win_width_mm, win_height_mm=win_height_mm, win_id=win_id),
        permanent=True,
    )


def _append_visit_context(
    to_template: dict,
    request: HttpRequest,
    time_start: float,
    log_visit: list | None = None,
    last_visit_cookie: list | None = None,
) -> None:
    """Дописывает в контекст стандартный хвост: визиты и время выполнения."""
    if log_visit is None:
        log_visit = get_last_all_user_visit_list()
    if last_visit_cookie is None:
        last_visit_cookie = get_last_user_visit_cookies(request)

    to_template.update({
        'LAST_VISIT': get_last_user_visit_list(last_visit_cookie[:3]),
        'LOG_VISIT': log_visit,
        'ticks': float(time.perf_counter() - time_start),
    })


def report_price_frame(apartment_id: int, mount_dim_per_offer: int, address_longitude: float, address_latitude: float,
                       frame_begin_n: int = 0, brand_id: int = 0, win_id: int = 0) -> dict:
    """ Формируем выдачу цен для фрейма

    :param apartment_id: int        -- ID типа квартиры, для которой получаем ценовые предложения
    :param mount_dim_per_offer: int -- число различных оконых проемов в этой квартире (чтобы отсеять предложения,
                                       в которых не представлены все проемы)
    :param address_longitude: float -- долгота адреса (геокоордината), чтобы рассчитать удаленность компании
                                       предоставившей коммерческие предложения
    :param address_latitude: float  -- широта адреса (геокоордината), чтобы рассчитать удаленность компании
                                       предоставившей коммерческие предложения
    :param frame_begin_n: int       -- Номер записи с которой начинается фрейм с ценами (с какого предложения начинать)
    :param brand_id: int            -- ID бренда, если выбран бренд, то отображаем только предложения этого бренда
                                       (нужно для виджета, где отображаются предложения только от одной компании)
                                       если 0, то отображаем все предложения
    :param win_id: int              -- ID окна, если выбрано окно, то отображаем только предложения этого окна
    :return: dict                   -- словарь данных для отображения в фрейме (цены предложений и их характеристики)
    """
    # ценовая выдача
    time_for_meta = 0  # время для мета-данных в HTML-кода
    apartment_id = int(apartment_id)
    mount_dim_per_offer = int(mount_dim_per_offer)
    address_longitude = float(address_longitude)
    address_latitude = float(address_latitude)
    frame_begin_n = int(frame_begin_n)
    brand_id = int(brand_id)
    win_id = int(win_id)
    add_to_sql_for_widget = ""
    offer_per_frame = OFFER_PER_FRAME
    if brand_id != 0:
        # Это вывод для выджета. Нужны цены только по определенному поставщику
        add_to_sql_for_widget = f"  AND oknardia_merchantbrand.id = {brand_id} "
        offer_per_frame = 1000  # Фреймовый вывод не нужен... фигачим сразу целую 1000 предложений.
    if int(apartment_id) == 0 and int(win_id) != 0:
        # ORM-ветка для одиночного типового окна.
        # Здесь можно полностью уйти от raw SQL, потому что все связи линейные и хорошо покрываются select_related.
        # Контракт ответа сохраняем прежним: META_DATA_PUBLISH, PRICE_FRAME, N и все вложенные ключи оферов.
        offer_per_frame = OFFER_PER_FRAME_FOR_ONE_FLAP
        if brand_id != 0:
            offer_per_frame = 1000

        q_price_offer = (
            PriceOffer.objects.filter(
                sOfferActive=True,
                kOffer2MountDim_id=win_id,
                kOffer2SetKit__sSetActive=True,
                kOffer2SetKit__kSet2User__kMerchantOffice__isnull=False,
                kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__isnull=False,
                kOffer2SetKit__kSet2Glazing__isnull=False,
                kOffer2SetKit__kSet2PVCprofiles__isnull=False,
            )
            .values(
                'id',
                'fOfferPrice',
                'dOfferModify',
                'sOfferFlapConfig',
                'kOffer2MountDim__sDescripion',
                'kOffer2MountDim__iWinWidth',
                'kOffer2MountDim__iWinHight',
                'kOffer2SetKit__id',
                'kOffer2SetKit__sSetName',
                'kOffer2SetKit__dSetModify',
                'kOffer2SetKit__dSetCommercialUntil',
                'kOffer2SetKit__sSetClimateControl',
                'kOffer2SetKit__sSetSill',
                'kOffer2SetKit__sSetImplementAll',
                'kOffer2SetKit__sSetImplementHandles',
                'kOffer2SetKit__sSetImplementHinges',
                'kOffer2SetKit__sSetImplementLatch',
                'kOffer2SetKit__sSetImplementLimiter',
                'kOffer2SetKit__sSetImplementCatch',
                'kOffer2SetKit__sSetPanes',
                'kOffer2SetKit__sSetSlope',
                'kOffer2SetKit__sSetOtherConditions',
                'kOffer2SetKit__sSetDelivery',
                'kOffer2SetKit__bSetDelivery',
                'kOffer2SetKit__sSetUninstallInstall',
                'kOffer2SetKit__bSetUninstallInstall',
                'kOffer2SetKit__fSetRating',
                'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficePhones',
                'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeDiscountMetaFormula',
                'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeName',
                'kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeAddress',
                'kOffer2SetKit__kSet2Glazing__sGlazingBriefDescription',
                'kOffer2SetKit__kSet2Glazing__sGlazingMark',
                'kOffer2SetKit__kSet2Glazing__sGlazingToning',
                'kOffer2SetKit__kSet2PVCprofiles__id',
                'kOffer2SetKit__kSet2PVCprofiles__sProfileName',
                'kOffer2SetKit__kSet2PVCprofiles__sProfileManufacturer',
                'kOffer2SetKit__kSet2PVCprofiles__sProfileSealDescription',
                'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName',
                'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo',
                'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantMainURL',
            )
            .order_by("-dOfferModify")
        )
        if brand_id != 0:
            q_price_offer = q_price_offer.filter(
                kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName_id=brand_id,
            )
        q_price_offer = [
            SimpleNamespace(
                id=offer['id'],
                fOfferPrice=offer['fOfferPrice'],
                dOfferModify=offer['dOfferModify'],
                sOfferFlapConfig=offer['sOfferFlapConfig'],
                sDescripion=offer['kOffer2MountDim__sDescripion'],
                iWinWidth=offer['kOffer2MountDim__iWinWidth'],
                iWinHight=offer['kOffer2MountDim__iWinHight'],
                setID=offer['kOffer2SetKit__id'],
                sSetName=offer['kOffer2SetKit__sSetName'],
                dSetModify=offer['kOffer2SetKit__dSetModify'],
                dSetCommercialUntil=offer['kOffer2SetKit__dSetCommercialUntil'],
                sSetClimateControl=offer['kOffer2SetKit__sSetClimateControl'],
                sSetSill=offer['kOffer2SetKit__sSetSill'],
                sSetImplementAll=offer['kOffer2SetKit__sSetImplementAll'],
                sSetImplementHandles=offer['kOffer2SetKit__sSetImplementHandles'],
                sSetImplementHinges=offer['kOffer2SetKit__sSetImplementHinges'],
                sSetImplementLatch=offer['kOffer2SetKit__sSetImplementLatch'],
                sSetImplementLimiter=offer['kOffer2SetKit__sSetImplementLimiter'],
                sSetImplementCatch=offer['kOffer2SetKit__sSetImplementCatch'],
                sSetPanes=offer['kOffer2SetKit__sSetPanes'],
                sSetSlope=offer['kOffer2SetKit__sSetSlope'],
                sSetOtherConditions=offer['kOffer2SetKit__sSetOtherConditions'],
                sSetDelivery=offer['kOffer2SetKit__sSetDelivery'],
                bSetDelivery=offer['kOffer2SetKit__bSetDelivery'],
                sSetUninstallInstall=offer['kOffer2SetKit__sSetUninstallInstall'],
                bSetUninstallInstall=offer['kOffer2SetKit__bSetUninstallInstall'],
                fSetRating=offer['kOffer2SetKit__fSetRating'],
                sOfficePhones=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficePhones'],
                sOfficeDiscountMetaFormula=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeDiscountMetaFormula'],
                sOfficeName=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeName'],
                sOfficeAddress=offer['kOffer2SetKit__kSet2User__kMerchantOffice__sOfficeAddress'],
                sGlazingBriefDescription=offer['kOffer2SetKit__kSet2Glazing__sGlazingBriefDescription'],
                sGlazingMark=offer['kOffer2SetKit__kSet2Glazing__sGlazingMark'],
                sGlazingToning=offer['kOffer2SetKit__kSet2Glazing__sGlazingToning'],
                pwc_id=offer['kOffer2SetKit__kSet2PVCprofiles__id'],
                sProfileName=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileName'],
                sProfileManufacturer=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileManufacturer'],
                sProfileSealDescription=offer['kOffer2SetKit__kSet2PVCprofiles__sProfileSealDescription'],
                sMerchantName=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantName'],
                pMerchantLogo=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__pMerchantLogo'],
                sMerchantMainURL=offer['kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__sMerchantMainURL'],
                iQuantity=1,
            )
            for offer in q_price_offer[frame_begin_n:frame_begin_n + 10000]
        ]

        price_frame = []
        n_begin = int(frame_begin_n)
        for offer in q_price_offer:
            n_begin += 1
            total = offer.fOfferPrice
            image_file = get_flaps_for_mini_pictures(offer.sOfferFlapConfig)
            dim_in_offer = [{
                'PRICE': offer.fOfferPrice,
                'FLAP': offer.sOfferFlapConfig,
                'DESCRIPTION': offer.sDescripion,
                'WIDTH': offer.iWinWidth,
                'HIGHT': offer.iWinHight,
                'ID': offer.id,
                'IMG_MINI': image_file,
                'QUANTITY': 1,
                'BULLET': ['A'],
                'SUBTOTAL': offer.fOfferPrice,
            }]

            discount = 0
            try:
                meta_keys = eval(offer.sOfficeDiscountMetaFormula)
                if KEY_DICSOUNT in meta_keys:
                    for CountVal in sorted(meta_keys[KEY_DICSOUNT]):
                        if float(total) > float(CountVal):
                            discount = meta_keys[KEY_DICSOUNT][CountVal]
            except (ValueError, TypeError):
                pass

            fin_price = total * (100 - discount) / 100
            if discount > 99 or discount < 0.1:
                discount_color1 = ""
                discount_color2 = ""
            else:
                color_ratio = (discount + 0.) / 100
                discount_color1 = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
                discount_color2 = f"#{255 - int(color_ratio * 255):02x}ff{255 - int(color_ratio * 255):02x}"

            price_frame.append({
                'DISTANCE': -1,
                'DIM': dim_in_offer,
                'TOTAL': total,
                'DISCOUNT': discount,
                'DISCOUNT_COLOR1': discount_color1,
                'DISCOUNT_COLOR2': discount_color2,
                'FIN_PRICE': fin_price,
                'OFFICE_NAME': offer.sOfficeName,
                'OFFICE_ADDRESS': offer.sOfficeAddress,
                'OFFICE_PHONES': offer.sOfficePhones,
                'MERCHANT': offer.sMerchantName,
                'MERCHANT_LOGO': offer.pMerchantLogo,
                'MERCHANT_URL': offer.sMerchantMainURL,
                'MERCHANT_URL_SHOT': re.sub(r"(^http://|^https://|/$|www\.)", "", offer.sMerchantMainURL),
                'SETS_NAME': offer.sSetName,
                'GLAZING_NAME_B': offer.sGlazingBriefDescription,
                'GLAZING_MARK': offer.sGlazingMark,
                'GLAZING_TONING': offer.sGlazingToning,
                'PVC_ID': offer.pwc_id,
                'PVC_NAME': offer.sProfileName,
                'PVC_NAME_T': _slugify_lower(offer.sProfileName),
                'PVC_MANUFACTURER': offer.sProfileManufacturer,
                'PVC_MANUFACTURER_T': _slugify_lower(offer.sProfileManufacturer),
                'PVC_SEAL': offer.sProfileSealDescription,
                'SETS_CLIMATE_CONTROL': offer.sSetClimateControl,
                'SETS_SILL': offer.sSetSill,
                'SETS_IMPLEMENT': offer.sSetImplementAll,
                'SETS_IMPLEMENT_R': offer.sSetImplementHandles,
                'SETS_IMPLEMENT_P': offer.sSetImplementHinges,
                'SETS_IMPLEMENT_Z': offer.sSetImplementLatch,
                'SETS_IMPLEMENT_O': offer.sSetImplementLimiter,
                'SETS_IMPLEMENT_F': offer.sSetImplementCatch,
                'SETS_PANES': offer.sSetPanes,
                'SETS_SLOPE': offer.sSetSlope,
                'SETS_DELIVERY': offer.sSetDelivery,
                'SETS_DELIVERY_B': offer.bSetDelivery,
                'SETS_OTHER': offer.sSetOtherConditions,
                'SETS_ID': offer.setID,
                'SETS_UNINSTALL_INSTALL': offer.sSetUninstallInstall,
                'SETS_UNINSTALL_INSTALL_B': offer.bSetUninstallInstall,
                'SETS_RATING': offer.fSetRating,
                'SETS_RATING_STARTS': get_rating_set_for_stars(offer.fSetRating),
                'SETS_DATA_MODIFY': offer.dOfferModify,
                'IS_COMMERCIAL': offer.dSetCommercialUntil > timezone.now(),
            })

            if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
                    django.utils.dateformat.format(offer.dOfferModify, 'U'):
                time_for_meta = offer.dOfferModify
            if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
                    django.utils.dateformat.format(offer.dSetModify, 'U'):
                time_for_meta = offer.dSetModify

            if len(price_frame) == offer_per_frame:
                break

        price_frame = sorted(price_frame, key=lambda item: item['DISTANCE'])
        if len(price_frame) < offer_per_frame:
            n_begin = '-1'
        return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin}
    else:
        # если выводим цены для типовой квартиры
        # ORM-ветка сохраняет контракт полей для шаблонов price_list.html и price_list_frame.html.
        quantities_by_mount_dim = {
            row['kMountDim_id']: row['iQuantity']
            for row in MountDim2Apartment.objects.filter(kApartment_id=apartment_id).values('kMountDim_id', 'iQuantity')
        }
        if not quantities_by_mount_dim:
            return {'META_DATA_PUBLISH': 0, 'PRICE_FRAME': [], 'N': '-1'}

        q_price_offer = (
            PriceOffer.objects.filter(
                sOfferActive=True,
                kOffer2MountDim_id__in=quantities_by_mount_dim.keys(),
                kOffer2SetKit__sSetActive=True,
                kOffer2SetKit__kSet2User__kMerchantOffice__isnull=False,
                kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName__isnull=False,
                kOffer2SetKit__kSet2Glazing__isnull=False,
                kOffer2SetKit__kSet2PVCprofiles__isnull=False,
            )
            .select_related(
                'kOffer2MountDim',
                'kOffer2SetKit',
                'kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName',
                'kOffer2SetKit__kSet2Glazing',
                'kOffer2SetKit__kSet2PVCprofiles',
            )
            .order_by(
                '-kOffer2SetKit__dSetCreate',
                '-kOffer2MountDim__bIsNearDoor',
                '-kOffer2MountDim__bIsDoor',
                'kOffer2MountDim__iWinWidth',
                '-kOffer2MountDim__iWinHight',
                'id',
            )
        )
        if brand_id != 0:
            q_price_offer = q_price_offer.filter(
                kOffer2SetKit__kSet2User__kMerchantOffice__kMerchantName_id=brand_id,
            )

        q_price_offer = [
            SimpleNamespace(
                id=offer.id,
                fOfferPrice=offer.fOfferPrice,
                dOfferModify=offer.dOfferModify,
                sOfferFlapConfig=offer.sOfferFlapConfig,
                sDescripion=offer.kOffer2MountDim.sDescripion,
                iWinWidth=offer.kOffer2MountDim.iWinWidth,
                iWinHight=offer.kOffer2MountDim.iWinHight,
                iQuantity=quantities_by_mount_dim.get(offer.kOffer2MountDim_id, 0),
                setID=offer.kOffer2SetKit.id,
                dSetModify=offer.kOffer2SetKit.dSetModify,
                dSetCommercialUntil=offer.kOffer2SetKit.dSetCommercialUntil,
                bCommercial=bool(
                    offer.kOffer2SetKit.dSetCommercialUntil
                    and offer.kOffer2SetKit.dSetCommercialUntil > timezone.now()
                ),
                sSetName=offer.kOffer2SetKit.sSetName,
                sSetClimateControl=offer.kOffer2SetKit.sSetClimateControl,
                sSetSill=offer.kOffer2SetKit.sSetSill,
                sSetImplementAll=offer.kOffer2SetKit.sSetImplementAll,
                sSetImplementHandles=offer.kOffer2SetKit.sSetImplementHandles,
                sSetImplementHinges=offer.kOffer2SetKit.sSetImplementHinges,
                sSetImplementLatch=offer.kOffer2SetKit.sSetImplementLatch,
                sSetImplementLimiter=offer.kOffer2SetKit.sSetImplementLimiter,
                sSetImplementCatch=offer.kOffer2SetKit.sSetImplementCatch,
                sSetPanes=offer.kOffer2SetKit.sSetPanes,
                sSetSlope=offer.kOffer2SetKit.sSetSlope,
                sSetOtherConditions=offer.kOffer2SetKit.sSetOtherConditions,
                sSetDelivery=offer.kOffer2SetKit.sSetDelivery,
                bSetDelivery=offer.kOffer2SetKit.bSetDelivery,
                sSetUninstallInstall=offer.kOffer2SetKit.sSetUninstallInstall,
                bSetUninstallInstall=offer.kOffer2SetKit.bSetUninstallInstall,
                fSetRating=offer.kOffer2SetKit.fSetRating,
                sOfficePhones=offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficePhones,
                sOfficeDiscountMetaFormula=(
                    offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficeDiscountMetaFormula or ""
                ),
                sOfficeName=offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficeName,
                sOfficeAddress=offer.kOffer2SetKit.kSet2User.kMerchantOffice.sOfficeAddress,
                fOfficeGeoCode_Longitude=(
                    offer.kOffer2SetKit.kSet2User.kMerchantOffice.fOfficeGeoCode_Longitude or 0
                ),
                fOfficeGeoCode_Latitude=(
                    offer.kOffer2SetKit.kSet2User.kMerchantOffice.fOfficeGeoCode_Latitude or 0
                ),
                sGlazingBriefDescription=offer.kOffer2SetKit.kSet2Glazing.sGlazingBriefDescription,
                sGlazingMark=offer.kOffer2SetKit.kSet2Glazing.sGlazingMark,
                sGlazingToning=offer.kOffer2SetKit.kSet2Glazing.sGlazingToning,
                pwc_id=offer.kOffer2SetKit.kSet2PVCprofiles.id,
                sProfileName=offer.kOffer2SetKit.kSet2PVCprofiles.sProfileName,
                sProfileManufacturer=offer.kOffer2SetKit.kSet2PVCprofiles.sProfileManufacturer or "",
                sProfileSealDescription=offer.kOffer2SetKit.kSet2PVCprofiles.sProfileSealDescription,
                sMerchantName=offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.sMerchantName,
                pMerchantLogo=(
                    str(offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.pMerchantLogo)
                    if offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.pMerchantLogo
                    else ""
                ),
                sMerchantMainURL=(
                    offer.kOffer2SetKit.kSet2User.kMerchantOffice.kMerchantName.sMerchantMainURL or ""
                ),
            )
            for offer in q_price_offer[frame_begin_n:frame_begin_n + 10000]
        ]
    price_frame = []
    count_mount_dim_in_offer = 0
    dim_in_offer = []
    total = 0
    cur_bullet = 0
    previous_set_id = 0
    count_mount_dim_in_frame_page = 0
    # Так как получен QuerySet начиная с frame_begin_n, то для того чтобы получить frame_begin_n следующего фрйма
    # считаем не от нуля, а от старого frame_begin_n
    n_begin = int(frame_begin_n)
    # Проверяем есть ли папка для хранения мини-картинки конфигурации схемы открывания.
    if not os.path.exists(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}"):
        # создаем такую папку если её нет
        os.makedirs(f"{STATIC_BASE_PATH}/{PATH_FOR_IMG}/{PATH_FOR_IMGFLAPCONFIG}")
    # print(">>>>>>>>>>>>>", apartment_id)
    for i2 in q_price_offer:
        n_begin += 1
        count_mount_dim_in_frame_page += 1
        # Случается, что в том или ином наборе поставщиком просчитаны не все проёмы.
        # Чтобы не происходило формирование предложения (офера) из окон разных наборов делаем проверку
        # является ли текущий проём в предложении из того же набора, что и предыдущий.
        # Если он из другого набора, то удаляем предыдущий проём из предложения и начинаем
        # формирование предложения сначала.
        if count_mount_dim_in_offer == 0:
            previous_set_id = i2.setID
        else:
            if previous_set_id != i2.setID:
                # print("Сбой в наборе. Обнуляем набор")
                previous_set_id = i2.setID
                count_mount_dim_in_offer = 0
                total = 0
                cur_bullet = 0
                dim_in_offer.pop()
                dim_in_offer = []
        # print("mID:", i2.mID, " ||  pID:", i2.id, " ||  price:", i2.fOfferPrice, " ||  N:", i2.iQuantity,
        #       " ||  set:",  i2.sSetName, " ||  merchant:", i2.sOfficeName)
        total += i2.fOfferPrice * i2.iQuantity
        image_file = get_flaps_for_mini_pictures(i2.sOfferFlapConfig)
        dim_in_offer.append({
            'PRICE': i2.fOfferPrice,
            'FLAP': i2.sOfferFlapConfig,
            'DESCRIPTION': i2.sDescripion,
            'WIDTH': i2.iWinWidth,
            'HIGHT': i2.iWinHight,
            'ID': i2.id,
            'IMG_MINI': image_file,
            'QUANTITY': i2.iQuantity,
            'BULLET': [chr(65 + cur_bullet + i) for i in range(i2.iQuantity)],
            # 'BULLET':   range(CurBullet, CurBullet+i2.iQuantity),
            'SUBTOTAL': i2.fOfferPrice * i2.iQuantity,
        })
        cur_bullet += i2.iQuantity
        count_mount_dim_in_offer += 1
        if count_mount_dim_in_offer == mount_dim_per_offer:
            # print("-----------------")
            # узнаем скидку через разбор формулы на метаязыке
            discount = 0
            try:
                meta_keys = eval(i2.sOfficeDiscountMetaFormula)
                if KEY_DICSOUNT in meta_keys:
                    # скидки рассчитываются исходя из общей суммы
                    for CountVal in sorted(meta_keys[KEY_DICSOUNT]):
                        # print(CountVal, "::",  meta_keys[KEY_DICSOUNT][CountVal])
                        if float(total) > float(CountVal):
                            discount = meta_keys[KEY_DICSOUNT][CountVal]
                        #     # DiscountTXT += u"!!%d!!" % Discount
                        #     print("Значит DISCOUNT: ", Discount)
            except (ValueError, TypeError):
                pass
            fin_price = total * (100 - discount) / 100
            # уточняем, есть ли в принципе геокоординаты?
            if int(i2.fOfficeGeoCode_Longitude) != 0 and int(i2.fOfficeGeoCode_Latitude) != 0 and \
                    int(address_longitude) != 0 and int(address_latitude) != 0:
                # рассчитываем дистанцию между адресом дома и офиса.
                distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude, address_longitude,
                                            address_latitude)
                # т.к. из-за изменений в api яндекс карт поменялась местами широта-долгота и вообще, то
                # порядок переменных  строчной выше... На самом деле должно быть как в закоментированной
                # строке ниже
                # distance = get_geo_distance(i2.fOfficeGeoCode_Longitude, i2.fOfficeGeoCode_Latitude,
                #                             address_longitude, address_latitude)
            else:
                distance = -1
            # print(discount)
            if discount > 99 or discount < 0.1:
                discount_color1 = ""
                discount_color2 = ""
            else:
                color_ratio = (discount + 0.) / 100
                discount_color1 = f"#{255 - int(color_ratio * 128):02x}ff{255 - int(color_ratio * 128):02x}"
                discount_color2 = f"#{255 - int(color_ratio * 255):02x}ff{255 - int(color_ratio * 255):02x}"
                # print(discount_color1, discount_color2)
            price_frame.append({
                'DISTANCE': distance,
                'DIM': dim_in_offer,
                'TOTAL': total,
                'DISCOUNT': discount,
                'DISCOUNT_COLOR1': discount_color1,
                'DISCOUNT_COLOR2': discount_color2,
                'FIN_PRICE': fin_price,
                'OFFICE_NAME': i2.sOfficeName,
                'OFFICE_ADDRESS': i2.sOfficeAddress,
                'OFFICE_PHONES': i2.sOfficePhones,
                'MERCHANT': i2.sMerchantName,
                'MERCHANT_LOGO': i2.pMerchantLogo,
                'MERCHANT_URL': i2.sMerchantMainURL,
                'MERCHANT_URL_SHOT': re.sub(r"(?:^http://|^https://|/$|www\.)", "", i2.sMerchantMainURL),
                'SETS_NAME': i2.sSetName,
                'GLAZING_NAME_B': i2.sGlazingBriefDescription,
                'GLAZING_MARK': i2.sGlazingMark,
                'GLAZING_TONING': i2.sGlazingToning,
                'PVC_ID': i2.pwc_id,
                'PVC_NAME': i2.sProfileName,
                'PVC_NAME_T': _slugify_lower(i2.sProfileName),
                'PVC_MANUFACTURER': i2.sProfileManufacturer,
                'PVC_MANUFACTURER_T': _slugify_lower(i2.sProfileManufacturer),
                'PVC_SEAL': i2.sProfileSealDescription,
                'SETS_CLIMATE_CONTROL': i2.sSetClimateControl,
                'SETS_SILL': i2.sSetSill,
                'SETS_IMPLEMENT': i2.sSetImplementAll,
                'SETS_IMPLEMENT_R': i2.sSetImplementHandles,
                'SETS_IMPLEMENT_P': i2.sSetImplementHinges,
                'SETS_IMPLEMENT_Z': i2.sSetImplementLatch,
                'SETS_IMPLEMENT_O': i2.sSetImplementLimiter,
                'SETS_IMPLEMENT_F': i2.sSetImplementCatch,
                'SETS_PANES': i2.sSetPanes,
                'SETS_SLOPE': i2.sSetSlope,
                'SETS_DELIVERY': i2.sSetDelivery,
                'SETS_DELIVERY_B': i2.bSetDelivery,
                'SETS_OTHER': i2.sSetOtherConditions,
                'SETS_ID': i2.setID,
                'SETS_UNINSTALL_INSTALL': i2.sSetUninstallInstall,
                'SETS_UNINSTALL_INSTALL_B': i2.bSetUninstallInstall,
                'SETS_RATING': i2.fSetRating,
                'SETS_RATING_STARTS': get_rating_set_for_stars(i2.fSetRating),
                'SETS_DATA_MODIFY': i2.dOfferModify,
                'IS_COMMERCIAL': i2.bCommercial,
            })
            if len(price_frame) == offer_per_frame:
                break
            count_mount_dim_in_offer = 0
            dim_in_offer = []
            total = 0
            cur_bullet = 0
        # узнаем дату-время самого свежего ценового предложения для размещения в META-тега
        if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
                django.utils.dateformat.format(i2.dOfferModify, 'U'):
            time_for_meta = i2.dOfferModify
        if time_for_meta == 0 or django.utils.dateformat.format(time_for_meta, 'U') < \
                django.utils.dateformat.format(i2.dSetModify, 'U'):
            time_for_meta = i2.dSetModify
    # массив ценовых предложений (что бы предложения на одной дистанции были в случайном порядке)
    # random.shuffle(PriceFrame)
    # сортируем по удаленности
    price_frame = sorted(price_frame, key=lambda item: item['DISTANCE'])
    if len(price_frame) < offer_per_frame:
        n_begin = '-1'
    return {'META_DATA_PUBLISH': time_for_meta, 'PRICE_FRAME': price_frame, 'N': n_begin}


def report_one_win_price(request: HttpRequest,
                         win_width_mm: str | int = DEFAULT_WIN_WIDTH_MM,
                         win_height_mm: str | int = DEFAULT_WIN_HEIGHT_MM,
                         win_id: str | int = DEFAULT_WIN_ID) -> HttpResponse:
    """ Формируем выдачу цен для единичного ТИПОВОГО окна (т.е. проема из серийного дома).


    :param request: HttpRequest -- входящий http-запрос
    :param win_width_mm: str -- Ширина проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы)
    :param win_height_mm: str -- Высота проема в миллиметрах (это SEO-параметр, в реальности он будет получен из базы)
    :param win_id: str -- ID проема (см. таблицу oknardia_win_mountdim)
    :return response: HttpResponse -- исходящий http-ответ
    """
    time_start = time.perf_counter()
    to_template: dict[str, object] = {}
    try:
        win_info_rows = (
            Win_MountDim.objects
            .filter(id=int(win_id))
            .values(
                'id',
                'iWinWidth',
                'iWinHight',
                'iWinDepth',
                'sFlapConfig',
                'bIsNearDoor',
                'bIsDoor',
                'sDescripion',
            )
        )
        list_win_info = [
            SimpleNamespace(
                id=item['id'],
                iWinWidth=item['iWinWidth'],
                iWinHight=item['iWinHight'],
                iWinDepth=item['iWinDepth'],
                sFlapConfig=item['sFlapConfig'],
                bIsNearDoor=item['bIsNearDoor'],
                bIsDoor=item['bIsDoor'],
                sDescripion=item['sDescripion'],
                iQuantity=0,
            )
            for item in win_info_rows
        ]
        # Если размеры типового проема не совпадают с размерами из базы, то подменяем
        # на правильные и перевызываем страницу
        canonical_width_mm = int(list_win_info[0].iWinWidth * 10)
        canonical_height_mm = int(list_win_info[0].iWinHight * 10)
        if (list_win_info[0].iWinWidth * 10 != int(win_width_mm)) or \
                (list_win_info[0].iWinHight * 10 != int(win_height_mm)):
            return redirect(
                _one_win_price_canonical_path(
                    win_width_mm=canonical_width_mm,
                    win_height_mm=canonical_height_mm,
                    win_id=win_id,
                ),
                permanent=True,
            )
    except (ObjectDoesNotExist, ValueError, IndexError, TypeError):
        return redirect(
            _one_win_price_canonical_path(
                win_width_mm=DEFAULT_WIN_WIDTH_MM,
                win_height_mm=DEFAULT_WIN_HEIGHT_MM,
                win_id=DEFAULT_WIN_ID,
            ),
            permanent=True,
        )
    # все хорошо, засылаем картинку в шаблон
    to_template.update(get_flaps_for_big_pictures(list_win_info))
    # получаем варианты схемы открывания (для графиков)
    flap_variations = (
        PriceOffer.objects.filter(
            sOfferActive=True,
            kOffer2MountDim_id=int(win_id),
        )
        .values('sOfferFlapConfig')
        .annotate(id=Count('sOfferFlapConfig'))
        .order_by('-id')
    )
    list_offer_flap_variation = [
        SimpleNamespace(
            id=item['id'],
            sOfferFlapConfig=item['sOfferFlapConfig'],
            IMG_MINI='',
            STR_NUM='',
        )
        for item in flap_variations
    ]
    for i in range(0, len(list_offer_flap_variation)):
        if i < 3:
            list_offer_flap_variation[i].STR_NUM = "вариант " + pytils.numeral.in_words(i + 1)
        elif i == 3:
            list_offer_flap_variation[i].STR_NUM = "остальные варианты"
            continue
        else:
            list_offer_flap_variation[3].id += list_offer_flap_variation[i].id
            continue
        list_offer_flap_variation[i].IMG_MINI = get_flaps_for_mini_pictures(
            list_offer_flap_variation[i].sOfferFlapConfig
        )
    to_template.update({'LIST_FLAP_VARIATION': list_offer_flap_variation[:4]})
    to_template.update({'NUM_FLAP_VARIATION_IN_WORD': pytils.numeral.sum_string(len(list_offer_flap_variation),
                                                                                pytils.numeral.MALE,
                                                                                ("вариант схемы",
                                                                                 "варианта схем",
                                                                                 "вариантов схем"))})
    #
    firms_count = (
        PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id))
        .values('kOfferFromUser_id')
        .distinct()
        .count()
    )
    to_template.update({'NUM_TOTAL_FIRM_N_WORD': pytils.numeral.get_plural(firms_count,
                                                                           ("компании", "компаний", "компаний"))})
    q = PriceOffer.objects.filter(kOffer2MountDim_id=int(win_id))
    to_template.update({'NUM_TOTAL_OFFER_N_WORD': pytils.numeral.get_plural(q.count(),
                                                                            ("готовый расчёт", "готовых расчёта",
                                                                             "готовых расчётов"))})
    to_template.update({'NUM_ARCHIVE_OFFER': q.filter(sOfferActive=0).count()})
    #
    seria_for_win = (
        MountDim2Apartment.objects.filter(kMountDim_id=int(win_id), kApartment__kSeria__isnull=False)
        .values('kApartment__kSeria__id', 'kApartment__kSeria__sName')
        .annotate(num_variation_of_apartment=Count('id'))
        .order_by('kApartment__kSeria__sName')
    )
    list_seria_for_win = []
    for seria_item in seria_for_win:
        seria_name = seria_item['kApartment__kSeria__sName']
        list_seria_for_win.append(SimpleNamespace(
            id=seria_item['kApartment__kSeria__id'],
            sName=seria_name,
            sNameLat=_slugify_lower(seria_name),
            num_variation_of_apartment=pytils.numeral.sum_string(
                seria_item['num_variation_of_apartment'],
                pytils.numeral.MALE,
                ("типовую планировку квартиры",
                 "типовые планировки квартир",
                 "типовых планировок квартир"),
            ),
        ))
    to_template.update(report_price_frame(apartment_id=0,
                                          mount_dim_per_offer= 1,
                                          address_longitude= 0,
                                          address_latitude= 0,
                                          frame_begin_n= 0,
                                          brand_id= 0,
                                          win_id=int(win_id)
                                          )
                       )
    to_template.update({
        'SERIA_FOR_WIN': list_seria_for_win,
        'WIN_ID': int(win_id),
        'MOUNT_DIM_PER_OFFER': 1,
    })
    _append_visit_context(to_template=to_template, request=request, time_start=time_start)
    return render(request, "price/price_offers_for_one_window.html", to_template)


def next_one_win_price(request: HttpRequest,
                       win_id: str | int = DEFAULT_WIN_ID,
                       frame_begin_n: str | int = 0):
    """ Возвращает очередной фреймом ценовых предложений для выдачи с одиночным окном.

    :param request: HttpRequest -- входящий http-запрос
    :param win_id: str -- id типового окна
    :param frame_begin_n: str -- Номер записи, с которой начинается фрейм с ценами
    :return: HttpResponse --
    """
    time_start = time.perf_counter()
    to_template: dict[str, object] = report_price_frame(apartment_id=0,
                                                        mount_dim_per_offer=1,
                                                        address_longitude=0,
                                                        address_latitude=0,
                                                        frame_begin_n=int(frame_begin_n),
                                                        brand_id=0,
                                                        win_id=int(win_id)
                                                        )
    to_template.update({'MOUNT_DIM_PER_OFFER': 1,
                        'WIN_ID': int(win_id),
                        'ticks': float(time.perf_counter() - time_start)})
    return render(request, "price/price_offers_for_one_window_frame.html", to_template)


def report_price(request: HttpRequest, build_id: str = "22427", apart_id: str = "61",
                 slug: str = "g-moskva-ul-novorossijskaya-d-16") -> HttpResponse:
    """ Страница с расчетом стоимости окон

    :param request: HttpRequest -- входящий http-запрос
    :param build_id: str - id здания (адрес в таблице oknardia_building_info.id)
    :param apart_id: str - id типовой планировки квартиры (в таблице oknardia_apartment_type.id)
    :param slug: str - slug адреса здания
    :return: response: HttpResponse
    """
    time_start = time.perf_counter()
    msg = ""
    to_template: dict[str, object] = {}
    try:
        build_id = int(build_id)
        apart_id = int(apart_id)
    except ValueError:
        return redirect("/")
    try:
        building = Building_Info.objects.select_related('kSeria_Link__kRoot').get(id=build_id)
        if not building.kSeria_Link_id or not getattr(building.kSeria_Link, 'kRoot_id', None):
            return redirect("/")

        list_apart = list(
            Apartment_Type.objects.filter(kSeria_id=building.kSeria_Link.kRoot_id).order_by('iSort')
        )
        if not list_apart:
            return redirect("/")

        # если кто-то нахимичит ID квартиры не для этого дома, то сделаем так, что он будет от этого дома!
        apart_inside = any(ap.id == apart_id for ap in list_apart)
        address_slug = _slugify_lower(building.sAddress)
        if not apart_inside or slug != address_slug:
            # Переадресация 302, если с apart_id (ID-квартиры нахимичили) или slug-ом.
            # Нужно для склейки парных URL в поисковиках
            # При переходе с карты apart_id выставляем в 0. Из-за этого тоже нужно 302-переадресация.
            return redirect(f"/{build_id}/{list_apart[0].id}/{address_slug}")
        address_latitude = building.fGeoCode_Latitude
        address_longitude = building.fGeoCode_Longitude
        to_template.update({'BUILD_ID': build_id})
        to_template.update({'APPARTMENT_ID': apart_id})
        to_template.update({'ADDRESS_LAT': address_latitude})
        to_template.update({'ADDRESS_LON': address_longitude})
        to_template.update({'ADDRESS': building.sAddress})
        to_template.update({'ADDRESS_T': address_slug})
        to_template.update({'SERIA': building.sSerias_Project})
        # данные нужные для отображения информации о доме (метраж, число подъездов и пр.)
        to_template.update({'CADASTRE_NUM': building.sCadastre_Num_Area})
        to_template.update({'INVENTORY_NUM': building.sInventory_Num})
        to_template.update({'TYPE_BUILDING': building.sType})
        to_template.update({'ENERGY_EFFICIENCY': building.sEnergy_Efficiency})
        if building.fTotal_Area != -1.0:
            to_template.update({'TOTAL_AREA': f"{building.fTotal_Area:.1f}"})
        if building.fLand_Area != -1.0:
            to_template.update({'LAND': f"{building.fLand_Area:.1f}"})
        if building.iNum_Apartments != -1:
            to_template.update({'NUM_APARTMENTS': building.iNum_Apartments})
        if building.iStoreys != -1:
            to_template.update({'STOREYS': building.iStoreys})
        if building.fCommon_Area != -1.0:
            to_template.update({'COMMON_AREA': f"{building.fCommon_Area:.1f}"})
        if building.iEntrances_Porchs != -1:
            to_template.update({'NUM_ENTERANCES': building.iEntrances_Porchs})
        if building.fUninhabited_Area != -1.0:
            to_template.update({'UNINHABITED_AREA': f"{building.fUninhabited_Area:.1f}"})
        if building.sManagement_Co != u"N/A":
            to_template.update({'MANAGEMENT_CO': building.sManagement_Co})
        if building.iElevators != -1:
            to_template.update({'NUM_ELEVATORS': building.iElevators})
        if building.fResidential_Area != -1.0:
            to_template.update({'RESIDENTIAL_AREA': f"{building.fResidential_Area:.1f}"})
        if building.iNum_Residents != -1:
            to_template.update({'NUM_RESIDENTS': building.iNum_Residents})
        if building.fPrivate_Area != -1.0:
            to_template.update({'PRIVATE_AREA': f"{building.fPrivate_Area:.1f}"})
        if building.iNum_Accounts != -1:
            to_template.update({'NUM_ACCOUNTS': building.iNum_Accounts})
        if building.iCommissioning_year != "N/A":
            to_template.update({'COMMISSIONING_YEAR': building.iCommissioning_year})
        if building.fGovernment_Area != -1.0:
            to_template.update({'GOVERNMENT_AREA': f"{building.fGovernment_Area:.1f}"})
        if building.fCondition_House != -1.0:
            to_template.update({'CONDITION_HOUSE': f"{building.fCondition_House:.0f}%"})
        if building.fCondition_Foundation != -1.0:
            to_template.update({'CONDITION_FOUNDATION': f"{building.fCondition_Foundation:.0f}%"})
        if building.fCondition_Walls != -1.0:
            to_template.update({'CONDITION_WALL': f"{building.fCondition_Walls:.0f}%"})
        if building.fCondition_Overlap != -1.0:
            to_template.update({'CONDITION_OVERLAP': f"{building.fCondition_Overlap:.0f}%"})
        if building.fMunicipal_Area != -1.0:
            to_template.update({'MUNICIPAL_AREA': f"{building.fMunicipal_Area:.1f}"})
        # заполняем массив квартир для отправки в шаблон
        apart_in_building = []
        for apartment_count in list_apart:
            apartment_in = {}
            if apartment_count.id != apart_id:
                apartment_in.update({'APT_ID': apartment_count.id})
            else:
                apartment_in.update({'APT_ID': "!"})
            apartment_in.update({'APT_NAME': apartment_count.sNameApartment})
            apart_in_building.append(apartment_in)
        to_template.update({'APARTMENT_IN_BUILDING': apart_in_building})

        # узнаем базовую серию дома
        q_base_seria = building.kSeria_Link.kRoot
        base_seria_slug = _slugify_lower(q_base_seria.sName)
        to_template.update({'BASE_SERIA': q_base_seria.sName,
                            'BASE_SERIA_LAT': base_seria_slug,
                            'BASE_SERIA_ID': q_base_seria.id})
    # except (ValueError, IndexError, TypeError, ObjectDoesNotExist):
    except ObjectDoesNotExist:
        return redirect("/")
    ###############################################
    # получаем массив окон для данной квартиры...
    try:
        list_mount_dim_per_offer = [
            SimpleNamespace(
                sNameApartment=row.kApartment.sNameApartment,
                iWinWidth=row.kMountDim.iWinWidth,
                iWinHight=row.kMountDim.iWinHight,
                iWinDepth=row.kMountDim.iWinDepth,
                sFlapConfig=row.kMountDim.sFlapConfig,
                bIsNearDoor=row.kMountDim.bIsNearDoor,
                bIsDoor=row.kMountDim.bIsDoor,
                sDescripion=row.kMountDim.sDescripion,
                id=row.kMountDim.id,
                iQuantity=row.iQuantity,
            )
            for row in MountDim2Apartment.objects.filter(kApartment_id=apart_id)
            .select_related('kApartment', 'kMountDim')
            .order_by(
                '-kMountDim__bIsNearDoor',
                '-kMountDim__bIsDoor',
                'kMountDim__iWinWidth',
                '-kMountDim__iWinHight',
            )
        ]
        if not list_mount_dim_per_offer:
            return redirect("/")

        mount_dim_per_offer = len(list_mount_dim_per_offer)
        to_template.update({'APART': list_mount_dim_per_offer[0].sNameApartment})

        # получаем данные для отрисовки больших картинок с проемами.
        to_template.update(get_flaps_for_big_pictures(list_mount_dim_per_offer))
        # <---
    except (ValueError, IndexError, TypeError, ObjectDoesNotExist):
        return redirect("/")

    # получаем данные для фрейма ценовых предложений
    price_frame = report_price_frame(apartment_id=apart_id,
                                     mount_dim_per_offer=mount_dim_per_offer,
                                     address_longitude=address_longitude,
                                     address_latitude=address_latitude
                                     )
    to_template.update(price_frame)
    # print u"строк в querySet:", CountMountDimInFramePage
    # dimension_to_template.update({'DISCOUNT_TXT': DiscountTXT})
    to_template.update({'MOUNT_DIM_PER_OFFER': mount_dim_per_offer,
                        'META_DESCRIPTION': "Окнардия ",
                        'META_KEYWORDS': "Окнардия+ ",
                        'MSG': msg})

    # получаем последние визиты всех посетителей из базы
    log_visit = get_last_all_user_visit_list()
    if log_visit and log_visit[0].get('id') is not None:
        id_last_visit_log = log_visit[0]['id'] + 1
    else:
        id_last_visit_log = 1
    # print("id_last_visit_log:", id_last_visit_log)
    if id_last_visit_log > MAX_LEN_RING_LOG_BUFFER:  # максимальный размер циклического буфера
        id_last_visit_log = 1  # ставим в начало буфера
    
    new_url = f"/price/seriaID{to_template['BASE_SERIA_ID']}--{to_template['BASE_SERIA_LAT']}/appartID{apart_id}/addressID{build_id}--null"
    
    try:
        log_entry = LogVisitPriceReport.objects.get(id=id_last_visit_log)
        log_entry.sLogAddress = to_template["ADDRESS"]
        log_entry.sLogNameApartment = to_template["APART"]
        log_entry.sLogURL = new_url
        log_entry.dLogVisitTime = time.perf_counter()
        log_entry.save()  # UPDATE
    except ObjectDoesNotExist:
        log_entry = LogVisitPriceReport(
            sLogAddress=to_template["ADDRESS"],
            sLogNameApartment=to_template["APART"],
            sLogURL=new_url,
            dLogVisitTime=time.perf_counter()
        )
        log_entry.save()  # INSERT

    # получаем последние визиты клиента через куки
    last_visit = get_last_user_visit_cookies(request)
    # Для блока LAST_VISIT показываем историю до текущего захода.
    last_visit_for_context = list(last_visit)
    # подготавливаем данные о текущем посещении для помещения в cookie
    Item = {
        "LastURL": new_url,
        "LastAddress": to_template["ADDRESS"],
        "LastApart": to_template["APART"],
        "Time": time.perf_counter()}
    last_visit.insert(0, Item)  # Добавляем текущий Item в начало
    last_visit = json.dumps(last_visit[:3])  # упаковываем json без пробелов (три записи)
    # print u"сейчас запишем вот эту куку:", LastVisit
    _append_visit_context(
        to_template=to_template,
        request=request,
        time_start=time_start,
        log_visit=log_visit,
        last_visit_cookie=last_visit_for_context,
    )
    response = render(request, "price/price_list.html", to_template)
    response.set_cookie("LastVisit", last_visit, max_age=7862400)   # ставим или перезаписываем куки (91 день)
    return response


def next_price_frame(request:  HttpRequest, apart_id: str = "1", mount_dim_per_offer: str = "1",
                     address_longitude: str = "0.", address_latitude: str = "0.",
                     frame_begin_n: str = "0") -> HttpResponse:
    """ Возвращает очередным фреймом ценовых предложений.

    :param request: HttpRequest -- входящий HTTP-запрос
    :param apart_id: str -- ID типовой квартиры, для которой получаем ценовые предложения
    :param mount_dim_per_offer: str -- число различных оконных проемов в этой квартире (чтобы отсеять предложения,
                                       в которых не представлены все проемы)
    :param address_longitude: str   -- долгота адреса (геокоордината), для которого получаем ценовые предложения, чтобы
                                       рассчитать удаленность компании предоставившей коммерческие предложения
    :param address_latitude: str    -- широта адреса (геокоордината), для которого получаем ценовые предложения, чтобы
                                       рассчитать удаленность компании предоставившей коммерческие предложения
    :param frame_begin_n: str       -- Номер записи с которой начинается фрейм с ценами
    :return: HttpResponse           -- HTTP-ответ
    """
    time_start = time.perf_counter()
    # получаем данные для фрейма ценовых предложений
    price_frame = report_price_frame(apartment_id=int(apart_id),
                                     mount_dim_per_offer=int(mount_dim_per_offer),
                                     address_longitude=float(address_longitude),
                                     address_latitude=float(address_latitude),
                                     frame_begin_n=int(frame_begin_n)
                                     )
    to_template: dict[str, object] = price_frame
    to_template.update({'APPARTMENT_ID': apart_id,
                        'MOUNT_DIM_PER_OFFER': mount_dim_per_offer,
                        'ADDRESS_LAT': address_latitude,
                        'ADDRESS_LON': address_longitude,
                        'ticks': float(time.perf_counter() - time_start)})
    return render(request, "price/price_list_frame.html", to_template)


def report_price_new(request, seria_id, seria_slug, apart_id, address_id, address_slug):
    """
    Новый view для ценовой выдачи по новому роутингу.
    :param seria_id: ID серии (Seria_Info)
    :param seria_slug: slug серии (транслит)
    :param apart_id: ID типа квартиры (Apartment_Type)
    :param address_id: ID адреса (Building_Info)
    :param address_slug: slug адреса (транслит)
    """
    from oknardia.models import Building_Info, Apartment_Type, Seria_Info
    from django.shortcuts import redirect
    # Проверяем, что все объекты существуют
    try:
        seria = Seria_Info.objects.get(id=seria_id)
        building = Building_Info.objects.get(id=address_id)
        # apartment = Apartment_Type.objects.get(id=apart_id)
    except Exception:
        return redirect("/")
    # Проверяем slug'и, если не совпадает — делаем 301 на канонический URL (новый формат)
    seria_slug_real = pytils.translit.slugify((seria.sName or "").strip()).lower()
    address_slug_real = pytils.translit.slugify((building.sAddress or "").strip()).lower()
    if seria_slug != seria_slug_real or address_slug != address_slug_real:
        # Новый формат: /price/seriaID<seria_id>--<seria_slug>/appartAD<apart_id>/addressID<address_id>--<address_slug>/
        return redirect(f"/price/seriaID{seria_id}--{seria_slug_real}/appartID{apart_id}/addressID{address_id}--{address_slug_real}/", permanent=True)
    # Вызываем старую логику выдачи (используем report_price)
    # В старом view: build_id = address_id, apart_id = apart_id, slug = address_slug
    return report_price(request, build_id=address_id, apart_id=apart_id, slug=address_slug)


def report_price_legacy_redirect(request, build_id, apart_id, slug):
    try:
        building = Building_Info.objects.select_related('kSeria_Link__kRoot').get(id=build_id)
        seria = building.kSeria_Link.kRoot
        # Если apart_id == 0, ищем минимальный валидный ID квартиры для этой серии
        if int(apart_id) == 0:
            min_apart = Apartment_Type.objects.filter(kSeria_id=seria.id).order_by('id').first()
            if min_apart:
                apart_id = min_apart.id
    except Exception:
        return redirect("/")
    import pytils
    seria_slug = pytils.translit.slugify((seria.sName or "").strip()).lower()
    address_slug = pytils.translit.slugify((building.sAddress or "").strip()).lower()
    # Новый формат: /price/seriaID<seria_id>--<seria_slug>/appartID<apart_id>/addressID<build_id>--<address_slug>/
    return redirect(f"/price/seriaID{seria.id}--{seria_slug}/appartID{apart_id}/addressID{build_id}--{address_slug}/", permanent=True)
    seria_slug = pytils.translit.slugify((seria.sName or "").strip()).lower()
    address_slug = pytils.translit.slugify((building.sAddress or "").strip()).lower()
    return redirect(f"/price/seriaID{seria.id}--{seria_slug}/appartID{apart_id}/addressID{build_id}--{address_slug}/", permanent=True)
