require('dotenv').config();

// На Windows включаем UTF-8 в консоли, чтобы русский не превращался в кракозябры
if (process.platform === 'win32' && process.stdout.isTTY) {
  try {
    require('child_process').execSync('chcp 65001 >nul', { stdio: 'ignore', windowsHide: true });
  } catch (_) {}
}

console.log('[price_searcher] Запуск...');

const { createLogger } = require('./log');
const { getConfigFromEnv, parseCliArgs } = require('./runtime/config');
const { fetchRowsFromServer, sendUpdateToServer } = require('./sheets-remote');
const { createBrowserAndContext } = require('./runtime/browser');
const { detectMarketplace, getMarketplace } = require('./marketplaces');
const { extractPriceNumber } = require('./utils/price');

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/** Текущее время в московском часовом поясе (YYYY-MM-DD HH:mm:ss). */
function nowMoscow() {
  const d = new Date();
  return d.toLocaleString('sv-SE', { timeZone: 'Europe/Moscow' }).replace(' ', ' ');
}

function withTimeout(promise, ms, message) {
  let tid;
  const timer = new Promise((_, reject) => {
    tid = setTimeout(() => reject(new Error(message || `Timeout after ${ms}ms`)), ms);
  });
  return Promise.race([promise, timer]).then((v) => {
    clearTimeout(tid);
    return v;
  });
}

const SHEET_ID_PLACEHOLDER = 'PASTE_GOOGLE_SHEET_ID_HERE';

async function runOnce(cfg, log) {
  if (!cfg.sheetId || cfg.sheetId === SHEET_ID_PLACEHOLDER) {
    throw new Error(
      'В настройках не указана ссылка на Google Таблицу. Укажите её в интерфейсе и сохраните.'
    );
  }

  if (!cfg.accessServerUrl) {
    throw new Error(
      'Задайте ACCESS_SERVER_URL в .env (адрес облачного сервера, например https://mp-tech-price.ru). Ключ Google только на сервере.'
    );
  }

  const browserChoice = cfg.browserChoice;
  if (browserChoice && !cfg.userDataDir) {
    console.log(
      `[price_searcher] Браузер "${browserChoice}" выбран, но путь к профилю не задан — используется встроенный Chromium (без авторизации WB).`
    );
  }

  const googleTimeoutMs = 60000;
  const getVal = (r, col) => r[col];
  const saveRowData = async (r, data, dryRun) => {
    if (dryRun) return;
    await sendUpdateToServer(cfg.accessServerUrl, cfg.sheetId, cfg.sheetTabTitle, r.rowIndex, {
      Price: data.Price,
      PriceClub: data.PriceClub,
      UpdatedAt: data.UpdatedAt
    });
  };

  console.log('[price_searcher] Загрузка строк с сервера...');
  log.info('Загрузка строк с сервера (ключ на сервере)...');
  const rows = await withTimeout(
    fetchRowsFromServer(cfg.accessServerUrl, cfg.sheetId, cfg.sheetTabTitle, {
      offset: cfg.offset,
      limit: cfg.limit,
      sortByColumn: cfg.colUpdatedAt,
      sortOrder: 'asc'
    }),
    googleTimeoutMs,
    `Сервер не ответил за ${googleTimeoutMs / 1000} с. Проверьте интернет и ACCESS_SERVER_URL.`
  );
  log.info({ count: rows.length }, 'Loaded rows');

  console.log('[price_searcher] Запуск браузера...');
  log.info('Запуск браузера...');
  const { browser, context } = await createBrowserAndContext({
    userDataDir: cfg.userDataDir,
    browserCdpUrl: cfg.browserCdpUrl,
    headless: cfg.headless,
    slowMoMs: cfg.slowMoMs
  });
  if (cfg.userDataDir) {
    log.info(
      { browser: cfg.browserChoice || 'профиль из USER_DATA_DIR' },
      'Используется профиль браузера, сессия и авторизация сохранены'
    );
  } else if (cfg.browserCdpUrl) {
    log.info('Используется подключение к браузеру по CDP');
  }

  const page = await context.newPage();
  page.setDefaultNavigationTimeout(cfg.navTimeoutMs);
  page.setDefaultTimeout(cfg.priceTimeoutMs);

  if (cfg.delayBetweenUrlsMs > 0) {
    await sleep(Math.min(cfg.delayBetweenUrlsMs, 2000));
  }

  for (const [idx, row] of rows.entries()) {
    const url = String(getVal(row, cfg.colLink) || '').trim();
    if (!url) {
      await saveRowData(row, { Price: '', PriceClub: '', UpdatedAt: nowMoscow() }, cfg.dryRun);
      continue;
    }

    const mpId = detectMarketplace(url);
    if (cfg.onlyMarket && cfg.onlyMarket !== mpId) {
      await saveRowData(row, { Price: '', PriceClub: '', UpdatedAt: nowMoscow() }, cfg.dryRun);
      continue;
    }

    const mp = getMarketplace(mpId);
    if (!mp) {
      log.warn({ url, mpId }, 'Unknown marketplace');
      await saveRowData(row, { Price: '', PriceClub: '', UpdatedAt: nowMoscow() }, cfg.dryRun);
      continue;
    }

    log.info({ i: idx + 1, total: rows.length, mp: mp.id, url }, 'Processing');

    if (idx > 0 && cfg.delayBetweenUrlsMs > 0) {
      await sleep(cfg.delayBetweenUrlsMs);
    }

    try {
      await page.goto(url, { waitUntil: 'domcontentloaded' });

      // Some pages render price after async calls; give them a tiny moment.
      await page.waitForLoadState('networkidle', { timeout: Math.min(15000, cfg.navTimeoutMs) }).catch(() => {});

      if (mpId === 'wildberries') {
        await sleep(2000);
      }

      let price = null;
      let priceClub = null;

      if (mpId === 'wildberries' && typeof mp.getPrices === 'function') {
        const { priceText, clubPriceText } = await mp.getPrices({
          page,
          selectorPrice: cfg.wbSelectorPrice,
          selectorClub: cfg.wbSelectorClub,
          timeoutMs: cfg.priceTimeoutMs
        });
        price = extractPriceNumber(priceText);
        priceClub = extractPriceNumber(clubPriceText);
      } else {
        const rawText = await mp.getRawPriceText({ page, url, timeoutMs: cfg.priceTimeoutMs });
        price = extractPriceNumber(rawText);
      }

      await saveRowData(row, {
        Price: price ?? '',
        PriceClub: priceClub ?? '',
        UpdatedAt: nowMoscow()
      }, cfg.dryRun);
      log.info({ price, priceClub }, 'Saved');
    } catch (err) {
      const msg = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err);
      log.error({ url, err: msg }, 'Failed');
      await saveRowData(row, { Price: getVal(row, cfg.colPrice), PriceClub: getVal(row, cfg.colPriceClub), UpdatedAt: nowMoscow() }, cfg.dryRun);
    }
  }

  if (browser) {
    await context.close().catch(() => {});
    await browser.close().catch(() => {});
  } else {
    await context.close().catch(() => {});
  }
}

async function main() {
  const cli = parseCliArgs(process.argv.slice(2));
  const cfg = getConfigFromEnv(cli);
  const log = createLogger(cfg.logLevel);

  log.info({ sheet: cfg.sheetTabTitle || 'первый' }, 'Старт парсера');

  do {
    await runOnce(cfg, log);
    if (!cfg.runContinuous) break;
    const intervalMs = cfg.updateIntervalHours * 60 * 60 * 1000;
    log.info({ nextRunInHours: cfg.updateIntervalHours }, 'Sleeping until next run');
    await sleep(intervalMs);
  } while (cfg.runContinuous);
}

main().catch((err) => {
  // eslint-disable-next-line no-console
  console.error(err);
  process.exitCode = 1;
});

