/**
 * AI Visibility Checker — Query-based brand monitoring in AI responses.
 * Simulates AI assistant responses via Claude to track brand mentions.
 */

import { prisma } from "@/lib/prisma";
import { callLlm } from "@/lib/ai-avatar/llm-client";
import { logAiInteraction } from "@/lib/services/ai-interaction-log";
import { getBrandLocations } from "@/lib/brand-helpers";

function extractJson(raw: string): string {
  let s = raw.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 s;
}

export interface AiCheckResult {
  mentioned: boolean; position: number | null; context: string | null;
  fullResponse: string; sentiment: string | null; competitors: string[];
  improvementTips: string[]; locationUsed: string | null;
  sourcesUsed?: string[];
  webSearchEnabled?: boolean;
}

// ─── Location resolution ────────────────────────────────────────────────────

/** Pull the brand's primary service location from GBP → profile → brand, in priority order. */
async function getPrimaryLocation(brandId: string): Promise<string | null> {
  // 1. GBP locations (most accurate — real office addresses)
  const gbp = await prisma.gbpLocation.findFirst({
    where: { brandId },
    select: { city: true, state: true },
    orderBy: { connectedAt: "asc" }, // primary = first connected
  }).catch(() => null);
  if (gbp?.city) return `${gbp.city}${gbp.state ? `, ${gbp.state}` : ""}`;

  // 2. Brand profile locations array
  const profile = await prisma.brandProfile.findUnique({
    where: { brandId },
    select: { locations: true },
  }).catch(() => null);
  if (profile?.locations?.length) return profile.locations[0];

  // 3. Brand.locations[] / Brand.location field (from AI Website Intake)
  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    select: { location: true, locations: true },
  });
  const brandLocs = brand ? getBrandLocations(brand) : [];
  if (brandLocs.length > 0) return brandLocs[0];

  return null;
}

const GEO_PATTERN = /\bnear me\b|\bin my area\b|\bnear my\b|\blocal\b|\bbest\s+\w+\s+in\b|\btop\s+\w+\s+in\b|\btop\s+\w+\s+near\b/i;
const NEAR_ME_REPLACE = /\bnear me\b|\bin my area\b|\bnear my location\b/gi;

export interface SuggestedQuery {
  query: string;
  intent: "informational" | "commercial" | "local" | "transactional";
  targetLocation: string;
}

export async function suggestQueries(brandId: string, type: "branded" | "nonbranded" = "branded"): Promise<SuggestedQuery[]> {
  const brand = await prisma.brand.findUnique({ where: { id: brandId }, include: { profile: true } });
  if (!brand) return [];

  // Collect all brand locations for multi-location rotation
  const gbpLocations = await prisma.gbpLocation.findMany({
    where: { brandId, isActive: true },
    select: { city: true, state: true },
    take: 5,
  }).catch(() => [] as { city: string | null; state: string | null }[]);

  const allLocations = getBrandLocations(brand);
  const gbpCityState = gbpLocations.map((g) => `${g.city || ""}${g.state ? `, ${g.state}` : ""}`.trim()).filter(Boolean);
  const locationPool = [...new Set([...gbpCityState, ...allLocations])].filter(Boolean);

  // Primary location for anchoring
  const primaryLoc = gbpCityState[0] || allLocations[0] || "";
  const primaryCity = gbpLocations[0]?.city || primaryLoc.split(",")[0]?.trim() || "";
  const primaryState = gbpLocations[0]?.state || primaryLoc.split(",")[1]?.trim() || "";

  const services = brand.profile?.services || [];
  const industry = brand.profile?.industry || brand.industry || "general";

  const locationsNote = locationPool.length > 1
    ? `The brand serves multiple locations: ${locationPool.slice(0, 4).join("; ")}. Distribute queries across these locations.`
    : `The brand is located in ${primaryLoc}.`;

  const prompt = type === "branded"
    ? `For ${brand.name} (${industry}) in ${primaryLoc}, services: ${services.join(", ") || industry}
Generate 20 queries that a user might ask an AI assistant specifically about ${brand.name}. Include questions about their services, reputation, reviews, locations, insurance, appointments, and comparisons.
Return JSON array: [{"query":"...","intent":"commercial","targetLocation":"${primaryCity}"},...]
intent must be one of: informational, commercial, local, transactional.
targetLocation is the city or area the query targets.`
    : `For the ${industry} industry. ${locationsNote} Services: ${services.join(", ") || industry}
Generate 20 queries a potential customer might ask an AI assistant about ${industry} WITHOUT mentioning any specific brand.
Distribution rules — NO bare "near me" phrases:
- ~60% city-anchored: "best ${industry} in ${primaryCity}", "top ${industry} in ${primaryCity}, ${primaryState}"
- ~30% neighborhood/district: mention a neighborhood, district, or nearby suburb of ${primaryCity}
- ~10% state-level: "best ${industry} in ${primaryState}"
Intent mix:
- 5-7 informational (conditions, how-to, educational)
- 5-7 commercial (best providers, comparisons, reviews, cost)
- 3-4 local (city/neighborhood anchored service searches)
- 3-4 transactional (scheduling, cost, insurance acceptance)
Do NOT use "near me", "in my area", or any first-person location phrase.
Do NOT include "${brand.name}" in any query.
Return JSON array: [{"query":"...","intent":"local","targetLocation":"${primaryCity}"},...]
intent must be one of: informational, commercial, local, transactional.
targetLocation is the specific city, neighborhood, or state used in the query.`;

  const fallback: SuggestedQuery[] = type === "branded"
    ? [
        { query: `${brand.name} reviews`, intent: "commercial", targetLocation: primaryCity },
        { query: `is ${brand.name} good`, intent: "commercial", targetLocation: primaryCity },
        { query: `${brand.name} ${industry}`, intent: "informational", targetLocation: primaryCity },
      ]
    : [
        { query: `best ${industry} in ${primaryCity}`, intent: "local", targetLocation: primaryCity },
        { query: `top ${industry} in ${primaryLoc}`, intent: "commercial", targetLocation: primaryLoc },
        { query: `${industry} cost in ${primaryCity}`, intent: "transactional", targetLocation: primaryCity },
      ];

  const result = await callLlm(
    [{ role: "system", content: "Generate realistic AI search queries. Respond ONLY with a JSON array." }, { role: "user", content: prompt }],
    () => JSON.stringify(fallback),
    { maxTokens: 2000 },
  );
  logAiInteraction({
    brandId,
    feature: "other",
    userPrompt: prompt,
    aiResponse: result.reply,
    model: result.model,
    brandDomain: brand.domain,
  });

  try {
    const raw = result.reply.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
    const arr = JSON.parse(raw);
    if (!Array.isArray(arr)) throw new Error("not array");
    return (arr as SuggestedQuery[])
      .filter((q) => q && typeof q.query === "string")
      .map((q) => ({
        query: q.query,
        intent: (["informational", "commercial", "local", "transactional"] as const).includes(q.intent as never)
          ? q.intent
          : "commercial",
        targetLocation: q.targetLocation || primaryCity,
      }))
      .slice(0, 20);
  } catch { return fallback; }
}

export async function checkAiVisibility(brandId: string, query: string): Promise<AiCheckResult> {
  const brand = await prisma.brand.findUnique({ where: { id: brandId }, select: { name: true, domain: true, industry: true } });
  if (!brand) throw new Error("Brand not found");

  // Resolve the brand's primary service location
  const location = await getPrimaryLocation(brandId);

  // Determine if this is a geo query and whether it's branded
  const isBranded = brand.name && query.toLowerCase().includes(brand.name.toLowerCase());
  const isGeoQuery = GEO_PATTERN.test(query);

  // Build the actual query sent to the LLM:
  // - Branded queries: send as-is (brand name is context enough)
  // - Geo queries: replace "near me" with actual location, add location to system prompt
  let resolvedQuery = query;
  let locationUsed: string | null = null;

  if (!isBranded && isGeoQuery && location) {
    // Replace "near me" / "in my area" with the actual location
    if (NEAR_ME_REPLACE.test(query)) {
      resolvedQuery = query.replace(NEAR_ME_REPLACE, `in ${location}`);
    } else if (!/\bin [A-Z]/.test(query)) {
      // Query has geo intent but no specific city — append location
      resolvedQuery = `${query} in ${location}`;
    }
    locationUsed = location;
  } else if (!isBranded && location) {
    // Non-geo query but we have a location — add as system context
    locationUsed = location;
  }

  // Phase 1: Get an unbiased AI response WITH web search enabled.
  // Without web search, the LLM responds with "I don't have specific
  // information" for every brand query, making the feature useless.
  const systemParts = [
    "You are a helpful AI assistant. Search the web and answer the user's question with specific, factual information.",
    "Include real business names, addresses, phone numbers, and services when available.",
    "Cite your sources.",
  ];
  if (locationUsed && !isBranded) {
    systemParts.push(`The user is located in ${locationUsed}.`);
  }

  let fullResponse: string;
  let sourcesUsed: string[] = [];
  const apiKey = process.env.ANTHROPIC_API_KEY?.trim();

  if (apiKey) {
    // Use Anthropic API directly with web_search tool
    try {
      const res = await fetch("https://api.anthropic.com/v1/messages", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-api-key": apiKey,
          "anthropic-version": "2023-06-01",
        },
        body: JSON.stringify({
          model: process.env.AI_MODEL_ID || "claude-sonnet-4-20250514",
          max_tokens: 2000,
          system: systemParts.join(" "),
          tools: [{ type: "web_search_20250305", name: "web_search" }],
          messages: [{ role: "user", content: resolvedQuery }],
        }),
      });

      if (res.ok) {
        const data = await res.json();
        // Extract text and source URLs from the response
        const textParts: string[] = [];
        for (const block of data.content ?? []) {
          if (block.type === "text") textParts.push(block.text);
          if (block.type === "web_search_tool_result") {
            for (const r of block.search_results ?? []) {
              if (r.url) sourcesUsed.push(r.url);
            }
          }
        }
        fullResponse = textParts.join("\n\n") || "No response generated.";
      } else {
        // Fallback to callLlm without web search
        const fallback = await callLlm(
          [{ role: "system", content: systemParts.join(" ") }, { role: "user", content: resolvedQuery }],
          () => "I don't have enough information to answer this question specifically.",
          { maxTokens: 2000 },
        );
        fullResponse = fallback.reply;
      }
    } catch {
      const fallback = await callLlm(
        [{ role: "system", content: systemParts.join(" ") }, { role: "user", content: resolvedQuery }],
        () => "I don't have enough information to answer this question specifically.",
        { maxTokens: 2000 },
      );
      fullResponse = fallback.reply;
    }
  } else {
    const fallback = await callLlm(
      [{ role: "system", content: systemParts.join(" ") }, { role: "user", content: resolvedQuery }],
      () => "I don't have enough information to answer this question specifically.",
      { maxTokens: 2000 },
    );
    fullResponse = fallback.reply;
  }

  logAiInteraction({
    brandId,
    feature: "other",
    userPrompt: resolvedQuery,
    systemPrompt: systemParts.join(" "),
    aiResponse: fullResponse,
    model: process.env.AI_MODEL_ID || "claude-sonnet-4-20250514",
    brandDomain: brand.domain,
  });

  // Phase 2: Analyze the response for brand mentions — done programmatically,
  // not by asking the LLM (which would re-introduce bias).
  const brandName = brand.name.toLowerCase();
  const brandDomain = (brand.domain || "").toLowerCase().replace(/^www\./, "");
  const responseLC = fullResponse.toLowerCase();

  // Check for brand name or domain mention
  const nameIdx = responseLC.indexOf(brandName);
  const domainIdx = brandDomain ? responseLC.indexOf(brandDomain) : -1;
  const mentioned = nameIdx >= 0 || domainIdx >= 0;

  // Determine position: count distinct business/brand names mentioned before this one
  let position: number | null = null;
  let context: string | null = null;
  if (mentioned) {
    const mentionIdx = nameIdx >= 0 ? nameIdx : domainIdx;
    // Count numbered list items or bold names before the mention as rough position
    const textBefore = fullResponse.slice(0, mentionIdx);
    const listItems = textBefore.match(/^\d+[\.\)]/gm) || [];
    position = Math.max(1, listItems.length + 1);

    // Extract the sentence containing the mention
    const mentionStart = Math.max(0, mentionIdx - 80);
    const mentionEnd = Math.min(fullResponse.length, mentionIdx + brandName.length + 80);
    context = fullResponse.slice(mentionStart, mentionEnd).replace(/\n/g, " ").trim();
  }

  // Simple sentiment from the surrounding context
  let sentiment: string | null = null;
  if (context) {
    const positiveWords = /excellent|great|best|top|leading|recommend|trusted|renowned|outstanding|highly rated/i;
    const negativeWords = /poor|avoid|bad|worst|complaints|issues|problems|concerns|controversial/i;
    if (positiveWords.test(context)) sentiment = "positive";
    else if (negativeWords.test(context)) sentiment = "negative";
    else sentiment = "neutral";
  }

  // Extract competitor names: look for other business names mentioned in the response.
  // Use the LLM for this limited task since it requires understanding which words are business names.
  let competitors: string[] = [];
  let improvementTips: string[] = [];
  try {
    const analysisResult = await callLlm(
      [
        { role: "system", content: "Extract data from the text. Respond ONLY with JSON." },
        { role: "user", content: `From this AI response, extract:\n1. Names of specific businesses/companies/brands mentioned (exclude "${brand.name}")\n2. 1-2 tips for "${brand.name}" to get mentioned in responses like this\n\nResponse:\n${fullResponse.slice(0, 1500)}\n\nReturn JSON: {"competitors":["name1"],"tips":["tip1"]}` },
      ],
      () => JSON.stringify({ competitors: [], tips: [] }),
      { maxTokens: 500 },
    );
    const parsed = JSON.parse(extractJson(analysisResult.reply));
    competitors = (parsed.competitors || []).filter((c: string) => c.toLowerCase() !== brandName);
    improvementTips = parsed.tips || [];
  } catch { /* extraction failed — use empty arrays */ }

  const checkResult: AiCheckResult = {
    mentioned, position, context, fullResponse,
    sentiment, competitors, improvementTips, locationUsed,
    sourcesUsed: sourcesUsed.length > 0 ? sourcesUsed : undefined,
    webSearchEnabled: !!apiKey,
  };

  await prisma.aiVisibilityCheck.create({
    data: { brandId, query, platform: "claude", mentioned: checkResult.mentioned, position: checkResult.position, context: checkResult.context, fullResponse: checkResult.fullResponse, sentiment: checkResult.sentiment, competitors: checkResult.competitors },
  });

  return checkResult;
}

export interface AuditSummary {
  totalQueries: number; mentionedCount: number; mentionRate: number;
  avgPosition: number | null; sentimentBreakdown: { positive: number; neutral: number; negative: number };
  topCompetitors: Array<{ name: string; count: number }>; score: number;
}

export async function runVisibilityAudit(brandId: string): Promise<AuditSummary> {
  const queries = await prisma.aiVisibilityQuery.findMany({ where: { brandId, isActive: true }, select: { query: true } });
  const results: AiCheckResult[] = [];
  for (const q of queries.slice(0, 20)) {
    try { results.push(await checkAiVisibility(brandId, q.query)); } catch { /* skip */ }
  }

  const mentioned = results.filter((r) => r.mentioned);
  const positions = mentioned.map((r) => r.position).filter((p): p is number => p != null);
  const sentiments = { positive: 0, neutral: 0, negative: 0 };
  mentioned.forEach((r) => { if (r.sentiment === "positive") sentiments.positive++; else if (r.sentiment === "negative") sentiments.negative++; else sentiments.neutral++; });
  const compCounts: Record<string, number> = {};
  results.forEach((r) => r.competitors.forEach((c) => { compCounts[c] = (compCounts[c] || 0) + 1; }));
  const topCompetitors = Object.entries(compCounts).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count }));
  const mentionRate = results.length > 0 ? mentioned.length / results.length : 0;
  const avgPosition = positions.length > 0 ? positions.reduce((a, b) => a + b, 0) / positions.length : null;
  const score = Math.round(mentionRate * 70 + (avgPosition ? Math.max(0, (5 - avgPosition) / 5 * 30) : 0));

  return { totalQueries: results.length, mentionedCount: mentioned.length, mentionRate, avgPosition, sentimentBreakdown: sentiments, topCompetitors, score: Math.min(100, Math.max(0, score)) };
}

export async function getVisibilityRecommendations(brandId: string): Promise<string> {
  const checks = await prisma.aiVisibilityCheck.findMany({ where: { brandId }, orderBy: { checkedAt: "desc" }, take: 50, select: { query: true, mentioned: true, position: true, competitors: true } });
  const total = checks.length || 1;
  const mentioned = checks.filter((c) => c.mentioned).length;
  const missingQueries = checks.filter((c) => !c.mentioned).map((c) => c.query).slice(0, 5);
  const allComps = checks.flatMap((c) => (c.competitors as string[]) || []);
  const compCounts: Record<string, number> = {};
  allComps.forEach((c) => { compCounts[c] = (compCounts[c] || 0) + 1; });
  const topComps = Object.entries(compCounts).sort((a, b) => b[1] - a[1]).slice(0, 5);
  const brand = await prisma.brand.findUnique({ where: { id: brandId }, select: { name: true } });

  const prompt = `Based on AI visibility results for ${brand?.name || "this brand"}:
- Mentioned in ${mentioned}/${total} queries (${Math.round(mentioned / total * 100)}%)
- Missing from: ${missingQueries.join(", ") || "none"}
- Top competitors in AI answers: ${topComps.map(([n, c]) => `${n}(${c})`).join(", ") || "none"}

Provide 3-5 specific, actionable recommendations to improve AI visibility. Be concise.`;

  const result = await callLlm(
    [{ role: "system", content: "You are an AI visibility expert. Be specific and actionable." }, { role: "user", content: prompt }],
    () => "Configure API key for real recommendations.",
    { maxTokens: 2000 },
  );
  logAiInteraction({
    brandId,
    feature: "other",
    userPrompt: prompt,
    aiResponse: result.reply,
    model: result.model,
  });
  return result.reply;
}
