const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const path = require('path');
const fs = require('fs');
const { pool } = require('../db');

puppeteer.use(StealthPlugin());

const screenshotsDir = path.join(__dirname, '../../screenshots');
if (!fs.existsSync(screenshotsDir)) {
  fs.mkdirSync(screenshotsDir, { recursive: true });
}

class CasinoCrawler {
  constructor() {
    this.browser = null;
  }

  async initialize() {
    const chromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
    this.browser = await puppeteer.launch({
      headless: 'new',
      executablePath: chromePath,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-blink-features=AutomationControlled',
        '--window-size=1366,768'
      ]
    });
  }

  normalizeName(name) {
    if (!name || typeof name !== 'string') return name;
    return name.toLowerCase().replace(/\b([a-z])/g, c => c.toUpperCase()).trim();
  }

  async crawl(url, siteName) {
    const page = await this.browser.newPage();

    await page.setUserAgent(
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
    );

    await page.setExtraHTTPHeaders({
      'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8',
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
      'Sec-Ch-Ua': '"Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"',
      'Sec-Ch-Ua-Mobile': '?0',
      'Sec-Ch-Ua-Platform': '"macOS"',
      'Upgrade-Insecure-Requests': '1'
    });

    try {
      await page.setViewport({ width: 1366, height: 768 });
      console.log(`Navigating to ${siteName}: ${url}...`);

      let maxRetries = 3;
      let success = false;

      for (let attempt = 1; attempt <= maxRetries && !success; attempt++) {
        try {
          console.log(`  Attempt ${attempt}/${maxRetries}...`);
          await page.goto(url, { waitUntil: 'networkidle2', timeout: 90000 });
          await new Promise(r => setTimeout(r, 5000));

          const blocked = await page.evaluate(() => {
            return document.body.innerHTML.includes('Request could not be satisfied') ||
                   document.body.innerHTML.includes('403 ERROR');
          });

          if (!blocked) success = true;
          else { console.log(`  Blocked, retrying...`); await new Promise(r => setTimeout(r, 2000)); }
        } catch (navError) {
          console.log(`  Nav error: ${navError.message}`);
          if (attempt < maxRetries) await new Promise(r => setTimeout(r, 3000));
        }
      }

      if (!success) throw new Error('All retries failed');

      const timestamp = Date.now();
      const cleanName = siteName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
      const screenshotFile = `${cleanName}_${timestamp}.png`;
      const screenshotPath = path.join(screenshotsDir, screenshotFile);
      await page.screenshot({ path: screenshotPath, fullPage: true });
      console.log(`Screenshot saved: ${screenshotPath}`);

      const storedCrawl = await pool.query(
        'INSERT INTO crawls (url, site_name, status, screenshot_path) VALUES ($1, $2, $3, $4) RETURNING id',
        [url, siteName, 'completed', screenshotFile]
      );
      const crawlId = storedCrawl.rows[0].id;

      const casinos = await this.extractCasinoData(page);
      const resolvedCasinos = await this.resolveRedirects(casinos, page);

      // Post-process: filter out junk entries (nav text, CTAs, page titles) and normalize brands  
      var filteredCasinos = this.filterAndNormalizeCasinos(resolvedCasinos);

      for (const c of filteredCasinos) {
        const normalizedName = this.normalizeName(c.name);
        await pool.query(
          'INSERT INTO casinos (crawl_id, position, casino_name, url, bonus_offer) VALUES ($1, $2, $3, $4, $5)',
          [crawlId, c.position, normalizedName, c.link, c.bonus]
        );
      }
      console.log(`Found ${casinos.length} casinos from ${siteName}`);

      for (let i = 0; i < Math.min(casinos.length, 10); i++) {
        const entry = casinos[i];
        console.log(`   #${entry.position}: ${entry.name} | ${entry.bonus || '(no bonus)'}`);
      }

      return { crawlId, count: casinos.length };

    } catch (error) {
      console.error(`Error crawling ${url}:`, error.message);
      try {
        await pool.query(
          'INSERT INTO crawls (url, site_name, status) VALUES ($1, $2, $3)',
          [url, siteName, `failed: ${error.message}`]
        );
      } catch (dbErr) {}
      return null;
    } finally {
      try { await page.close(); } catch (e) {}
    }
  }

  async extractCasinoData(page) {
    const url = page.url();
    console.log(`  Extracting from: ${url}`);
    const extractorManager = require('./extractorManager').getInstance();
    const extractor = extractorManager.getExtractor(url);
    return await extractorManager.run(page, extractor);
  }

  filterAndNormalizeCasinos(casinos) {
    var kept = [];
    var seenLower = {};

    for (const c of casinos) {
      if (!c || !c.name) continue;

      var name = c.name.trim();
      var lower = name.toLowerCase();

      // Skip empty, too short, or obvious page-structure junk text  
      if (name.length < 3 || name.length > 60) continue;
      if (!/[a-zA-Z]/.test(name[0])) continue;

      // Must be mostly letters (not numbers/unicode/symbols/icons)
      var letterCount = (c.name.match(/[a-zA-Z]/g) || []).length;
      if (letterCount / name.length < 0.65) continue;

      // Reject nav/menu/footer/CTA/payment/badge text that gets scraped as "casino names"
      var junkPhrases = [
        'home', 'menu', 'nav', 'contact', 'about us', 'terms', 'privacy',
        'login', 'signup', 'register', 'account', 'support', 'help',
        'subscribe', 'newsletter', 'gambleaware', 'gamcare',
        'best casinos', 'top casinos', 'online casinos', 'uk online',
        'real money', 'free spin', 'welcome bonus', 'no deposit',
        'crypto casino', 'bitcoin', 'mobile', 'payout speed',
        'secure', 'license', 'licensed', 'dmca', 'bonuses & promos',
        'payment methods', 'types of bonuses', 'how we rank',
        'editorial', 'sitemap', 'latest news', 'view top casinos',
        'games', 'slots', 'poker', 'sports betting', 'bingo',
        'see bonuses', 'read review', 'play now', 'get bonus',
        'full t&c', 'claim offer', 'visit site', 'our guide',
        't&c apply', '18+', 'footer 18 plus'
      ];
      var isJunk = false;
      for (var j = 0; j < junkPhrases.length; j++) {
        if (lower.includes(junkPhrases[j])) {
          isJunk = true; break;
        }
      }
      if (isJunk) continue;

      // Reject things that look like icons/emoji/special chars rather than brands
      if (/[😀😃😄😁😆😅🤣😂🙂🙃😉😊😇]/.test(name)) continue;
      if (/^[♠♥♦♣]+/.test(name)) continue;

      // Reject things that are clearly domain names, phone numbers, or prices rather than casino brands  
      if (/^https?:\/\//.test(name)) continue;  // Looks like a URL got saved as "name"
      if (/[£$€]\d+/.test(name)) continue;       // Contains currency + digits — this is bonus/pricing text not brand

      // Deduplicate identical brand names case-insensitive (some extractors find same casino twice per page)
      var key = lower.replace(/[^a-z]/g, '');
      if (seenLower[key]) continue;
      seenLower[key] = true;

      kept.push({ ...c, name: this.normalizeName(name), position: kept.length + 1 });
    }

    console.log('    Filtered to', kept.length, 'valid casino entries from', casinos.length, 'raw');
    return kept.slice(0, 25);
  }

  async resolveRedirects(casinos, originalPage) {
    const resolved = [];
    const batchSize = 3;
    console.log('    Resolving affiliate link redirects...');

    for (let i = 0; i < casinos.length; i += batchSize) {
      const batch = casinos.slice(i, i + batchSize);
      const promises = batch.map(async (casino) => {
        if (!casino.link || !casino.link.startsWith('http')) {
          return casino;
        }

        let tempPage = null;
        try {
          tempPage = await this.browser.newPage();
          await tempPage.setUserAgent(
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
          );

          await tempPage.goto(casino.link, {
            waitUntil: 'networkidle2',
            timeout: 15000,
          });

          await new Promise(r => setTimeout(r, 3000));
          const finalUrl = tempPage.url();
          try {
            const parsed = new URL(finalUrl);
            return { ...casino, link: parsed.origin };
          } catch(e) {
            return { ...casino, link: finalUrl };
          }
        } catch (e) {
          console.warn(`      Failed to resolve redirect for ${casino.name}: ${e.message}`);
          return casino;
        } finally {
          if (tempPage) await tempPage.close().catch(() => {});
        }
      });

      const batchResults = await Promise.all(promises);
      resolved.push(...batchResults);
    }

    console.log(`    Resolved ${resolved.length} casino URLs`);
    return resolved;
  }

  async extractTop10Slots(page) {
    console.log('    Using top10onlineslots extractor');
    return page.evaluate(() => {
      var casinos = [];
      var seenNames = {};

      function safeText(el, maxLen) {
        if (!el) return '';
        var t = el.textContent.trim().replace(/\s+/g, ' ');
        return maxLen && t.length > maxLen ? t.slice(0, maxLen) : t;
      }

      // Cards have class "ni-7dqolb" wrapping casino data columns-wrapper ni-195yc4e
      var cards = document.querySelectorAll('.columns-wrapper[class*="ni-"], .mainProduct');

      if (cards.length === 0) {
        // Fallback to ni-7dqolb containers
        cards = document.querySelectorAll('[class*="ni-7dqolb"]');
      }

      if (cards.length === 0) {
        // Try chart-wrapper which contains all casino rows
        var charts = document.querySelectorAll('.chart-wrapper');
        for (var ch = 0; ch < charts.length; ch++) {
          cards = charts[ch].querySelectorAll('div:not([class*="ni-0"])');
        }
      }

      // Better strategy: look for divs with "row-index" pattern or similar numbered rows
      var allDivs = document.querySelectorAll('div[class]');
      for (var d = 0; d < allDivs.length; d++) {
        if (casinos.length >= 20) break;
        var cls = String(allDivs[d].className);
        var txt = safeText(allDivs[d], 500);

        if (!txt.includes('Get Bonus') && !txt.includes('Welcome Bonus')) continue;
        if (!allDivs[d].querySelector('img')) continue;

        var logoImg = allDivs[d].querySelector('img[alt*="logo"]');
        var name = '';
        if (logoImg) {
          name = logoImg.alt.replace(/\s*logo\s*$/i, '').trim();
        }

        if (!name || seenNames[name]) continue;
        seenNames[name] = true;

        // Get casino link - the main CTA/button to visit casino
        var visitLink = null;
        allDivs[d].querySelectorAll('a').forEach(a => {
          var atxt = a.textContent.trim();
          if (atxt.includes('Visit') || atxt.includes('Get Bonus')) {
            if (!visitLink) visitLink = a;
          }
        });

        // Get bonus text - look for "Welcome Bonus" + description
        var bonusText = '';
        allDivs[d].querySelectorAll('span, p, div').forEach(el => {
          var t = safeText(el, 200);
          var tl = t.toLowerCase();
          if (tl.includes('free spin') || /[£€$]\d+/.test(tl)) {
            if (!bonusText || bonusText.includes('Welcome Bonus')) {
              bonusText = t.replace(/Welcomed Bonus/gi, '').trim();
            }
          }
        });

        // Clean bonus text
        var bonusClean = '';
        allDivs[d].querySelectorAll('*').forEach(el => {
          var t = safeText(el, 150);
          if (/get\s*\d*\s*(free spins|spins)/i.test(t)) {
            bonusClean = t.trim();
          }
        });

        casinos.push({
          position: casinos.length + 1,
          name: name.replace(/\s+/g, ' ').replace(/-/g, ' '),
          link: visitLink ? new URL(visitLink.href, document.baseURI).href : '',
          bonus: bonusClean || bonusText || ''
        });

        if (casinos.length >= 20) return;
      }

      return casinos.slice(0, 20);
    });
  }

  async extractUbet(page) {
    console.log('    Using ubet extractor');
    return page.evaluate(() => {
      var casinos = [];
      var seenNames = {};

      function safeText(el, maxLen) {
        if (!el) return '';
        var t = el.textContent.trim().replace(/\s+/g, ' ');
        return maxLen && t.length > maxLen ? t.slice(0, maxLen) : t;
      }

      // Cards have classes like "wss-topContainer-3871867399 mainProduct  jquery-loaded row-index-1"
      // The key identifier is having both "mainProduct" and "row-index" in class string
      var allCardDivs = document.querySelectorAll('div');
      var cards = [];
      for (var i = 0; i < allCardDivs.length; i++) {
        var cls = ' ' + allCardDivs[i].className + ' ';
        if (cls.includes('.mainProduct ') || cls.includes(' mainProduct ')) {
          if (cls.includes('row-index')) cards.push(allCardDivs[i]);
        }
      }

      for (var c = 0; c < cards.length && casinos.length < 20; c++) {
        var card = cards[c];
        if (casinos.length >= 20) break;

        // Casino name is in div with class "wss-vendorName-*" containing text like "Visit livescore - casino"
        var vendorNameDiv = card.querySelector('[class*="vendorName"]');
        var casinoName = '';
        if (vendorNameDiv) {
          casinoName = safeText(vendorNameDiv, 100)
            .replace(/^Visit\s*/i, '')
            .replace(/\s*[-–—]\s*(casino|Casino)\s*$/i, '')
            .trim();
        }

        // Bonus is in div with class "coupon-container" or "wss-couponContainer-*"
        var couponDiv = card.querySelector('[class*="couponContainer"]');
        var bonusText = '';
        if (couponDiv) {
          var rawCoupon = safeText(couponDiv, 200);
          // Remove "Welcome bonus" prefix to get the actual offer
          bonusText = rawCoupon.replace(/^Welcome\s+bonus/i, '').trim();
          // Keep first meaningful line only
          bonusText = bonusText.split(/\n/)[0].trim();
        }

        // Casino link is in the "Get Bonus" button's href
        var casinoUrl = '';
        card.querySelectorAll('a').forEach(function(a) {
          var t = a.textContent.trim();
          if (t === 'Get Bonus' || t == 'Claim Offer' || t.includes('Visit Site')) {
            try {
              if (!casinoUrl) casinoUrl = new URL(a.href, document.baseURI).href;
            } catch(e) {}
          }
        });

        if (!casinoName || seenNames[casinoName]) continue;
        seenNames[casinoName] = true;

        casinos.push({
          position: casinos.length + 1,
          name: casinoName,
          link: casinoUrl,
          bonus: bonusText
        });
      }

      // Fallback: look for "Get Bonus" buttons and work backwards
      if (casinos.length === 0) {
        var getBonusBtns = document.querySelectorAll('a[class*="button"], button[class*="bonus"]');
        for (var g = 0; g < getBonusBtns.length && casinos.length < 20; g++) {
          var btn = getBonusBtns[g];
          var parentCard = btn.closest('[class*="topContainer"], [class*="Product"], div[class]');
          if (!parentCard) continue;

          var pName = '';
          parentCard.querySelectorAll('a').forEach(a => {
            var t = a.textContent.trim();
            if (t.startsWith('Visit ')) pName = a.textContent.replace(/^Visit\s*/, '');
          });

          if (!pName || seenNames[pName]) continue;
          seenNames[pName] = true;

          casinos.push({
            position: casinos.length + 1,
            name: pName,
            link: btn.href || '',
            bonus: ''
          });
        }
      }

      return casinos.slice(0, 20);
    });
  }

  async extractGeneric(page) {
    console.log('    Using generic extractor');
    return page.evaluate(() => {
      var casinos = [];
      var seenNames = {};

      // Filter: only accept short brand-like names, reject nav/menu/footer/CTA text
      function isValidCasinoBrand(name) {
        if (!name || name.length < 3 || name.length > 40) return false;
        if (!/[a-zA-Z]/.test(name.charAt(0))) return false;
        var lower = name.toLowerCase();
        // Skip obvious nav/footer/CTA junk text
        var badWords = [
          'menu', 'nav', 'home', 'contact', 'about', 'terms', 'privacy',
          'login', 'signup', 'register', 'account', 'support', 'help',
          'subscribe', 'newsletter', 'best online casinos', 'top casinos',
          'betting', 'wagering', 'secure gaming', 'fast payout',
          'crypto casino', 'bitcoin', 'mobile casino', 'welcome bonus',
          'license', 'fair play', 'gamble'
        ];
        for (var i = 0; i < badWords.length; i++) {
          if (lower.includes(badWords[i])) return false;
        }
        // A real brand name is mostly letters, not symbols/icons text
        var letterCount = (name.match(/[a-zA-Z]/g) || []).length;
        if (letterCount / name.length < 0.6) return false;
        return true;
      }

      function clean(raw) {
        if (!raw) return '';
        // Strip common suffix/prefix that logos repeat on every card
        return raw.replace(/[Cc]asino \s*$/i, '').replace(/\s*logo\s*$/i, '');
      }

      function getExternalLink(cardNode) {
        var host = location.hostname.replace('www.', '');
        var links = cardNode.querySelectorAll('a[href]');
        for (var a = 0; a < links.length; a++) {
          try {
            var resolvedUrl = new URL(links[a].href, document.baseURI);
            var lh = resolvedUrl.hostname.replace('www.', '');
            if (lh && lh !== host) return resolvedUrl.origin;
          } catch(e) {}
        }
        return '';
      }

      // Strategy 1: Card-based layouts that have logo image + external CTA link in them  
      var cardSelectors = '.review-card, .casino-item, .casino-box, .card-listing, ' +
        '[class*="ranking"], [class*="columns-wrapper"]';
      var allCards = document.querySelectorAll(cardSelectors);

      // Fallback: walk every container div that has meaningful content
      if (allCards.length === 0) {
        allCards = document.querySelectorAll('div[class]');
      }

      for (var ci = 0; ci < allCards.length && casinos.length < 20; ci++) {
        var card = allCards[ci];
        var brandName = '';

        // Get logo alt text from images in this card container
        var imgs = card.querySelectorAll('img');
        for (var ii = 0; ii < imgs.length; ii++) {
          if (!imgs[ii].alt) continue;
          var candidate = clean(imgs[ii].alt);
          if (isValidCasinoBrand(candidate)) {
            brandName = candidate;
            break;
          } else break;
        }

        if (!brandName || seenNames[brandName]) continue;

        // Find the external affiliate/CTA link from this card
        var extUrl = getExternalLink(card);

        seenNames[brandName] = true;

        casinos.push({
          position: casinos.length + 1,
          name: brandName.replace(/[^a-zA-Z0-9\s&.]/g, '').trim(),
          link: extUrl,
          bonus: ''
        });
      }

      // Strategy 2: Tables (review sites that organize in table format)
      if (casinos.length === 0) {
        var tables = document.querySelectorAll('table');
        for (var t = 0; t < Math.min(tables.length, 3); t++) {
          var rows = tables[t].querySelectorAll('tr');
          for (var r = 0; r < rows.length && casinos.length < 20; r++) {
            var cells = rows[r].querySelectorAll('td, th');
            if (cells.length < 2) continue;

            var cellName = '';
            var linkUrl = '';

            for (var i = 0; i < cells.length; i++) {
              var img2 = cells[i].querySelector('img');
              var anchor = cells[i].querySelector('a');

              if (!img2 && !anchor) continue;

              // Prefer logo alt text, fallback to link text
              var n = '';
              if (img2 && img2.alt) {
                n = clean(img2.alt);
              } else if (anchor) {
                n = anchor.textContent.trim().substring(0, 60);
              }

              // Extract external affiliate/visit link URL
              if (anchor && !linkUrl) {
                try {
                  var extL = new URL(anchor.href, document.baseURI);
                  var pHost = location.hostname.replace('www.', '');
                  var aHost = extL.hostname.replace('www.', '');
                  if (!aHost || aHost !== pHost) linkUrl = extL.origin;
                } catch(e) {}
              }

              if (n && isValidCasinoBrand(n)) { cellName2 = n; break; }
            }

            if (!cellName || seenNames[cellName]) continue;

            var cleanN = cellName.replace(/[^a-zA-Z\s&.]/g, '').trim();
            casinos.push({
              position: casinos.length + 1,
              name: cleanN,
              link: extL || '',
              bonus: ''
            });
          }
        }

        // Strategy 3: Row-based layouts common on UK review sites  
        var rowDivs = document.querySelectorAll('[class*="row"], .row');
        for (var ri = 0; ri < rowDivs.length && casinos.length < 20; ri++) {
          var rowDiv = rowDivs[ri];

          // Quick skip: must contain logo/brand image OR CTA button/text
          if (!rowDiv.querySelector('img') && !rowDiv.querySelector('[class*="CTA"], [class*="cta"]')) continue;

          var imgs2 = rowDiv.querySelectorAll('img[alt]');
          var brandCand = '';

          for (var i2 = 0; i2 < imgs2.length; i++) {
            candidate = clean(imgs2[i2].alt);
            if (isValidCasinoBrand(candidate)) {
              brandCand = candidate; break; }
          }

          // Fallback to anchor/button text that looks like a casino name  
          if (!brandCand) {
            rowDiv.querySelectorAll('a[href]').forEach(function(a) {
              var lt = a.textContent.trim();
              if (lt.startsWith('Visit ') || 
                  lt.includes('Get Bonus') || 
                  lt.includes('Claim')) {
                // Extract brand part from CTA  
                brandCand = lt.replace(/^Visit\s*/i, '').replace(/\s*[-–—].*/, '');
              }
            });
          }

          if (!brandCand || seenNames[brandCand]) continue;
          seenNames[brandCand] = true;

          var cleanN2 = brandCand.replace(/[^a-zA-Z0-9\s&.]/g, '').trim();
          casinos.push({ position: casinos.length + 1, name: cleanN2, link: '', bonus: '' });
        }
      }

      return casinos.slice(0, 20);
    });
  }

  async close() {
    if (this.browser) await this.browser.close();
  }
}

module.exports = CasinoCrawler;
