# -*- coding: utf-8 -*-
"""
Каталог производителей и компаний.

Модуль предоставляет views для отображения:
1. Списка всех производителей с их ключевыми показателями (рейтинг, количество
   предложений, среднюю цену и т.п.)
2. Детальную информацию о конкретном производителе со всеми его оконными наборами

Все запросы переведены на Django ORM для лучшей производительности и чистоты кода.
"""
from django.shortcuts import render, redirect
from django.http import HttpRequest, HttpResponse, Http404
from django.db.models import Count, Avg, Max, Min, DecimalField
from oknardia.models import (
    MerchantBrand,
    SetKit,
    PriceOffer,
)
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 get_rating_set_for_stars
import django.utils.dateformat
import time
import random
import re
import pytils


def _get_company_statistics() -> list:
    """
    Получает список компаний (MerchantBrand) с агрегированной статистикой.

    Статистика включает:
    - Количество оконных наборов от компании
    - Средний рейтинг наборов
    - Количество ценовых предложений
    - Среднюю цену предложений
    - Дату последнего обновления цены

    Оптимизировано для минимизации запросов к БД.

    Returns:
        list: Список словарей с данными компаний
    """
    # 1. Статистика по наборам (SetKit) для каждой компании
    set_stats = (
        SetKit.objects
        .filter(kSet2User__kMerchantOffice__kMerchantName__isnull=False)
        .values('kSet2User__kMerchantOffice__kMerchantName_id')
        .annotate(
            num_sets=Count('id', distinct=True),
            avg_rating=Avg('fSetRating')
        )
    )
    set_stats_dict = {
        stat['kSet2User__kMerchantOffice__kMerchantName_id']: {
            'num_sets': stat['num_sets'],
            'avg_rating': stat['avg_rating'] or 0
        }
        for stat in set_stats
    }

    # 2. Статистика по ценовым предложениям (PriceOffer)
    companies_data = (
        PriceOffer.objects
        .filter(
            sOfferActive=True,
            kOfferFromUser__kMerchantOffice__kMerchantName__isnull=False
        )
        .values('kOfferFromUser__kMerchantOffice__kMerchantName_id')
        .annotate(
            num_offers=Count('id', distinct=True),
            price_avg=Avg('fOfferPrice', output_field=DecimalField()),
            last_update=Max('dOfferModify')
        )
        .order_by('-last_update')
    )

    # 3. Получаем все объекты MerchantBrand одним запросом (решение проблемы N+1)
    company_ids = [
        offer['kOfferFromUser__kMerchantOffice__kMerchantName_id']
        for offer in companies_data
    ]
    merchants = MerchantBrand.objects.in_bulk(company_ids)

    # 4. Собираем финальный результат
    result = []
    for offer in companies_data:
        company_id = offer['kOfferFromUser__kMerchantOffice__kMerchantName_id']
        merchant = merchants.get(company_id)

        if not merchant:
            continue

        set_stat = set_stats_dict.get(company_id, {
            'num_sets': 0,
            'avg_rating': 0
        })

        result.append({
            'id': merchant.id,
            'sMerchantName': merchant.sMerchantName,
            'pMerchantLogo': merchant.pMerchantLogo,
            'NumSets': set_stat['num_sets'],
            'RatingAVG': set_stat['avg_rating'],
            'NumOffers': offer['num_offers'],
            'PriceAVG': offer['price_avg'],
            'lastUpdate': offer['last_update']
        })

    # Сортируем по среднему рейтингу (убывание)
    result.sort(key=lambda x: x['RatingAVG'], reverse=True)

    return result


def _format_company_for_template(company_data: dict) -> dict:
    """
    Форматирует данные компании для вывода в шаблон.

    Применяет:
    - Конвертацию времени в читаемый формат (e.g., "3 дня назад")
    - Склонение существительных (plural forms)
    - Вычисление звёзд рейтинга
    - Скатывание имени в slug для URL

    Args:
        company_data (dict): Словарь с данными компании

    Returns:
        dict: Отформатированные данные компании
    """
    formatted = company_data.copy()

    # Вычисляем звёзды на основе рейтинга
    formatted['STARS'] = get_rating_set_for_stars(
        formatted['RatingAVG']
    )

    # Применяем правильные формы множественного числа
    formatted['NumSets'] = pytils.numeral.get_plural(
        formatted['NumSets'],
        "оконный набор, оконных набора, оконных наборов"
    )
    formatted['NumOffers'] = pytils.numeral.get_plural(
        formatted['NumOffers'],
        "вариант, варианта, вариантов"
    )

    # Конвертируем время последнего обновления в читаемый формат
    if formatted['lastUpdate']:
        timestamp = int(
            django.utils.dateformat.format(
                formatted['lastUpdate'],
                'U'
            )
        )
        formatted['lastUpdate'] = pytils.dt.distance_of_time_in_words(
            timestamp
        )

    # Генерируем slug из имени компании для URL
    formatted['sMerchantMainURL'] = pytils.translit.slugify(
        formatted['sMerchantName']
    )

    return formatted


def catalog_company(request: HttpRequest) -> HttpResponse:
    """
    Показывает список всех производителей с ключевыми показателями.

    GET параметры: опционально могут использоваться для фильтрации

    Контекст шаблона:
        - COMPANIES (list): Список компаний с статистикой
        - LAST_VISIT (list): Последние визиты текущего пользователя
        - LOG_VISIT (list): Последние визиты всех пользователей

    Args:
        request (HttpRequest): HTTP запрос от клиента

    Returns:
        HttpResponse: Отрендеренная HTML страница со списком компаний
    """
    # Получаем статистику по компаниям с использованием ORM
    companies_list = _get_company_statistics()

    # Форматируем каждую компанию для вывода в шаблон
    formatted_companies = [
        _format_company_for_template(company)
        for company in companies_list
    ]

    # Получаем информацию о посещениях для персонализации
    to_template: dict[str, object] = {
        'COMPANIES': formatted_companies,
        'LAST_VISIT': get_last_user_visit_list(
            get_last_user_visit_cookies(request)[:3]
        ),
        'LOG_VISIT': get_last_all_user_visit_list(),
    }

    return render(request, "catalog/catalog_company.html", to_template)


def _lowercase_first_char(text: str) -> str:
    """
    Преобразует первый символ строки в нижний регистр.

    Args:
        text (str): Исходная строка

    Returns:
        str: Строка с строчным первым символом (если длина > 0)
    """
    return text[0].lower() + text[1:] if len(text) > 0 else text


def _clean_text_field(text: str, empty_values: list) -> str:
    """
    Очищает текстовое поле, удаляя типичные маркеры "пусто" и преобразуя
    первый символ в нижний регистр.

    Args:
        text (str): Исходный текст
        empty_values (list): Список значений, которые считаются "пустыми"

    Returns:
        str: Очищенный текст или пустая строка если значение в empty_values
    """
    if text.lower() in empty_values:
        return ""
    return _lowercase_first_char(text)


def _get_company_sets_detail(company_id: int) -> list:
    """
    Получает все оконные наборы для компании с полной статистикой по ценам.

    Использует оптимизированные select_related и prefetch_related для минимизации
    запросов к БД. Группирует данные по наборам (SetKit) с уникальностью.

    Args:
        company_id (int): ID компании (MerchantBrand)

    Returns:
        list: Список словарей с данными наборов, отсортированные по рейтингу
    """
    # Получаем активные ценовые предложения для компаний с агрегацией по наборам
    price_stats = (
        PriceOffer.objects
        .filter(
            sOfferActive=True,
            kOfferFromUser__kMerchantOffice__kMerchantName_id=company_id
        )
        .values('kOffer2SetKit_id')
        .annotate(
            num_offers=Count('id'),
            price_avg=Avg('fOfferPrice', output_field=DecimalField()),
            last_update=Max('dOfferModify'),
            early_creation=Min('dOfferCreate')
        )
    )

    # Преобразуем в словарь для быстрого доступа по ID набора
    price_stats_dict = {
        stat['kOffer2SetKit_id']: {
            'num_offers': stat['num_offers'],
            'price_avg': stat['price_avg'],
            'last_update': stat['last_update'],
            'early_creation': stat['early_creation']
        }
        for stat in price_stats
    }

    # Получаем все наборы компании с их зависимостями
    # select_related оптимизирует ForeignKey запросы (профиль, стеклопакет)
    sets_queryset = (
        SetKit.objects
        .filter(
            kSet2User__kMerchantOffice__kMerchantName_id=company_id
        )
        .select_related(
            'kSet2User',
            'kSet2User__kMerchantOffice',
            'kSet2User__kMerchantOffice__kMerchantName',
            'kSet2PVCprofiles',
            'kSet2Glazing'
        )
        .order_by('-fSetRating')
    )

    # Собираем результат, комбинируя данные SetKit с агрегированной статистикой
    result = []
    seen_set_ids = set()

    for setkit in sets_queryset:
        # Пропускаем дубликаты наборов (может быть несколько ценовых предложений
        # для одного набора)
        if setkit.id in seen_set_ids:
            continue
        seen_set_ids.add(setkit.id)

        # Получаем статистику по ценам для этого набора
        price_stat = price_stats_dict.get(setkit.id, {
            'num_offers': 0,
            'price_avg': None,
            'last_update': None,
            'early_creation': None
        })

        # Собираем все данные в один объект
        result.append({
            'setkit': setkit,
            'num_offers': price_stat['num_offers'],
            'price_avg': price_stat['price_avg'],
            'last_update': price_stat['last_update'],
            'early_creation': price_stat['early_creation'],
            'merchant_office': setkit.kSet2User.kMerchantOffice,
            'merchant_brand': setkit.kSet2User.kMerchantOffice.kMerchantName,
            'profile': setkit.kSet2PVCprofiles,
            'glazing': setkit.kSet2Glazing
        })

    return result


def _format_set_for_template(set_data: dict, empty_values: list) -> dict:
    """
    Форматирует данные оконного набора для вывода в шаблон.

    Применяет:
    - Преобразование URL в удобный для отображения формат
    - Разделение email адресов на части (для обфускации)
    - Вычисление звёзд рейтинга
    - Конвертация времени в читаемый формат
    - Создание slugs для названий и производителей
    - Склонение числительных(контуры, швы и т.п.)
    - Очистку пустых полей от стандартных маркеров ("нет", "—" и т.п.)

    Args:
        set_data (dict): Данные набора с объектами моделей
        empty_values (list): Список значений, считаемых "пустыми"

    Returns:
        dict: Отформатированные данные для шаблона
    """
    set_kit = set_data['setkit']
    merchant_office = set_data['merchant_office']
    merchant_brand = set_data['merchant_brand']
    profile = set_data['profile']
    glazing = set_data['glazing']

    formatted = {
        # Ключи ниже оставлены в legacy-формате, т.к. шаблон использует именно их имена.
        'sSetName': set_kit.sSetName,
        'sMerchantName': merchant_brand.sMerchantName,
        'sMerchantDescription': merchant_brand.sMerchantDescription,
        'fSetRating': {
            'RATING': set_kit.fSetRating,
            'STARS': get_rating_set_for_stars(set_kit.fSetRating)
        },
        'num_offers': set_data['num_offers'],
        'price_avg': set_data['price_avg'],
        'bSetDelivery': set_kit.bSetDelivery,
        'bSetUninstallInstall': set_kit.bSetUninstallInstall,
        'sSetImplementAll': set_kit.sSetImplementAll,
        'sSetImplementHandles': set_kit.sSetImplementHandles,
        'sMerchantMainURL': {
            'URL': merchant_office.kMerchantName.sMerchantMainURL,
            'URL_VIEW': re.sub(
                r"^https?://|/$|www\.",
                "",
                merchant_office.kMerchantName.sMerchantMainURL
            )
        },
        'sOfficePhones': merchant_office.sOfficePhones,
        'sOfficeDescription': merchant_office.sOfficeDescription,
        'sOfficeEmails': merchant_office.sOfficeEmails,
        'sOfficeName': merchant_office.sOfficeName,
        'sOfficeAddress': merchant_office.sOfficeAddress,
        'fOfficeGeoCode_Latitude': merchant_office.fOfficeGeoCode_Latitude,
        'fOfficeGeoCode_Longitude': merchant_office.fOfficeGeoCode_Longitude,
        'sOfficeDiscountMetaFormula': merchant_office.sOfficeDiscountMetaFormula,
        'pMerchantLogo': merchant_office.kMerchantName.pMerchantLogo,
        'idPVC': profile.id,
        'sProfileBriefDescription': profile.sProfileBriefDescription,
        'iProfileCameras': profile.iProfileCameras,
        'sProfileName': {
            'NAME': profile.sProfileName,
            'NAME_T': pytils.translit.slugify(profile.sProfileName)
        },
        'sProfileManufacturer': {
            'NAME': profile.sProfileManufacturer,
            'NAME_T': pytils.translit.slugify(profile.sProfileManufacturer)
        },
        'sProfileColor': profile.sProfileColor,
        'sProfileSealDescription': profile.sProfileSealDescription,
        'fProfileSeals': pytils.numeral.sum_string(
            profile.fProfileSeals,
            pytils.numeral.MALE,
            "контур, контура, контуров"
        ),
        'sGlazingBriefDescription': glazing.sGlazingBriefDescription,
        'sGlazingManufacturer': glazing.sGlazingManufacturer,
        'sGlazingMark': glazing.sGlazingMark,
        'sGlazingToning': glazing.sGlazingToning,
        'sSetImplementCatch': _clean_text_field(set_kit.sSetImplementCatch, empty_values),
        'sSetClimateControl': _clean_text_field(set_kit.sSetClimateControl, empty_values),
        'sProfileReinforcement': _lowercase_first_char(profile.sProfileReinforcement),
        'sSetSill': _lowercase_first_char(set_kit.sSetSill),
        'sSetPanes': _lowercase_first_char(set_kit.sSetPanes),
        'sSetSlope': _lowercase_first_char(set_kit.sSetSlope),
        'sSetUninstallInstall': _lowercase_first_char(set_kit.sSetUninstallInstall),
        'sSetDelivery': _lowercase_first_char(set_kit.sSetDelivery),
        'sSetOtherConditions': _lowercase_first_char(set_kit.sSetOtherConditions),
    }

    # Конвертируем даты в читаемый формат
    if set_data['last_update']:
        timestamp = int(django.utils.dateformat.format(set_data['last_update'], 'U'))
        formatted['lastUpdate'] = pytils.dt.distance_of_time_in_words(timestamp)

    if set_data['early_creation']:
        timestamp = int(django.utils.dateformat.format(set_data['early_creation'],'U'))
        formatted['earlyCreation'] = pytils.dt.distance_of_time_in_words(timestamp)

    # Разделяем email на части для обфускации (показываем середину отдельно)
    # На фронтенде JS собирает все обратно в валидный e-mail
    if formatted['sOfficeEmails']:
        try:
            email_len = len(formatted['sOfficeEmails'])
            k = random.randint(1, max(1, int(email_len / 2) - 1))
            formatted['sOfficeEmails'] = [
                formatted['sOfficeEmails'][0:k],
                formatted['sOfficeEmails'][k:-k],
                formatted['sOfficeEmails'][-k:]
            ]
        except (ValueError, ZeroDivisionError):
            # Если ошибка при случайном разделении, оставляем как есть
            pass

    return formatted


def catalog_company_detail(
    request: HttpRequest,
    company_id: str,
    company_name_slug: str
) -> HttpResponse:
    """
    Показывает детальную информацию о компании и все её оконные наборы.

    Производит редирект если slug в URL не совпадает с актуальным.

    GET параметры: опционально могут использоваться для фильтрации

    Контекст шаблона:
        - COMPANY (str): Название компании
        - COMPANY_ID (int): ID компании
        - COMPANY_T (str): Slug компании
        - SETS (list): Список оконных наборов с их полной информацией
        - IMG_FOR_BLOG (str): Логотип компании
        - LIST_NOT (list): Стандартные маркеры "пусто"
        - LAST_VISIT (list): Последние визиты текущего пользователя
        - LOG_VISIT (list): Последние визиты всех пользователей
        - ticks (float): Время выполнения представления (в секундах)

    Args:
        request (HttpRequest): HTTP запрос от клиента
        company_id (str): ID компании в виде строки
        company_name_slug (str): Slug названия компании из URL

    Returns:
        HttpResponse: Отрендеренная HTML страница с деталью компании или редирект
    """
    time_start = time.perf_counter()
    company_id_int = int(company_id)

    # Получаем компанию или возвращаем 404
    try:
        company = MerchantBrand.objects.get(id=company_id_int)
    except MerchantBrand.DoesNotExist:
        raise Http404("Компания не найдена")

    # Проверяем что slug совпадает (для SEO и красивых URL)
    actual_slug = pytils.translit.slugify(company.sMerchantName)
    if actual_slug != company_name_slug:
        return redirect(
            f'/catalog/company/{company_id_int}-{actual_slug}'
        )

    # Типичные маркеры, которые означают что поле пусто
    empty_values = ["нет", "—", ""]

    # Получаем все наборы компании с ценовой статистикой
    sets_list = _get_company_sets_detail(company_id_int)

    # Форматируем каждый набор для вывода в шаблон
    formatted_sets = [
        _format_set_for_template(set_data, empty_values)
        for set_data in sets_list
    ]

    to_template: dict[str, object] = {
        'COMPANY': company.sMerchantName,
        'COMPANY_ID': company_id_int,
        'COMPANY_T': company_name_slug,
        'SETS': formatted_sets,
        'HEADER': f'Изготовитель окон «{company.sMerchantName}»',
        'META_KEYWORDS': company.sMerchantName,
        'IMG_FOR_BLOG': company.pMerchantLogo,
        'LIST_NOT': empty_values,
        'LAST_VISIT': get_last_user_visit_list(
            get_last_user_visit_cookies(request)[:3]
        ),
        'LOG_VISIT': get_last_all_user_visit_list(),
    }

    # Добавляем метрику выполнения представления
    to_template['ticks'] = float(time.perf_counter() - time_start)

    return render(request, "catalog/catalog_company_detail.html", to_template)
