import * as cheerio from 'cheerio';

export interface Finding {
  check: string;
  passed: boolean;
  detail: string;
}

export interface PageDiagnosis {
  pageTitle: string;
  bodyBytes: number;
  actualUrl?: string;
  findings: Finding[];
  likelyCause: string;
}

function pass(check: string, detail: string): Finding {
  return { check, passed: true, detail };
}

function fail(check: string, detail: string): Finding {
  return { check, passed: false, detail };
}

export function analyzeTocPage(
  html: string,
  scope: string,
  linkPattern: string,
  actualUrl?: string,
): PageDiagnosis {
  const $ = cheerio.load(html);
  const pageTitle = $('title').text().trim();
  const bodyText = $('body').text().toLowerCase();
  const bodyBytes = html.length;
  const findings: Finding[] = [];

  // ── 1. Page loaded at all ────────────────────────────────────────────────────
  findings.push(
    bodyBytes < 500
      ? fail('Page content', `Only ${bodyBytes} bytes — page likely failed to load (expected hundreds of KB)`)
      : pass('Page content', `${bodyBytes.toLocaleString()} bytes`),
  );

  // ── 2. Cloudflare ────────────────────────────────────────────────────────────
  const cfByTitle =
    pageTitle.toLowerCase().includes('just a moment') ||
    pageTitle.toLowerCase().includes('attention required');
  const cfByElement =
    $('[data-cf-settings]').length > 0 ||
    $('#cf-wrapper, #challenge-form, #challenge-running, #challenge-stage').length > 0 ||
    $('script[src*="challenges.cloudflare.com"]').length > 0;
  const isCf = cfByTitle || cfByElement;
  findings.push(
    isCf
      ? fail('Cloudflare challenge', `Detected — title: "${pageTitle}"`)
      : pass('Cloudflare challenge', 'Not detected'),
  );

  // ── 3. Generic security / bot block ─────────────────────────────────────────
  const blockKeywords = [
    'access denied', 'you have been blocked', 'ddos-guard',
    'bot protection', 'security check', 'please verify',
  ];
  const foundKeyword = blockKeywords.find(
    k => bodyText.includes(k) || pageTitle.toLowerCase().includes(k),
  );
  findings.push(
    foundKeyword
      ? fail('Security / bot block', `Keyword "${foundKeyword}" found on page`)
      : pass('Security / bot block', 'Not detected'),
  );

  // ── 4. CAPTCHA ───────────────────────────────────────────────────────────────
  const hasCaptcha =
    $('iframe[src*="recaptcha"], div.g-recaptcha, [class*="captcha"], [id*="captcha"]').length > 0 ||
    bodyText.includes('captcha');
  findings.push(
    hasCaptcha
      ? fail('CAPTCHA', 'CAPTCHA element found — manual solving required')
      : pass('CAPTCHA', 'Not detected'),
  );

  // ── 5. Login wall ────────────────────────────────────────────────────────────
  const hasLogin =
    $('input[type="password"]').length > 0 ||
    $('form[action*="login"], form[action*="signin"]').length > 0 ||
    pageTitle.toLowerCase().includes('login') ||
    pageTitle.toLowerCase().includes('sign in');
  findings.push(
    hasLogin
      ? fail('Login wall', `Login elements present — title: "${pageTitle}"`)
      : pass('Login wall', 'Not detected'),
  );

  // ── 6. Progressive scope selector breakdown ──────────────────────────────────
  // Builds the compound selector segment-by-segment so we can pinpoint exactly
  // which part of "a b c" breaks down.
  const scopeSegments = scope.trim().split(/\s+/).filter(Boolean);
  let builtSelector = '';
  let lastFoundSeg = '';
  let firstMissingSeg = '';

  for (const seg of scopeSegments) {
    const candidate = builtSelector ? `${builtSelector} ${seg}` : seg;
    if ($(candidate).length > 0) {
      lastFoundSeg = seg;
      builtSelector = candidate;
    } else {
      firstMissingSeg = seg;
      break;
    }
  }

  if (!firstMissingSeg) {
    findings.push(pass('TOC scope selector', `"${scope}" matched — unexpected, re-check logic`));
  } else if (!lastFoundSeg) {
    const root = scopeSegments[0] ?? scope;
    findings.push(
      fail('TOC scope selector', `Root "${root}" not found anywhere on page — scope: "${scope}"`),
    );
  } else {
    const existsElsewhere = $(firstMissingSeg).length > 0;
    findings.push(
      fail(
        'TOC scope selector',
        `"${lastFoundSeg}" found, but "${firstMissingSeg}" is missing within it` +
          (existsElsewhere
            ? ` (it exists elsewhere on the page — wrong DOM hierarchy)`
            : ` (not found anywhere on page)`) +
          ` — full scope: "${scope}"`,
      ),
    );
  }

  // ── 7. Link pattern anywhere on page ────────────────────────────────────────
  const pattern = new RegExp(linkPattern);
  const allHrefs: string[] = [];
  $('a[href]').each((_, el) => {
    const href = $(el).attr('href');
    if (href) allHrefs.push(href);
  });
  const matchingLinks = allHrefs.filter(h => pattern.test(h));

  if (matchingLinks.length > 0) {
    findings.push(
      pass(
        'Link pattern (anywhere)',
        `${matchingLinks.length} link(s) matching "${linkPattern}" exist on page — they are just outside the scope selector`,
      ),
    );
  } else {
    const sample = allHrefs.slice(0, 5).join(', ') || 'none';
    findings.push(
      fail(
        'Link pattern (anywhere)',
        `No links match "${linkPattern}". Total links: ${allHrefs.length}. Sample: [${sample}]`,
      ),
    );
  }

  // ── Determine likely cause ───────────────────────────────────────────────────
  // Priority: security blocks first (actionable externally), then scope/structure
  // issues (actionable in config), then unknown.
  // "links found outside scope" beats "scope root missing" because it tells the
  // user the data is there — just the selector needs updating — rather than
  // implying the whole site was redesigned.
  let likelyCause: string;
  if (isCf) {
    likelyCause =
      'Cloudflare challenge page — uBlock may not be installed correctly, ' +
      'or the wait time is too short for the JS challenge to resolve';
  } else if (foundKeyword) {
    likelyCause = `Security block ("${foundKeyword}") — the site is actively blocking automated access`;
  } else if (hasCaptcha) {
    likelyCause = 'CAPTCHA challenge — automated solving is not supported';
  } else if (hasLogin) {
    likelyCause = 'Login wall — the content may have moved behind an account requirement';
  } else if (matchingLinks.length > 0) {
    likelyCause =
      'Chapter links exist on the page but are outside the scope selector — ' +
      'the DOM structure shifted; update the scope selector in novels.yaml';
  } else if (!lastFoundSeg) {
    likelyCause =
      `Root selector "${scopeSegments[0] ?? scope}" is completely absent — ` +
      'the site has likely been redesigned; the scope in novels.yaml needs updating';
  } else if (firstMissingSeg) {
    likelyCause =
      `Inner selector "${firstMissingSeg}" is missing within "${lastFoundSeg}" — ` +
      'a partial page redesign; update the scope selector in novels.yaml';
  } else {
    likelyCause = `Unable to determine cause automatically. Page title: "${pageTitle}"`;
  }

  return { pageTitle, bodyBytes, actualUrl, findings, likelyCause };
}

export function formatDiagnosis(diagnosis: PageDiagnosis): string {
  const lines: string[] = [
    'TOC fetch failed — page diagnosis:',
    `  Title     : "${diagnosis.pageTitle}"`,
    `  Body size : ${diagnosis.bodyBytes.toLocaleString()} bytes`,
  ];
  if (diagnosis.actualUrl) {
    lines.push(`  Landed on : ${diagnosis.actualUrl}`);
  }
  lines.push('  Checks:');
  for (const f of diagnosis.findings) {
    lines.push(`    [${f.passed ? '✓' : '✗'}] ${f.check}: ${f.detail}`);
  }
  lines.push(`  Likely cause: ${diagnosis.likelyCause}`);
  return lines.join('\n');
}
