/**
 * Competitor Deep Dive — Aggregation Service
 * ────────────────────────────────────────────
 * Fetches authority metrics and scrapes up to 2 pages per competitor to build
 * a structured competitive gap analysis. Reuses already-fetched brand data.
 * Max 6 Firecrawl calls total (2 per competitor × 3 competitors).
 *
 * Internal module — vendor names permitted in code; forbidden in user-facing output.
 */

import Firecrawl from "@mendable/firecrawl-js";
import { fetchBacklinksSummary, type BacklinksSummary } from "./backlinks-summary";
import type { SerpAnalysis } from "./serp-analysis";
import type { CompetitorAnalysis, PageData } from "../types";
import type { ContentSample } from "../scrapers/firecrawl-content";

const FIRECRAWL_API_KEY = process.env.FIRECRAWL_API_KEY ?? "";

function isFirecrawlConfigured(): boolean {
  return !!FIRECRAWL_API_KEY;
}

// ─── Schema detection from markdown (JSON-LD @type patterns) ─────────────────

const SCHEMA_TYPES_TO_CHECK = [
  "FAQPage", "AggregateRating", "LocalBusiness", "MedicalBusiness",
  "Product", "Service", "Physician", "Organization", "BreadcrumbList",
  "HowTo", "VideoObject", "Article",
];

function detectSchemaInMarkdown(markdown: string): string[] {
  return SCHEMA_TYPES_TO_CHECK.filter((type) =>
    new RegExp(`["']@type["']\\s*:\\s*["']${type}["']`, "i").test(markdown),
  );
}

function extractH2s(md: string): string[] {
  return (md.match(/^##\s+(.+)$/gm) ?? [])
    .map((m) => m.replace(/^##\s+/, "").trim())
    .slice(0, 8);
}

// ─── Competitor page scraper (max 2 pages: homepage + /services) ──────────────

async function scrapeCompetitorPages(domain: string): Promise<ContentSample[]> {
  if (!isFirecrawlConfigured()) return [];

  const app = new Firecrawl({ apiKey: FIRECRAWL_API_KEY });
  const urls = [`https://${domain}`, `https://${domain}/services`];
  const results: ContentSample[] = [];

  for (const url of urls) {
    try {
      const result = await app.scrape(url, { formats: ["markdown"], onlyMainContent: true });
      if (!result.markdown) continue;

      const words = result.markdown.split(/\s+/);
      results.push({
        url,
        markdown: words.slice(0, 500).join(" "),
        title: result.metadata?.title ?? undefined,
        wordCount: words.length,
        h1: (result.metadata as Record<string, unknown> | undefined)?.["h1"] as string | undefined,
        headingOutline: extractH2s(result.markdown),
        hasFAQ: /\bfaq\b|frequently asked/i.test(result.markdown),
        hasCTA: /\b(book|schedule|request|get started|contact us|call us|sign up|register)\b/i.test(result.markdown),
        hasPhoneClick: /tel:|(\+1[\s.-]?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/i.test(result.markdown),
        hasLocationSchema: false,
        ctaExamples: [],
      });
    } catch (err) {
      console.warn(`[competitor-analysis] scrape failed for ${url}:`, err instanceof Error ? err.message : err);
    }
  }
  return results;
}

// ─── Key differentiator (rule-based summary of standout signals) ──────────────

function buildKeyDifferentiator(
  domain: string,
  backlinks: BacklinksSummary,
  samples: ContentSample[],
  avgRank: number,
  queryCount: number,
): string {
  const parts: string[] = [];

  if (avgRank <= 5) {
    parts.push(`Ranks avg position ${avgRank} across ${queryCount} tracked ${queryCount === 1 ? "query" : "queries"}`);
  }

  const avgWords = samples.length > 0
    ? Math.round(samples.reduce((acc, s) => acc + (s.wordCount ?? 0), 0) / samples.length)
    : 0;
  const hasFAQ = samples.some((s) => s.hasFAQ);
  const hasCTA = samples.some((s) => s.hasCTA);

  if (hasFAQ) parts.push("FAQ content detected on service pages");
  if (avgWords > 800) parts.push(`service pages average ~${avgWords} words`);
  if (hasCTA && parts.length < 3) parts.push("strong conversion CTAs present");
  if (backlinks.domainRank && backlinks.domainRank > 40 && parts.length < 4) {
    parts.push(`domain authority ${backlinks.domainRank}`);
  }
  if (backlinks.referringDomains && backlinks.referringDomains > 300 && parts.length < 4) {
    parts.push(`${backlinks.referringDomains.toLocaleString()} referring domains`);
  }

  if (parts.length === 0) return `Competitor ${domain} detected across tracked queries.`;
  const sentence = parts.join("; ");
  return sentence.charAt(0).toUpperCase() + sentence.slice(1) + ".";
}

// ─── Gap matrix construction ──────────────────────────────────────────────────

type GapValue = string | number;

function toNum(v: GapValue): number {
  if (typeof v === "number") return v;
  if (v === "Yes") return 1;
  if (v === "No" || v === "N/A") return 0;
  const n = parseFloat(String(v));
  return isNaN(n) ? 0 : n;
}

function computeBrandRank(brandVal: GapValue, compVals: GapValue[], higherIsBetter: boolean): 1 | 2 | 3 | 4 {
  const all = [brandVal, ...compVals].map(toNum);
  const brandNum = all[0];
  const sorted = [...all].sort((a, b) => higherIsBetter ? b - a : a - b);
  const rank = sorted.indexOf(brandNum) + 1;
  return Math.min(4, Math.max(1, rank)) as 1 | 2 | 3 | 4;
}

function computeGapMagnitude(
  brandRank: 1 | 2 | 3 | 4,
  brandVal: GapValue,
  compVals: GapValue[],
  higherIsBetter: boolean,
): "critical" | "high" | "moderate" | "low" {
  if (brandRank === 1) return "low";
  const brandNum = toNum(brandVal);
  const bestNum = higherIsBetter
    ? Math.max(...compVals.map(toNum))
    : Math.min(...compVals.filter((v) => v !== "N/A").map(toNum));

  if (bestNum === 0) return "low";
  if (typeof brandVal === "string" && typeof compVals[0] === "string") {
    // Boolean dimension
    return brandNum === 0 && bestNum === 1 ? (brandRank === 4 ? "high" : "moderate") : "low";
  }
  const ratio = brandNum / bestNum;
  if (ratio < 0.4) return brandRank >= 3 ? "critical" : "high";
  if (ratio < 0.65) return "high";
  if (ratio < 0.85) return "moderate";
  return "low";
}

function buildDimension(
  name: string,
  brandVal: GapValue,
  compVals: GapValue[], // [A, B, C] — may be shorter if <3 competitors
  higherIsBetter: boolean,
): CompetitorAnalysis["competitiveGapMatrix"]["dimensions"][0] {
  const padded: GapValue[] = [compVals[0] ?? "N/A", compVals[1] ?? "N/A", compVals[2] ?? "N/A"];
  const brandRank = computeBrandRank(brandVal, padded, higherIsBetter);
  const gapMag = computeGapMagnitude(brandRank, brandVal, padded, higherIsBetter);
  return {
    name,
    brandValue: brandVal,
    competitorAValue: padded[0],
    competitorBValue: padded[1],
    competitorCValue: padded[2],
    brandRank,
    gapMagnitude: gapMag,
  };
}

// ─── Main export ──────────────────────────────────────────────────────────────

export async function aggregateCompetitorAnalysis(
  serpData: SerpAnalysis,
  pages: PageData[],
  contentSamples: ContentSample[],
  brandBacklinks: BacklinksSummary | null,
): Promise<CompetitorAnalysis | null> {
  const competitorList = serpData.competitorDetails.length > 0
    ? serpData.competitorDetails
    : serpData.competitorDomains.slice(0, 3).map((domain) => ({ domain, queries: [], avgRankPosition: 5 }));

  const top3 = competitorList.slice(0, 3);
  if (top3.length === 0) return null;

  console.log(`[competitor-analysis] Analyzing ${top3.length} competitors: ${top3.map((c) => c.domain).join(", ")}`);

  // Per-competitor metrics — keep in parallel array for gap matrix
  type CompMetrics = {
    domainAuthority: number | null;
    referringDomains: number | null;
    totalBacklinks: number | null;
    hasFAQPage: boolean;
    hasAggRating: boolean;
    hasCTA: boolean;
    hasPhone: boolean;
    avgWordCount: number;
    schemaTypeCount: number;
    avgH2Count: number;
  };

  const detectedCompetitors: CompetitorAnalysis["detectedCompetitors"] = [];
  const compMetrics: CompMetrics[] = [];
  let successfulScrapes = 0;

  for (const comp of top3) {
    try {
      // Sequential per competitor to be rate-limit safe; backlinks + scrape run in parallel
      const [backlinks, samples] = await Promise.all([
        fetchBacklinksSummary(comp.domain),
        scrapeCompetitorPages(comp.domain),
      ]);

      successfulScrapes += samples.length;

      const allSchema = new Set<string>();
      for (const sample of samples) {
        for (const type of detectSchemaInMarkdown(sample.markdown)) allSchema.add(type);
      }

      const topics: string[] = [];
      for (const sample of samples) {
        if (sample.h1) topics.push(sample.h1.trim());
        for (const h2 of (sample.headingOutline ?? []).slice(0, 2)) topics.push(h2.trim());
      }

      const avgWordCount = samples.length > 0
        ? Math.round(samples.reduce((acc, s) => acc + (s.wordCount ?? 0), 0) / samples.length)
        : 0;
      const avgH2Count = samples.length > 0
        ? Math.round(samples.reduce((acc, s) => acc + (s.headingOutline?.length ?? 0), 0) / samples.length)
        : 0;

      compMetrics.push({
        domainAuthority: backlinks.domainRank,
        referringDomains: backlinks.referringDomains,
        totalBacklinks: backlinks.totalBacklinks,
        hasFAQPage: allSchema.has("FAQPage"),
        hasAggRating: allSchema.has("AggregateRating"),
        hasCTA: samples.some((s) => s.hasCTA),
        hasPhone: samples.some((s) => s.hasPhoneClick),
        avgWordCount,
        schemaTypeCount: allSchema.size,
        avgH2Count,
      });

      detectedCompetitors.push({
        domain: comp.domain,
        appearsInQueries: comp.queries,
        avgRankPosition: comp.avgRankPosition,
        domainAuthority: backlinks.domainRank,
        referringDomains: backlinks.referringDomains,
        totalBacklinks: backlinks.totalBacklinks,
        detectedSchema: SCHEMA_TYPES_TO_CHECK.map((type) => ({ type, present: allSchema.has(type) })),
        topContentTopics: [...new Set(topics)].slice(0, 5),
        pagesScraped: samples.length,
        keyDifferentiator: buildKeyDifferentiator(comp.domain, backlinks, samples, comp.avgRankPosition, comp.queries.length),
      });

      console.log(`[competitor-analysis] ${comp.domain}: DA=${backlinks.domainRank} refs=${backlinks.referringDomains} scraped=${samples.length} schemas=${[...allSchema].join(",") || "none"}`);
    } catch (err) {
      console.warn(`[competitor-analysis] failed for ${comp.domain}:`, err instanceof Error ? err.message : err);
      // Still push placeholder so gap matrix indices align
      compMetrics.push({
        domainAuthority: null, referringDomains: null, totalBacklinks: null,
        hasFAQPage: false, hasAggRating: false, hasCTA: false, hasPhone: false,
        avgWordCount: 0, schemaTypeCount: 0, avgH2Count: 0,
      });
    }
  }

  if (detectedCompetitors.length === 0) return null;

  // ── Brand metrics ──────────────────────────────────────────────────────────

  const brandSchemaTypes = new Set(pages.flatMap((p) => (p.schemaTypes as string[] | undefined) ?? []));
  const brandAvgWords = contentSamples.length > 0
    ? Math.round(contentSamples.reduce((acc, s) => acc + (s.wordCount ?? 0), 0) / contentSamples.length)
    : 0;
  const brandAvgH2 = contentSamples.length > 0
    ? Math.round(contentSamples.reduce((acc, s) => acc + (s.headingOutline?.length ?? 0), 0) / contentSamples.length)
    : 0;

  // ── Gap matrix ─────────────────────────────────────────────────────────────

  const cVals = <K extends keyof CompMetrics>(key: K): GapValue[] =>
    compMetrics.map((m) => {
      const v = m[key];
      if (v === null || v === undefined) return "N/A";
      if (typeof v === "boolean") return v ? "Yes" : "No";
      return v as GapValue;
    });

  const dimensions: CompetitorAnalysis["competitiveGapMatrix"]["dimensions"] = [
    buildDimension("Domain Authority",       brandBacklinks?.domainRank ?? 0,       cVals("domainAuthority"),  true),
    buildDimension("Referring Domains",      brandBacklinks?.referringDomains ?? 0, cVals("referringDomains"), true),
    buildDimension("Total Backlinks",        brandBacklinks?.totalBacklinks ?? 0,   cVals("totalBacklinks"),   true),
    buildDimension("FAQPage Schema",         brandSchemaTypes.has("FAQPage") ? "Yes" : "No",       cVals("hasFAQPage"),    true),
    buildDimension("AggregateRating Schema", brandSchemaTypes.has("AggregateRating") ? "Yes" : "No", cVals("hasAggRating"), true),
    buildDimension("Avg Page Word Count",    brandAvgWords,                         cVals("avgWordCount"),     true),
    buildDimension("CTA Presence",           contentSamples.some((s) => s.hasCTA) ? "Yes" : "No",  cVals("hasCTA"),        true),
    buildDimension("Phone Contact Signal",   contentSamples.some((s) => s.hasPhoneClick) ? "Yes" : "No", cVals("hasPhone"), true),
    buildDimension("Schema Type Breadth",    brandSchemaTypes.size,                 cVals("schemaTypeCount"),  true),
    buildDimension("Content H2 Depth",       brandAvgH2,                            cVals("avgH2Count"),       true),
  ];

  const dataQuality: CompetitorAnalysis["dataQuality"] =
    successfulScrapes >= 4 ? "high" : successfulScrapes >= 2 ? "moderate" : "low";

  return { detectedCompetitors, competitiveGapMatrix: { dimensions }, dataQuality };
}
