/**
 * Backlink Monitoring Service
 * ───────────────────────────
 * DataForSEO (primary) → GSC (fallback) → enrichment → AI quality → snapshots.
 * 7-day cache per brand to control API costs.
 */

import { prisma } from "@/lib/prisma";
import { getValidAccessToken } from "./integration-credentials";
import { callLlm } from "@/lib/ai-avatar/llm-client";
import { hasDataForSEO, fetchBacklinks as dfseFetchBacklinks, fetchCompetitorGaps as dfseFetchGaps, mapLinkType, type DfseLinkItem } from "./dataforseo-backlinks";
import { calculateToxicityScore } from "./backlink-toxicity";

interface RawLink { sourceDomain: string; sourceUrl: string; targetUrl: string; anchorText?: string | null; linkType?: string | null; domainAuthority?: number | null; status?: string; firstSeenAt?: Date; lastSeenAt?: Date }

// ─── DataForSEO fetch ─────────────────────────────────────────────────────

async function fetchFromDataForSEO(domain: string): Promise<RawLink[]> {
  const items = await dfseFetchBacklinks(domain, 500);
  console.log(`[backlinks] DataForSEO returned ${items.length} backlinks`);
  return items.map((item: DfseLinkItem) => ({
    sourceDomain: item.domain_from,
    sourceUrl: item.url_from,
    targetUrl: item.url_to,
    anchorText: item.anchor || null,
    linkType: mapLinkType(item.type || "follow"),
    domainAuthority: Math.round((item.domain_from_rank || 0) / 10),
    status: item.is_lost ? "lost" : item.is_new ? "new" : "active",
    firstSeenAt: item.first_seen ? new Date(item.first_seen) : new Date(),
    lastSeenAt: item.last_seen ? new Date(item.last_seen) : new Date(),
  }));
}

// ─── GSC fallback ─────────────────────────────────────────────────────────

async function fetchFromGSC(brandId: string, domain: string): Promise<RawLink[]> {
  let token: string;
  try { token = await getValidAccessToken(brandId, "gsc"); } catch { return []; }
  const siteUrl = `sc-domain:${domain}`;
  try {
    const res = await fetch(`https://searchconsole.googleapis.com/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/searchAnalytics/query`, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      body: JSON.stringify({ startDate: new Date(Date.now() - 90 * 86400000).toISOString().split("T")[0], endDate: new Date().toISOString().split("T")[0], dimensions: ["page"], rowLimit: 500 }),
    });
    if (!res.ok) return [];
    const data = await res.json() as { rows?: Array<{ keys: string[] }> };
    console.log(`[backlinks] GSC returned ${data.rows?.length ?? 0} pages`);
    return (data.rows ?? []).map((r) => ({ sourceDomain: "organic-search", sourceUrl: r.keys[0], targetUrl: r.keys[0] }));
  } catch { return []; }
}

// ─── Upsert + change detection ────────────────────────────────────────────

async function upsertAndDetect(brandId: string, links: RawLink[]): Promise<{ newCount: number; lostCount: number }> {
  const now = new Date();
  let newCount = 0;
  for (const link of links) {
    // Deterministic toxicity scoring runs on EVERY link, every sync.
    // Cheap pure-JS regex/comparison; populates toxicityScore,
    // qualityTier (the friendly "High Risk" string — NOT the legacy
    // excellent/good/neutral/poor labels), and the reasons array
    // used by the Toxic Panel and the disavow-file comments.
    const tox = calculateToxicityScore({
      domain: link.sourceDomain,
      da: link.domainAuthority ?? null,
      anchor: link.anchorText ?? null,
      type: link.linkType ?? null,
      sourcePage: link.sourceUrl,
    });

    const existing = await prisma.backlink.findUnique({ where: { brandId_sourceUrl_targetUrl: { brandId, sourceUrl: link.sourceUrl, targetUrl: link.targetUrl } } });
    if (!existing) {
      await prisma.backlink.create({ data: {
        brandId,
        sourceDomain: link.sourceDomain,
        sourceUrl: link.sourceUrl,
        targetUrl: link.targetUrl,
        anchorText: link.anchorText ?? null,
        linkType: link.linkType ?? null,
        domainAuthority: link.domainAuthority ?? null,
        toxicityScore: tox.score,
        qualityTier: tox.tier,
        toxicityReasons: tox.reasons,
        status: link.status || "new",
        firstSeenAt: link.firstSeenAt ?? now,
        lastSeenAt: now,
      } });
      newCount++;
    } else {
      // Preserve a previously-disavowed status so a re-sync doesn't
      // resurrect a link the user explicitly chose to disavow. Update
      // toxicity fields always — DA + anchor may have shifted since
      // the last run and we want the scores fresh.
      await prisma.backlink.update({ where: { id: existing.id }, data: {
        lastSeenAt: now,
        anchorText: link.anchorText ?? existing.anchorText,
        linkType: link.linkType ?? existing.linkType,
        domainAuthority: link.domainAuthority ?? existing.domainAuthority,
        toxicityScore: tox.score,
        qualityTier: tox.tier,
        toxicityReasons: tox.reasons,
        status: existing.status === "disavowed" ? "disavowed"
              : existing.status === "lost" ? "active"
              : existing.status,
      } });
    }
  }
  // Mark stale as lost
  const stale = await prisma.backlink.findMany({ where: { brandId, status: { in: ["active", "new"] }, lastSeenAt: { lt: new Date(now.getTime() - 7 * 86400000) } }, select: { id: true } });
  for (const s of stale) await prisma.backlink.update({ where: { id: s.id }, data: { status: "lost", lostAt: now } });
  return { newCount, lostCount: stale.length };
}

/**
 * Re-score every backlink for a brand using the deterministic toxicity
 * heuristics. Called by POST /api/backlinks/rescore — surfaced as the
 * "Analyze Toxicity" button on the page. Walks every non-disavowed
 * link and re-applies the score so legacy rows without reasons get
 * caught up, and a heuristic tweak propagates without a full sync.
 */
export async function rescoreToxicity(brandId: string): Promise<{ rescored: number; total: number; toxic: number; highRisk: number }> {
  const links = await prisma.backlink.findMany({
    where: { brandId, status: { not: "disavowed" } },
    select: { id: true, sourceDomain: true, sourceUrl: true, anchorText: true, linkType: true, domainAuthority: true },
  });
  for (const link of links) {
    const tox = calculateToxicityScore({
      domain: link.sourceDomain,
      da: link.domainAuthority,
      anchor: link.anchorText,
      type: link.linkType,
      sourcePage: link.sourceUrl,
    });
    await prisma.backlink.update({
      where: { id: link.id },
      data: { toxicityScore: tox.score, qualityTier: tox.tier, toxicityReasons: tox.reasons },
    });
  }
  const [total, toxic, highRisk] = await Promise.all([
    prisma.backlink.count({ where: { brandId, status: { not: "lost" } } }),
    prisma.backlink.count({ where: { brandId, toxicityScore: { gte: 80 } } }),
    prisma.backlink.count({ where: { brandId, toxicityScore: { gte: 60, lt: 80 } } }),
  ]);
  return { rescored: links.length, total, toxic, highRisk };
}

// ─── AI quality analysis ──────────────────────────────────────────────────

export async function analyzeQuality(backlinks: Array<{ id: string; sourceDomain: string; anchorText?: string | null }>, industry: string): Promise<number> {
  if (backlinks.length === 0) return 0;
  const domainList = backlinks.slice(0, 20).map((b) => `${b.sourceDomain}${b.anchorText ? ` (anchor: "${b.anchorText}")` : ""}`).join("\n");
  const result = await callLlm([
    { role: "system", content: "You are a backlink quality analyst. Respond ONLY with JSON." },
    { role: "user", content: `Analyze these linking domains for a ${industry} business:\n${domainList}\n\nReturn: {"domains":[{"domain":"...","relevanceScore":0-100,"toxicityScore":0-100,"qualityTier":"excellent|good|neutral|poor|toxic"}]}` },
  ], () => JSON.stringify({ domains: [] }), { maxTokens: 2000 });
  try {
    let s = result.reply.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
    const f = s.indexOf("{"), l = s.lastIndexOf("}");
    if (f >= 0 && l > f) s = s.slice(f, l + 1);
    const parsed = JSON.parse(s) as { domains?: Array<{ domain: string; relevanceScore?: number; toxicityScore?: number; qualityTier?: string }> };
    let updated = 0;
    for (const d of parsed.domains ?? []) {
      const bl = backlinks.find((b) => b.sourceDomain === d.domain);
      if (bl) { await prisma.backlink.update({ where: { id: bl.id }, data: { relevanceScore: d.relevanceScore ?? null, toxicityScore: d.toxicityScore ?? null, qualityTier: d.qualityTier ?? null } }); updated++; }
    }
    return updated;
  } catch { return 0; }
}

// ─── Full sync orchestrator ───────────────────────────────────────────────

export async function syncBacklinks(brandId: string): Promise<{ total: number; newCount: number; lostCount: number; enriched: number; analyzed: number; source: string }> {
  const brand = await prisma.brand.findUnique({ where: { id: brandId }, select: { domain: true, industry: true } });
  if (!brand) throw new Error("Brand not found");

  // 7-day cache: skip if recently synced
  const lastSnap = await prisma.backlinkSnapshot.findFirst({ where: { brandId }, orderBy: { snapshotDate: "desc" } });
  const daysSince = lastSnap ? (Date.now() - lastSnap.snapshotDate.getTime()) / 86400000 : 999;
  console.log(`[backlinks] Days since last sync: ${daysSince.toFixed(1)}`);

  // Fetch from DataForSEO (primary) or GSC (fallback)
  const source = hasDataForSEO() ? "dataforseo" : "gsc";
  console.log(`[backlinks] Source: ${source}, brand.domain: "${brand.domain}"`)
  const rawLinks = source === "dataforseo" ? await fetchFromDataForSEO(brand.domain) : await fetchFromGSC(brandId, brand.domain);
  console.log(`[backlinks] Raw links fetched: ${rawLinks.length}`);

  const { newCount, lostCount } = await upsertAndDetect(brandId, rawLinks);

  // AI quality on unscored (only relevance + toxicity — DA comes from DataForSEO)
  const unscored = await prisma.backlink.findMany({ where: { brandId, qualityTier: null, status: { in: ["active", "new"] } }, select: { id: true, sourceDomain: true, anchorText: true }, take: 20 });
  const analyzed = await analyzeQuality(unscored, brand.industry || "general");

  // Snapshot
  const total = await prisma.backlink.count({ where: { brandId, status: { in: ["active", "new"] } } });
  const domains = await prisma.backlink.groupBy({ by: ["sourceDomain"], where: { brandId, status: { in: ["active", "new"] } } });
  const toxic = await prisma.backlink.count({ where: { brandId, toxicityScore: { gte: 60 } } });
  const avgDa = await prisma.backlink.aggregate({ where: { brandId, domainAuthority: { not: null } }, _avg: { domainAuthority: true } });

  await prisma.backlinkSnapshot.create({ data: { brandId, totalBacklinks: total, totalDomains: domains.length, newBacklinks: newCount, lostBacklinks: lostCount, avgDomainAuthority: avgDa._avg.domainAuthority, toxicCount: toxic } });

  return { total, newCount, lostCount, enriched: 0, analyzed, source };
}

// ─── Gap analysis ─────────────────────────────────────────────────────────

export async function findBacklinkGaps(brandId: string, competitorDomain: string): Promise<Array<{ domain: string; reason: string; difficulty: string; outreach: string }>> {
  const brand = await prisma.brand.findUnique({ where: { id: brandId }, select: { domain: true, industry: true } });
  if (!brand) return [];

  // Use DataForSEO real gap data if available
  if (hasDataForSEO()) {
    const gaps = await dfseFetchGaps(brand.domain, competitorDomain);
    if (gaps.length > 0) {
      console.log(`[backlinks] DataForSEO gap: ${gaps.length} domains`);
      return gaps.slice(0, 15).map((g) => ({
        domain: g.domain, reason: `Links to ${competitorDomain} (rank: ${g.rank}, ${g.backlinks} links)`,
        difficulty: g.rank > 500 ? "easy" : g.rank > 200 ? "medium" : "hard",
        outreach: g.rank > 500 ? "Directory or niche site — submit listing" : "Authority site — pitch guest post or collaboration",
      }));
    }
  }

  // Fall back to AI estimation
  const result = await callLlm([
    { role: "system", content: "You are a link building strategist. Respond ONLY with JSON." },
    { role: "user", content: `For ${competitorDomain} competing with ${brand.domain} in ${brand.industry || "general"}, identify 10-15 likely backlink sources they have that ${brand.domain} probably doesn't.\nReturn: {"gaps":[{"domain":"...","reason":"...","difficulty":"easy|medium|hard","outreach":"..."}]}` },
  ], () => JSON.stringify({ gaps: [] }), { maxTokens: 2000 });
  try {
    let s = result.reply.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
    const f = s.indexOf("{"), l = s.lastIndexOf("}");
    if (f >= 0 && l > f) s = s.slice(f, l + 1);
    return (JSON.parse(s) as { gaps: Array<{ domain: string; reason: string; difficulty: string; outreach: string }> }).gaps ?? [];
  } catch { return []; }
}

// Re-export for backwards compat
export { fetchBacklinks as fetchDfseBacklinks, fetchReferringDomains } from "./dataforseo-backlinks";
