/**
 * Сервис парсинга отзывов с Яндекс Карт и 2ГИС
 */

// Опциональные зависимости для парсинга
let axios, puppeteer;
try {
  axios = require('axios');
  puppeteer = require('puppeteer');
} catch (err) {
  console.warn('[reviewParser] Модули axios и/или puppeteer не установлены. Парсинг будет недоступен.');
  console.warn('[reviewParser] Установите их: npm install axios puppeteer');
}

class ReviewParser {
  constructor(pool) {
    this.pool = pool;
    this.browser = null; // Будет инициализирован при первом использовании
  }

  /**
   * Получение или создание браузера Puppeteer
   * @returns {Promise<Browser>}
   */
  async getBrowser() {
    if (!puppeteer) {
      throw new Error('Puppeteer не установлен. Установите: npm install puppeteer');
    }
    
    if (!this.browser) {
      this.browser = await puppeteer.launch({
        headless: 'new', // Используем новый headless режим
        args: [
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--disable-dev-shm-usage',
          '--disable-accelerated-2d-canvas',
          '--disable-gpu',
          '--disable-blink-features=AutomationControlled', // Отключаем автоматизацию
          '--disable-features=IsolateOrigins,site-per-process',
          '--window-size=1920,1080'
        ],
        ignoreHTTPSErrors: true,
        defaultViewport: {
          width: 1920,
          height: 1080
        }
      });
    }
    return this.browser;
  }

  /**
   * Закрытие браузера
   */
  async closeBrowser() {
    if (this.browser) {
      await this.browser.close();
      this.browser = null;
    }
  }

  /**
   * Retry механизм для выполнения функции с повторными попытками
   * @param {Function} fn - функция для выполнения
   * @param {number} maxRetries - максимальное количество попыток
   * @param {number} delay - задержка между попытками (мс)
   * @returns {Promise<any>}
   */
  async retry(fn, maxRetries = 3, delay = 1000) {
    let lastError;
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn();
      } catch (err) {
        lastError = err;
        if (i < maxRetries - 1) {
          console.log(`[reviewParser] Попытка ${i + 1} не удалась, повтор через ${delay}мс...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          delay *= 2; // Экспоненциальная задержка
        }
      }
    }
    throw lastError;
  }

  /**
   * Парсинг отзывов с указанного источника
   * @param {string} source - 'yandex_maps' или '2gis'
   * @param {boolean} testMode - тестовый режим (не сохраняет в БД)
   * @returns {Promise<{parsed: number, errors: string[], found: number, valid: number, invalid: number}>}
   */
  async parseReviews(source, testMode = false) {
    const errors = [];
    let parsed = 0;
    let found = 0;
    let valid = 0;
    let invalid = 0;

    console.log(`[reviewParser] Начало парсинга отзывов из источника: ${source}`);

    // Проверяем наличие необходимых модулей
    if (!axios) {
      const errorMsg = 'Модуль axios не установлен. Установите: npm install axios';
      errors.push(errorMsg);
      console.error(`[reviewParser] ${errorMsg}`);
      return { parsed: 0, errors, found: 0, valid: 0, invalid: 0 };
    }

    if (!puppeteer) {
      const errorMsg = 'Модуль puppeteer не установлен. Установите: npm install puppeteer';
      errors.push(errorMsg);
      console.error(`[reviewParser] ${errorMsg}`);
      return { parsed: 0, errors, found: 0, valid: 0, invalid: 0 };
    }

    try {
      // Получаем настройки парсинга
      const client = await this.pool.connect();
      try {
        const settingsResult = await client.query(
          'SELECT * FROM parsing_settings WHERE source = $1 AND enabled = true',
          [source]
        );

        if (settingsResult.rows.length === 0) {
          throw new Error(`Настройки парсинга для ${source} не найдены или отключены`);
        }

        const settings = settingsResult.rows[0];
        console.log(`[reviewParser] Настройки найдены для ${source}, URL: ${settings.url_template || 'не указан'}`);

        if (!settings.url_template) {
          throw new Error(`URL шаблон не настроен для ${source}`);
        }

        // Парсим в зависимости от источника
        let reviews = [];
        if (source === 'yandex_maps') {
          reviews = await this.parseYandexMaps(settings);
        } else if (source === '2gis') {
          reviews = await this.parse2GIS(settings);
        } else {
          throw new Error(`Неподдерживаемый источник: ${source}`);
        }

        found = reviews.length;
        console.log(`[reviewParser] Найдено отзывов на странице: ${found}`);

        // Валидируем и фильтруем отзывы
        const validReviews = [];
        for (const review of reviews) {
          const validation = await this.validateReview(review, client);
          if (validation.valid) {
            validReviews.push(review);
            valid++;
          } else {
            invalid++;
            errors.push(`Невалидный отзыв: ${validation.error}`);
            console.warn(`[reviewParser] Пропущен невалидный отзыв: ${validation.error}`);
          }
        }

        console.log(`[reviewParser] Валидных отзывов: ${valid}, невалидных: ${invalid}`);

        // Сохраняем отзывы в БД (если не тестовый режим)
        if (!testMode && validReviews.length > 0) {
          for (const review of validReviews) {
            try {
              // Проверяем, не существует ли уже такой отзыв (по source_url или тексту)
              const existing = await client.query(
                `SELECT id FROM reviews 
                 WHERE source = $1 
                 AND (source_url = $2 OR (text = $3 AND date = $4))
                 LIMIT 1`,
                [source, review.source_url || null, review.text, review.date]
              );

              if (existing.rows.length === 0) {
                await client.query(
                  `INSERT INTO reviews (building_id, source, source_url, author_name, text, rating, date, status)
                   VALUES ($1, $2, $3, $4, $5, $6, $7, 'new')`,
                  [
                    review.building_id,
                    source,
                    review.source_url,
                    review.author_name,
                    review.text,
                    review.rating,
                    review.date
                  ]
                );
                parsed++;
                console.log(`[reviewParser] Сохранен отзыв от ${review.author_name || 'Аноним'}`);
              } else {
                console.log(`[reviewParser] Отзыв уже существует, пропущен`);
              }
            } catch (err) {
              errors.push(`Ошибка сохранения отзыва: ${err.message}`);
              console.error('[reviewParser] Error saving review:', err);
            }
          }

          // Обновляем время последнего парсинга
          await client.query(
            'UPDATE parsing_settings SET last_parsed_at = NOW() WHERE source = $1',
            [source]
          );
        } else {
          parsed = validReviews.length;
        }

        console.log(`[reviewParser] Парсинг завершен. Сохранено: ${parsed}, ошибок: ${errors.length}`);
        return { parsed, errors, found, valid, invalid };
      } finally {
        client.release();
      }
    } catch (err) {
      const errorMsg = `Ошибка парсинга: ${err.message}`;
      errors.push(errorMsg);
      console.error('[reviewParser] Error parsing reviews:', err);
      console.error('[reviewParser] Stack trace:', err.stack);
      return { parsed, errors, found, valid, invalid };
    }
  }

  /**
   * Парсинг отзывов с Яндекс Карт с использованием Puppeteer
   * @param {object} settings - настройки парсинга
   * @returns {Promise<Array>}
   */
  async parseYandexMaps(settings) {
    const reviews = [];

    try {
      console.log('[reviewParser] Начало парсинга Яндекс Карт с Puppeteer');
      
      // Если есть API ключ, используем API (TODO: реализовать)
      if (settings.api_key) {
        console.log('[reviewParser] Парсинг через Яндекс Карты API (не реализовано, используем web scraping)');
      }

      if (!settings.url_template) {
        console.warn('[reviewParser] URL шаблон не указан для Яндекс Карт');
        return reviews;
      }

      // Используем Puppeteer для парсинга динамического контента
      const browser = await this.getBrowser();
      const page = await browser.newPage();

      try {
        // Устанавливаем реалистичные заголовки для обхода защиты
        await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36');
        
        // Устанавливаем дополнительные заголовки
        await page.setExtraHTTPHeaders({
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
          'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
          'Accept-Encoding': 'gzip, deflate, br',
          'Connection': 'keep-alive',
          'Upgrade-Insecure-Requests': '1',
          'Sec-Fetch-Dest': 'document',
          'Sec-Fetch-Mode': 'navigate',
          'Sec-Fetch-Site': 'none',
          'Cache-Control': 'max-age=0'
        });
        
        // Убираем признаки автоматизации
        await page.evaluateOnNewDocument(() => {
          // Переопределяем webdriver
          Object.defineProperty(navigator, 'webdriver', {
            get: () => false,
          });
          
          // Добавляем chrome объект
          window.chrome = {
            runtime: {},
          };
          
          // Переопределяем permissions
          const originalQuery = window.navigator.permissions.query;
          window.navigator.permissions.query = (parameters) => (
            parameters.name === 'notifications' ?
              Promise.resolve({ state: Notification.permission }) :
              originalQuery(parameters)
          );
          
          // Добавляем плагины
          Object.defineProperty(navigator, 'plugins', {
            get: () => [1, 2, 3, 4, 5],
          });
          
          // Добавляем языки
          Object.defineProperty(navigator, 'languages', {
            get: () => ['ru-RU', 'ru', 'en-US', 'en'],
          });
        });
        
        console.log(`[reviewParser] Загрузка страницы: ${settings.url_template}`);
        
        // Загружаем страницу с ожиданием загрузки контента
        await this.retry(async () => {
          await page.goto(settings.url_template, {
            waitUntil: 'networkidle2',
            timeout: 60000
          });
          
          // Ждем немного для загрузки динамического контента
          await new Promise(resolve => setTimeout(resolve, 2000));
          
          // Проверяем, не появилась ли капча или страница с ошибкой
          const pageContent = await page.content();
          const captchaCheck = await page.evaluate(() => {
            const bodyText = document.body.textContent.toLowerCase();
            return bodyText.includes('robot') || 
                   bodyText.includes('капча') ||
                   bodyText.includes('not a robot') ||
                   bodyText.includes('автоматизированные запросы') ||
                   document.querySelector('[class*="captcha"]') !== null ||
                   document.querySelector('[id*="captcha"]') !== null ||
                   document.querySelector('[class*="smartcaptcha"]') !== null;
          });
          
          if (captchaCheck) {
            console.error('[reviewParser] Обнаружена капча Яндекс. Страница заблокирована защитой.');
            console.error('[reviewParser] Рекомендации:');
            console.error('[reviewParser] 1. Используйте прокси или VPN');
            console.error('[reviewParser] 2. Парсите вручную через браузер');
            console.error('[reviewParser] 3. Используйте API Яндекс Карт (требуется ключ)');
            throw new Error('Обнаружена капча Яндекс. Парсинг заблокирован защитой Яндекс Карт. Попробуйте использовать прокси или парсить вручную.');
          }
          
          // Проверяем, загрузилась ли страница с отзывами
          const hasReviews = await page.evaluate(() => {
            return document.querySelector('[class*="review"]') !== null ||
                   document.querySelector('[data-review-id]') !== null ||
                   document.body.textContent.includes('отзыв');
          });
          
          if (!hasReviews) {
            console.warn('[reviewParser] На странице не найдены отзывы. Возможно, их нет или страница не загрузилась полностью.');
          }
        }, 3, 3000);

        // Ждем загрузки отзывов - пробуем разные селекторы
        const selectors = [
          '[data-review-id]',
          '.business-reviews-card-view__review',
          '.review-item',
          '[class*="review"]',
          '[class*="Review"]'
        ];

        let reviewsLoaded = false;
        for (const selector of selectors) {
          try {
            await page.waitForSelector(selector, { timeout: 5000 });
            reviewsLoaded = true;
            console.log(`[reviewParser] Найден селектор отзывов: ${selector}`);
            break;
          } catch (err) {
            // Пробуем следующий селектор
          }
        }

        if (!reviewsLoaded) {
          console.warn('[reviewParser] Не удалось найти элементы отзывов, пробуем парсить страницу...');
          // Ждем немного для загрузки динамического контента
          await new Promise(resolve => setTimeout(resolve, 3000));
        }

        // Извлекаем отзывы из страницы
        const extractedReviews = await page.evaluate(() => {
          const reviews = [];
          
          // Пробуем разные селекторы для поиска отзывов
          const reviewSelectors = [
            '[data-review-id]',
            '.business-reviews-card-view__review',
            '.review-item',
            'article[class*="review"]',
            'div[class*="review"]'
          ];

          let reviewElements = [];
          for (const selector of reviewSelectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) {
              reviewElements = Array.from(elements);
              break;
            }
          }

          // Если не нашли через селекторы, ищем по структуре
          if (reviewElements.length === 0) {
            // Ищем элементы с ссылками на профили пользователей
            const userLinks = document.querySelectorAll('a[href*="/user/"], a[href*="/maps/user/"]');
            if (userLinks.length > 0) {
              userLinks.forEach(link => {
                const reviewContainer = link.closest('article, div[class*="review"], div[class*="card"]');
                if (reviewContainer && !reviewElements.includes(reviewContainer)) {
                  reviewElements.push(reviewContainer);
                }
              });
            }
          }

          reviewElements.forEach((elem, index) => {
            try {
              // Ищем автора
              let authorName = '';
              const authorLink = elem.querySelector('a[href*="/user/"], a[href*="/maps/user/"]');
              if (authorLink) {
                authorName = authorLink.textContent.trim();
              } else {
                const authorElem = elem.querySelector('strong, b, [class*="author"], [class*="name"], [class*="user"]');
                if (authorElem) {
                  authorName = authorElem.textContent.trim();
                }
              }

              // Ищем текст отзыва
              let text = '';
              const textSelectors = [
                '[class*="review-text"]',
                '[class*="text"]',
                'p',
                'div[class*="body"]',
                'div[class*="content"]'
              ];

              for (const selector of textSelectors) {
                const textElem = elem.querySelector(selector);
                if (textElem) {
                  const textContent = textElem.textContent.trim();
                  if (textContent.length > 20) {
                    text = textContent;
                    break;
                  }
                }
              }

              // Если не нашли через селекторы, берем весь текст и очищаем
              if (!text || text.length < 10) {
                text = elem.textContent.trim();
                // Удаляем имя автора и дату из начала
                if (authorName) {
                  text = text.replace(new RegExp(`^${authorName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?\\d{4}`, 'i'), '').trim();
                }
              }

              // Ищем рейтинг
              let rating = 5; // По умолчанию
              
              // Пробуем найти рейтинг в атрибутах
              const ratingElem = elem.querySelector('[data-rating], [data-stars], [class*="rating"], [class*="star"]');
              if (ratingElem) {
                const ratingAttr = ratingElem.getAttribute('data-rating') || ratingElem.getAttribute('data-stars');
                if (ratingAttr) {
                  rating = parseInt(ratingAttr);
                } else {
                  // Считаем заполненные звезды
                  const filledStars = ratingElem.querySelectorAll('[class*="filled"], [class*="active"], [class*="full"]');
                  if (filledStars.length > 0) {
                    rating = filledStars.length;
                  } else {
                    // Считаем все звезды
                    const stars = ratingElem.querySelectorAll('[class*="star"], [class*="Star"]');
                    if (stars.length > 0) {
                      rating = stars.length;
                    }
                  }
                }
              }

              // Пробуем найти рейтинг в тексте
              if (rating === 5) {
                const ratingMatch = elem.textContent.match(/(\d+)\s*(?:звезд|star|★|из\s*5)/i);
                if (ratingMatch) {
                  rating = parseInt(ratingMatch[1]);
                }
              }

              // Конвертируем рейтинг из 5-балльной в 10-балльную шкалу
              if (rating <= 5) {
                rating = rating * 2; // 1->2, 2->4, 3->6, 4->8, 5->10
              }

              // Ищем дату
              let dateText = '';
              const dateElem = elem.querySelector('[class*="date"], time, [datetime]');
              if (dateElem) {
                dateText = dateElem.getAttribute('datetime') || dateElem.textContent.trim();
              } else {
                // Пробуем найти дату в тексте
                const dateMatch = elem.textContent.match(/(\d{1,2}[.\s]+(?:[а-я]+|\d{1,2})[.\s]+\d{4}|[а-я]+ \d{1,2}, \d{4}|\d{4}-\d{2}-\d{2})/i);
                if (dateMatch) {
                  dateText = dateMatch[0];
                }
              }

              // Ищем ссылку на оригинальный отзыв
              let sourceUrl = '';
              const reviewLink = elem.querySelector('a[href*="/review/"], a[href*="/reviews/"]');
              if (reviewLink) {
                const href = reviewLink.getAttribute('href');
                if (href) {
                  sourceUrl = href.startsWith('http') ? href : `https://yandex.ru${href}`;
                }
              }

              if (text && text.length >= 10) {
                reviews.push({
                  author_name: authorName || 'Аноним',
                  text: text.substring(0, 5000),
                  rating: Math.max(1, Math.min(10, rating)),
                  date: dateText,
                  source_url: sourceUrl || settings.url_template
                });
              }
            } catch (err) {
              console.error('[reviewParser] Ошибка парсинга элемента отзыва:', err);
            }
          });

          return reviews;
        }, settings.url_template);

        // Парсим даты для каждого отзыва
        for (const review of extractedReviews) {
          review.date = this.parseDate(review.date);
          review.building_id = this.extractBuildingId(settings);
          reviews.push(review);
        }

        console.log(`[reviewParser] Всего распарсено отзывов с Яндекс Карт: ${reviews.length}`);
      } finally {
        await page.close();
      }
    } catch (err) {
      console.error('[reviewParser] Error parsing Yandex Maps:', err);
      throw err;
    }

    return reviews;
  }

  /**
   * Парсинг отзывов с 2ГИС с использованием Puppeteer
   * @param {object} settings - настройки парсинга
   * @returns {Promise<Array>}
   */
  async parse2GIS(settings) {
    const reviews = [];

    try {
      console.log('[reviewParser] Начало парсинга 2ГИС с Puppeteer');
      
      if (settings.api_key) {
        console.log('[reviewParser] Парсинг через 2ГИС API (не реализовано, используем web scraping)');
      }

      if (!settings.url_template) {
        console.warn('[reviewParser] URL шаблон не указан для 2ГИС');
        return reviews;
      }

      // Используем Puppeteer для парсинга динамического контента
      const browser = await this.getBrowser();
      const page = await browser.newPage();

      try {
        await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36');
        
        // Устанавливаем дополнительные заголовки для 2ГИС
        await page.setExtraHTTPHeaders({
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
          'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
          'Accept-Encoding': 'gzip, deflate, br',
          'Connection': 'keep-alive',
          'Upgrade-Insecure-Requests': '1'
        });
        
        // Убираем признаки автоматизации для 2ГИС
        await page.evaluateOnNewDocument(() => {
          Object.defineProperty(navigator, 'webdriver', {
            get: () => false,
          });
          window.chrome = { runtime: {} };
        });
        
        console.log(`[reviewParser] Загрузка страницы 2ГИС: ${settings.url_template}`);
        
        await this.retry(async () => {
          await page.goto(settings.url_template, {
            waitUntil: 'networkidle2',
            timeout: 60000
          });
          await new Promise(resolve => setTimeout(resolve, 3000));
        }, 3, 3000);

        // Ждём появления контента отзывов по тексту (2ГИС часто меняет классы)
        try {
          await page.waitForFunction(
            () => {
              const body = document.body && document.body.innerText ? document.body.innerText : '';
              return body.includes('Полезно') || body.includes('Читать целиком') || body.includes('официальный ответ');
            },
            { timeout: 15000 }
          );
          console.log('[reviewParser] Контент отзывов 2ГИС обнаружен на странице');
        } catch (e) {
          console.warn('[reviewParser] Таймаут ожидания контента отзывов 2ГИС, продолжаем...');
        }

        // Прокрутка для подгрузки ленивого контента
        await page.evaluate(() => {
          const scrollContainer = document.querySelector('[class*="scroll"], [class*="Scroll"], [class*="review"]') || document.documentElement;
          if (scrollContainer.scrollHeight > scrollContainer.clientHeight) {
            scrollContainer.scrollTop = scrollContainer.scrollHeight;
            scrollContainer.scrollTop = 0;
          }
        });
        await new Promise(resolve => setTimeout(resolve, 2000));

        // Селекторы для проверки наличия блоков отзывов (опционально)
        const selectors = [
          '[data-review-id]',
          '.review-item',
          '.reviews__item',
          '[class*="review"]',
          '[class*="Review"]',
          '[class*="feedback"]'
        ];
        let reviewsLoaded = false;
        for (const selector of selectors) {
          try {
            const count = await page.$$eval(selector, els => els.length);
            if (count > 0) {
              reviewsLoaded = true;
              console.log(`[reviewParser] Найден селектор отзывов 2ГИС: ${selector} (элементов: ${count})`);
              break;
            }
          } catch (err) {
            // ignore
          }
        }
        if (!reviewsLoaded) {
          console.warn('[reviewParser] Классы отзывов не найдены, парсинг по структуре (ссылки «Читать целиком»)');
        }

        // Извлекаем отзывы из страницы 2ГИС
        const baseUrl = settings.url_template;
        const extractedReviews = await page.evaluate((baseUrlForEval) => {
          const reviews = [];
          
          const reviewSelectors = [
            '[data-review-id]',
            '.review-item',
            '.reviews__item',
            'article[class*="review"]',
            'div[class*="review"]',
            'div[class*="feedback"]'
          ];

          let reviewElements = [];
          for (const selector of reviewSelectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) {
              reviewElements = Array.from(elements);
              break;
            }
          }

          // Fallback 1: любой элемент с текстом «Читать целиком» (не только <a>)
          if (reviewElements.length === 0) {
            const allNodes = document.querySelectorAll('a, button, span, div');
            const expandNodes = Array.from(allNodes).filter(el => {
              const t = (el.textContent || '').trim();
              return t === 'Читать целиком' || t === 'Читать полностью' || t.replace(/\s+/g, ' ').includes('Читать целиком');
            });
            const seen = new Set();
            expandNodes.forEach(node => {
              let block = node.closest('article') || node.closest('[class*="card"]') || node.closest('[class*="item"]') || node.parentElement;
              for (let i = 0; i < 20 && block && block !== document.body; i++) {
                const text = (block.textContent || '').trim();
                if (text.length > 120 && text.length < 50000 && !seen.has(block)) {
                  seen.add(block);
                  reviewElements.push(block);
                  break;
                }
                block = block.parentElement;
              }
            });
          }
          // Fallback 2: блоки по кнопке/счётчику «Полезно» (у каждого отзыва есть «Полезно» или «Полезно N»)
          if (reviewElements.length === 0) {
            const allEls = document.querySelectorAll('*');
            const usefulNodes = Array.from(allEls).filter(el => {
              const t = (el.textContent || '').trim();
              return (t === 'Полезно' || /^Полезно\s*\d*\s*$/.test(t)) && t.length < 25;
            });
            const seen = new Set();
            usefulNodes.forEach(node => {
              let block = node.parentElement;
              for (let i = 0; i < 25 && block && block !== document.body; i++) {
                const text = (block.textContent || '').trim();
                if (text.length > 200 && text.length < 50000 && !seen.has(block)) {
                  const lower = text.toLowerCase();
                  if (/[А-Яа-я]{3,}.*[А-Яа-я]{3,}/.test(text) && (lower.includes('полезно') || lower.includes('читать'))) {
                    seen.add(block);
                    reviewElements.push(block);
                    break;
                  }
                }
                block = block.parentElement;
              }
            });
          }

          reviewElements.forEach((elem) => {
            try {
              // Ищем автора
              let authorName = '';
              const authorSelectors = [
                '.review-item__author',
                '[class*="author"]',
                '[class*="user"]',
                'strong',
                'b'
              ];

              for (const selector of authorSelectors) {
                const authorElem = elem.querySelector(selector);
                if (authorElem) {
                  authorName = authorElem.textContent.trim();
                  if (authorName) break;
                }
              }
              // Fallback: первая строка блока часто — имя автора (2ГИС: «Имя Фамилия», «Аббревиатура Имя»)
              if (!authorName) {
                const fullText = elem.textContent.trim();
                const firstLine = fullText.split(/\n/)[0].trim();
                if (firstLine && firstLine.length < 80 && !/^\d+ отзыв/.test(firstLine) && !/^Уфа,|^[А-Яа-я]+, \d/.test(firstLine)) {
                  authorName = firstLine;
                }
              }

              // Ищем текст отзыва
              let text = '';
              const textSelectors = [
                '.review-item__text',
                '[class*="review-text"]',
                '[class*="text"]',
                'p',
                'div[class*="body"]',
                'div[class*="content"]'
              ];

              for (const selector of textSelectors) {
                const textElem = elem.querySelector(selector);
                if (textElem) {
                  const textContent = textElem.textContent.trim();
                  if (textContent.length > 20) {
                    text = textContent;
                    break;
                  }
                }
              }

              if (!text || text.length < 10) {
                text = elem.textContent.trim();
                if (authorName) {
                  text = text.replace(new RegExp(`^${authorName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?\\d{4}`, 'i'), '').trim();
                }
              }
              // Убираем из текста блок «официальный ответ» и служебные подписи 2ГИС
              const officialMatch = text.match(/\s*(?:\d{1,2}\s+\w+\s+\d{4},?\s+)?официальный ответ/i);
              if (officialMatch) {
                text = text.substring(0, text.indexOf(officialMatch[0])).trim();
              }
              text = text.replace(/\s*Читать целиком\s*/gi, ' ').replace(/\s*Полезно\s*\d*\s*/gi, ' ').replace(/\s*Посещен\w*\s*Отзыв подтверждён\s*/gi, ' ').trim();

              // Ищем рейтинг
              let rating = 5;
              const ratingElem = elem.querySelector('[data-rating], [class*="rating"], [class*="star"]');
              if (ratingElem) {
                const ratingAttr = ratingElem.getAttribute('data-rating');
                if (ratingAttr) {
                  rating = parseInt(ratingAttr);
                } else {
                  const filledStars = ratingElem.querySelectorAll('[class*="filled"], [class*="active"], [class*="full"]');
                  if (filledStars.length > 0) {
                    rating = filledStars.length;
                  }
                }
              }

              // Конвертируем рейтинг из 5-балльной в 10-балльную шкалу
              if (rating <= 5) {
                rating = rating * 2;
              }

              // Ищем дату
              let dateText = '';
              const dateElem = elem.querySelector('[class*="date"], time, [datetime]');
              if (dateElem) {
                dateText = dateElem.getAttribute('datetime') || dateElem.textContent.trim();
              } else {
                const dateMatch = elem.textContent.match(/(\d{1,2}[.\s]+(?:[а-я]+|\d{1,2})[.\s]+\d{4}|[а-я]+ \d{1,2}, \d{4}|\d{4}-\d{2}-\d{2})/i);
                if (dateMatch) {
                  dateText = dateMatch[0];
                }
              }

              // Ищем ссылку на оригинальный отзыв
              let sourceUrl = '';
              const reviewLink = elem.querySelector('a[href*="/review/"], a[href*="/reviews/"]');
              if (reviewLink) {
                const href = reviewLink.getAttribute('href');
                if (href) {
                  sourceUrl = href.startsWith('http') ? href : `https://2gis.ru${href}`;
                }
              }

              if (text && text.length >= 10) {
                reviews.push({
                  author_name: authorName || 'Аноним',
                  text: text.substring(0, 5000),
                  rating: Math.max(1, Math.min(10, rating)),
                  date: dateText,
                  source_url: sourceUrl || baseUrlForEval
                });
              }
            } catch (err) {
              // игнорируем ошибки по одному элементу
            }
          });

          const bodyText = document.body ? (document.body.innerText || '').substring(0, 800) : '';
          const iframes = document.querySelectorAll('iframe');
          return {
            reviews,
            debug: {
              reviewBlocksFound: reviewElements.length,
              bodySnippet: bodyText.replace(/\s+/g, ' ').trim().substring(0, 400),
              hasPolezno: bodyText.includes('Полезно'),
              hasChitat: bodyText.includes('Читать'),
              iframeCount: iframes.length
            }
          };
        }, baseUrl);

        const extractedList = Array.isArray(extractedReviews) ? extractedReviews : (extractedReviews && extractedReviews.reviews) || [];
        const debugInfo = extractedReviews && extractedReviews.debug;
        if (extractedList.length === 0 && debugInfo) {
          console.log('[reviewParser] 2ГИС отладка: блоков:', debugInfo.reviewBlocksFound, ', «Полезно»:', debugInfo.hasPolezno, ', «Читать»:', debugInfo.hasChitat, ', iframe:', debugInfo.iframeCount || 0);
          if (debugInfo.bodySnippet) {
            console.log('[reviewParser] 2ГИС фрагмент:', debugInfo.bodySnippet);
          }
        }

        // Парсим даты для каждого отзыва
        for (const review of extractedList) {
          review.date = this.parseDate(review.date);
          review.building_id = this.extractBuildingId(settings);
          reviews.push(review);
        }

        console.log(`[reviewParser] Всего распарсено отзывов с 2ГИС: ${reviews.length}`);
      } finally {
        await page.close();
      }
    } catch (err) {
      console.error('[reviewParser] Error parsing 2GIS:', err);
      throw err;
    }

    return reviews;
  }

  /**
   * Парсинг даты из текста
   * @param {string} dateText - текст с датой
   * @returns {string} - дата в формате YYYY-MM-DD
   */
  parseDate(dateText) {
    if (!dateText) {
      return new Date().toISOString().split('T')[0];
    }

    // Пытаемся распарсить различные форматы дат
    const today = new Date();
    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);

    // "Сегодня" -> сегодняшняя дата
    if (dateText.toLowerCase().includes('сегодня')) {
      return today.toISOString().split('T')[0];
    }

    // "Вчера" -> вчерашняя дата
    if (dateText.toLowerCase().includes('вчера')) {
      return yesterday.toISOString().split('T')[0];
    }

    // Пытаемся распарсить формат "Month DD, YYYY" (например, "July 15, 2025")
    const englishDateMatch = dateText.match(/([a-z]+)\s+(\d{1,2}),\s+(\d{4})/i);
    if (englishDateMatch) {
      const monthNames = {
        'january': 1, 'february': 2, 'march': 3, 'april': 4,
        'may': 5, 'june': 6, 'july': 7, 'august': 8,
        'september': 9, 'october': 10, 'november': 11, 'december': 12,
        'января': 1, 'февраля': 2, 'марта': 3, 'апреля': 4,
        'мая': 5, 'июня': 6, 'июля': 7, 'августа': 8,
        'сентября': 9, 'октября': 10, 'ноября': 11, 'декабря': 12
      };
      const monthName = englishDateMatch[1].toLowerCase();
      const month = monthNames[monthName];
      const day = parseInt(englishDateMatch[2]);
      const year = parseInt(englishDateMatch[3]);
      
      if (month && day && year) {
        return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
      }
    }

    // Пытаемся распарсить дату в формате "DD.MM.YYYY" или "DD MMM YYYY"
    const dateMatch = dateText.match(/(\d{1,2})[.\s]+(\d{1,2}|[а-я]+)[.\s]+(\d{4})/i);
    if (dateMatch) {
      const day = parseInt(dateMatch[1]);
      const month = parseInt(dateMatch[2]) || this.monthNameToNumber(dateMatch[2]);
      const year = parseInt(dateMatch[3]);
      
      if (month && day && year) {
        return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
      }
    }

    // Пытаемся использовать встроенный парсер дат
    try {
      const parsedDate = new Date(dateText);
      if (!isNaN(parsedDate.getTime())) {
        return parsedDate.toISOString().split('T')[0];
      }
    } catch (err) {
      // Игнорируем ошибки парсинга
    }

    // По умолчанию возвращаем сегодняшнюю дату
    return today.toISOString().split('T')[0];
  }

  /**
   * Преобразование названия месяца в число
   */
  monthNameToNumber(monthName) {
    const months = {
      'января': 1, 'февраля': 2, 'марта': 3, 'апреля': 4,
      'мая': 5, 'июня': 6, 'июля': 7, 'августа': 8,
      'сентября': 9, 'октября': 10, 'ноября': 11, 'декабря': 12
    };
    return months[monthName.toLowerCase()] || null;
  }

  /**
   * Извлечение building_id из настроек или URL
   * @param {object} settings - настройки парсинга
   * @returns {string|null} - building_id или null
   */
  extractBuildingId(settings) {
    // Сначала проверяем, есть ли building_id в настройках (в поле settings JSONB)
    if (settings.settings) {
      let settingsObj = settings.settings;
      
      // Если settings - это строка JSON, парсим её
      if (typeof settingsObj === 'string') {
        try {
          settingsObj = JSON.parse(settingsObj);
        } catch (err) {
          console.warn('[reviewParser] Ошибка парсинга settings JSON:', err);
          settingsObj = {};
        }
      }
      
      // Если settings - это объект, проверяем building_id
      if (typeof settingsObj === 'object' && settingsObj !== null) {
        const buildingId = settingsObj.building_id;
        if (buildingId) {
          console.log(`[reviewParser] Building ID из настроек: ${buildingId}`);
          return buildingId;
        }
      }
    }

    // Пытаемся извлечь из URL (если есть параметр building_id)
    if (settings.url_template) {
      try {
        const url = new URL(settings.url_template);
        const buildingIdParam = url.searchParams.get('building_id');
        if (buildingIdParam) {
          console.log(`[reviewParser] Building ID из URL параметра: ${buildingIdParam}`);
          return buildingIdParam;
        }
      } catch (err) {
        // URL может быть относительным или некорректным
        console.warn('[reviewParser] Не удалось распарсить URL:', err);
      }
    }

    // Если ничего не найдено, возвращаем null
    console.warn('[reviewParser] Building ID не найден в настройках или URL. Отзывы будут сохранены без building_id.');
    return null;
  }

  /**
   * Валидация отзыва перед сохранением
   * @param {object} review - объект отзыва
   * @param {object} client - клиент БД для проверки building_id
   * @returns {object} - {valid: boolean, error?: string}
   */
  async validateReview(review, client) {
    // Проверяем наличие обязательных полей
    if (!review.text || typeof review.text !== 'string') {
      return { valid: false, error: 'Текст отзыва отсутствует или неверного типа' };
    }

    // Проверяем минимальную длину текста
    if (review.text.trim().length < 10) {
      return { valid: false, error: 'Текст отзыва слишком короткий (минимум 10 символов)' };
    }

    // Проверяем максимальную длину текста
    if (review.text.length > 10000) {
      return { valid: false, error: 'Текст отзыва слишком длинный (максимум 10000 символов)' };
    }

    // Проверяем рейтинг (теперь 1-10 вместо 1-5)
    if (review.rating === undefined || review.rating === null) {
      return { valid: false, error: 'Рейтинг отсутствует' };
    }

    const rating = parseInt(review.rating);
    if (isNaN(rating) || rating < 1 || rating > 10) {
      return { valid: false, error: `Рейтинг должен быть числом от 1 до 10, получено: ${review.rating}` };
    }

    // Проверяем дату
    if (!review.date) {
      return { valid: false, error: 'Дата отзыва отсутствует' };
    }

    // Проверяем формат даты (YYYY-MM-DD)
    const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
    if (!dateRegex.test(review.date)) {
      return { valid: false, error: `Неверный формат даты: ${review.date}, ожидается YYYY-MM-DD` };
    }

    // Проверяем, что building_id существует в БД (если указан)
    if (review.building_id) {
      try {
        const buildingCheck = await client.query(
          'SELECT id FROM buildings WHERE id = $1',
          [review.building_id]
        );
        if (buildingCheck.rows.length === 0) {
          return { valid: false, error: `Building ID ${review.building_id} не найден в БД` };
        }
      } catch (err) {
        // Если ошибка при проверке, не блокируем сохранение, но логируем
        console.warn(`[reviewParser] Не удалось проверить building_id ${review.building_id}:`, err);
      }
    }

    return { valid: true };
  }
}

// Экспортируем функцию для создания парсера с pool
function createReviewParser(pool) {
  return new ReviewParser(pool);
}

// Экспортируем класс для использования в server.js
createReviewParser.ReviewParser = ReviewParser;

module.exports = createReviewParser;
