/**
 * Bilingual Content Audit — Aggregation Service
 * ───────────────────────────────────────────────
 * Analyzes multi-language signals from crawl and content data to produce
 * a BilingualAudit object. Works on already-loaded data — no external API calls.
 *
 * Internal module — no third-party vendor names in user-facing output.
 */

import { Prisma } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import type { BilingualAudit, PageData, AiAttributionContext } from "../types";
import type { ContentSample } from "../scrapers/firecrawl-content";

// ─── Language registry ────────────────────────────────────────────────────────

const LANGUAGE_DISPLAY: Record<string, string> = {
  es: "Spanish", fr: "French", de: "German", pt: "Portuguese", it: "Italian",
  zh: "Chinese", ja: "Japanese", ko: "Korean", ar: "Arabic", nl: "Dutch",
  sv: "Swedish", no: "Norwegian", da: "Danish", fi: "Finnish", pl: "Polish",
  ru: "Russian", tr: "Turkish", hi: "Hindi", en: "English",
  "es-mx": "Spanish (Mexico)", "es-us": "Spanish (US)", "pt-br": "Portuguese (Brazil)",
};

// Supported non-English language codes to detect
const DETECT_CODES = ["es", "fr", "de", "pt", "it", "zh", "ja", "ko", "ar", "nl", "sv", "no", "da", "fi", "pl", "ru", "tr", "hi"];

// Regex to extract language code from URL pathname prefix
const LANG_PREFIX_RE = /^\/([a-z]{2}(?:-[a-z]{2})?)(?:\/|$)/i;

// ─── URL language detection ───────────────────────────────────────────────────

function detectUrlLanguage(url: string): string | null {
  try {
    const pathname = new URL(url).pathname;
    const m = pathname.match(LANG_PREFIX_RE);
    if (!m) return null;
    const code = m[1].toLowerCase();
    // Only return if it's a recognized language code (not a generic path segment)
    if (LANGUAGE_DISPLAY[code] || DETECT_CODES.includes(code)) return code;
    return null;
  } catch {
    return null;
  }
}

// Build a canonical key for matching primary/non-primary pages
function canonicalPath(url: string): string {
  try {
    const u = new URL(url);
    const m = u.pathname.match(LANG_PREFIX_RE);
    const path = m ? u.pathname.slice(("/" + m[1]).length) || "/" : u.pathname;
    return path.toLowerCase().replace(/\/$/, "") || "/";
  } catch {
    return url;
  }
}

// ─── Hreflang detection from markdown content ─────────────────────────────────

interface HreflangSignals {
  pagesWithHreflang: number;
  hasMissingSelfRef: boolean;
  hasMissingXDefault: boolean;
  hasUnidirectional: boolean;
  affectedUrls: string[];
}

function detectHreflangSignals(contentSamples: ContentSample[]): HreflangSignals {
  const pagesWithHreflang: string[] = [];
  let hasMissingSelfRef = false;
  let hasMissingXDefault = false;
  const hasUnidirectional = false;

  for (const sample of contentSamples) {
    const md = sample.markdown;
    // Look for hreflang in markdown (may appear as attribute in embedded HTML or as text)
    if (/hreflang/i.test(md)) {
      pagesWithHreflang.push(sample.url);
      if (!/hreflang\s*=\s*["']?x-default/i.test(md)) hasMissingXDefault = true;
    }
  }

  // hasMissingSelfRef not detectable from markdown alone — default false
  hasMissingSelfRef = false;

  return {
    pagesWithHreflang: pagesWithHreflang.length,
    hasMissingSelfRef,
    hasMissingXDefault,
    hasUnidirectional,
    affectedUrls: pagesWithHreflang.slice(0, 5),
  };
}

// ─── Spanish CTA detection ────────────────────────────────────────────────────

const ES_CTA_PATTERNS = [
  /llamar|llame|llámenos|llamenos/i,
  /reservar|reserva\b|reserve\b/i,
  /agendar|agenda\b/i,
  /cita\b|appointmen/i,
  /contáctenos|contactenos|contáctanos|contactanos/i,
  /hable con|habla con/i,
  /programar|programa\b/i,
  /solicitar|solicita\b|solicite\b/i,
  /regístrate|registrate|regístrese|registrese/i,
];

function detectSpanishCta(markdown: string): boolean {
  return ES_CTA_PATTERNS.some((p) => p.test(markdown));
}

// ─── Sub-section A: Language Coverage ────────────────────────────────────────

function computeLanguageCoverage(pages: PageData[]): {
  detectedLanguages: Array<{ code: string; displayName: string; pageCount: number }>;
  primaryLanguage: string;
  languageCoverage: BilingualAudit["languageCoverage"];
  hasBilingualSignal: boolean;
} {
  const counts: Record<string, number> = {};

  for (const page of pages) {
    const lang = detectUrlLanguage(page.url) ?? "en";
    counts[lang] = (counts[lang] ?? 0) + 1;
  }

  // If no pages were detected at all, default to English
  if (Object.keys(counts).length === 0) counts["en"] = 0;

  const total = Object.values(counts).reduce((a, b) => a + b, 0) || 1;
  const sortedEntries = Object.entries(counts).sort((a, b) => b[1] - a[1]);

  const primaryLanguage = sortedEntries[0]?.[0] ?? "en";
  const hasBilingualSignal = Object.keys(counts).some((c) => c !== primaryLanguage && DETECT_CODES.includes(c));

  const detectedLanguages = sortedEntries.map(([code, pageCount]) => ({
    code,
    displayName: LANGUAGE_DISPLAY[code] ?? code.toUpperCase(),
    pageCount,
  }));

  const languageCoverage: BilingualAudit["languageCoverage"] = {
    totalPages: total,
    perLanguage: sortedEntries.map(([code, pageCount]) => ({
      code,
      pageCount,
      sharePercent: Math.round((pageCount / total) * 10000) / 100,
    })),
  };

  return { detectedLanguages, primaryLanguage, languageCoverage, hasBilingualSignal };
}

// ─── Sub-section B: Content Parity ───────────────────────────────────────────

function computeContentParity(
  pages: PageData[],
  primaryLanguage: string,
  nonPrimaryLanguages: string[],
): BilingualAudit["contentParity"] {
  try {
    // Index pages by language + canonical path
    const byLang: Record<string, Set<string>> = {};
    for (const page of pages) {
      const lang = detectUrlLanguage(page.url) ?? primaryLanguage;
      const cp = canonicalPath(page.url);
      if (!byLang[lang]) byLang[lang] = new Set();
      byLang[lang].add(cp);
    }

    const primaryPaths = byLang[primaryLanguage] ?? new Set<string>();
    const primaryPages = pages.filter(
      (p) => (detectUrlLanguage(p.url) ?? primaryLanguage) === primaryLanguage,
    );

    const perNonPrimary = nonPrimaryLanguages.map((code) => {
      const nonPrimaryPaths = byLang[code] ?? new Set<string>();
      let withEquivalent = 0;
      const missingPages: Array<{ primaryUrl: string; expectedNonPrimaryUrl: string }> = [];

      for (const primaryPage of primaryPages) {
        const cp = canonicalPath(primaryPage.url);
        if (nonPrimaryPaths.has(cp)) {
          withEquivalent++;
        } else {
          try {
            const u = new URL(primaryPage.url);
            const expectedPath = "/" + code + (u.pathname === "/" ? "/" : u.pathname);
            u.pathname = expectedPath;
            missingPages.push({ primaryUrl: primaryPage.url, expectedNonPrimaryUrl: u.toString() });
          } catch {
            missingPages.push({
              primaryUrl: primaryPage.url,
              expectedNonPrimaryUrl: `/${code}${primaryPage.url}`,
            });
          }
        }
      }

      const total = primaryPaths.size;
      const missing = total - withEquivalent;
      return {
        code,
        pagesWithEquivalent: withEquivalent,
        pagesMissingEquivalent: Math.max(0, missing),
        parityPercent: total > 0 ? Math.round((withEquivalent / total) * 10000) / 100 : 0,
        topMissingPages: missingPages.slice(0, 20),
      };
    });

    return {
      primaryLanguagePageCount: primaryPaths.size,
      perNonPrimaryLanguage: perNonPrimary,
    };
  } catch (err) {
    console.warn("[bilingual-audit] contentParity failed:", err instanceof Error ? err.message : err);
    return { primaryLanguagePageCount: 0, perNonPrimaryLanguage: [] };
  }
}

// ─── Sub-section C: Hreflang Implementation ──────────────────────────────────

function computeHreflang(
  pages: PageData[],
  contentSamples: ContentSample[],
  nonPrimaryLanguages: string[],
): BilingualAudit["hreflangImplementation"] {
  try {
    const signals = detectHreflangSignals(contentSamples);
    // Pages that "should" have hreflang = pages that exist in 2+ languages
    const pagesShouldHaveHreflang =
      pages.length > 0 && nonPrimaryLanguages.length > 0
        ? Math.round(pages.length * 0.7) // heuristic: most pages in a bilingual site need hreflang
        : 0;

    const bugs: BilingualAudit["hreflangImplementation"]["commonBugs"] = [];

    if (signals.hasMissingXDefault) {
      bugs.push({
        type: "missing_x_default",
        description:
          "Pages are missing the x-default hreflang attribute used for language fallback targeting.",
        affectedUrls: signals.affectedUrls.slice(0, 5),
      });
    }

    if (signals.pagesWithHreflang === 0) {
      bugs.push({
        type: "missing_self_reference",
        description:
          "No hreflang tags detected in content samples. Pages with multi-language equivalents should include self-referencing hreflang attributes.",
        affectedUrls: [],
      });
    }

    const coveragePercent =
      pagesShouldHaveHreflang > 0
        ? Math.min(
            100,
            Math.round((signals.pagesWithHreflang / pagesShouldHaveHreflang) * 10000) / 100,
          )
        : 0;

    const status: "complete" | "partial" | "missing" =
      signals.pagesWithHreflang === 0
        ? "missing"
        : coveragePercent >= 90
          ? "complete"
          : "partial";

    return {
      pagesWithHreflang: signals.pagesWithHreflang,
      pagesShouldHaveHreflang,
      coveragePercent,
      bidirectionalCheckPassed: signals.pagesWithHreflang > 0 ? !signals.hasUnidirectional : null,
      commonBugs: bugs,
      status,
    };
  } catch (err) {
    console.warn("[bilingual-audit] hreflang failed:", err instanceof Error ? err.message : err);
    return {
      pagesWithHreflang: 0,
      pagesShouldHaveHreflang: 0,
      coveragePercent: 0,
      bidirectionalCheckPassed: null,
      commonBugs: [],
      status: "missing",
    };
  }
}

// ─── Sub-section D: Conversion Pathway (1st party — DB query) ────────────────

type LangConversionRow = {
  langCode: string | null;
  sessions: bigint;
  conversions: bigint;
  phone: bigint;
  form: bigint;
  paperwork: bigint;
  chat: bigint;
};

async function computeConversionPathway(
  brandId: string,
  nonPrimaryLanguages: string[],
  aiAttribution: AiAttributionContext | null,
): Promise<BilingualAudit["conversionPathway"]> {
  try {
    const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

    // Query sessions and conversions by language prefix.
    // TrackedSession links to TrackedSite (which holds brandId), not to Brand directly.
    // entryPage holds the first page URL; we extract the language prefix from it.
    // SiteConversion links to Brand via brandId and to session via sessionId (string, not FK).
    const rows = await Promise.race([
      prisma.$queryRaw<LangConversionRow[]>(Prisma.sql`
        SELECT
          SUBSTRING(ts."entryPage" FROM '^/([a-z]{2}(?:-[a-z]{2})?)/')  AS "langCode",
          COUNT(DISTINCT ts.id)::bigint                                   AS sessions,
          COUNT(DISTINCT sc.id)::bigint                                   AS conversions,
          COUNT(DISTINCT sc.id) FILTER (
            WHERE sc."conversionType" IN ('phone_call', 'phone_number_click')
          )::bigint AS phone,
          COUNT(DISTINCT sc.id) FILTER (
            WHERE sc."conversionType" = 'form_submit'
          )::bigint AS form,
          COUNT(DISTINCT sc.id) FILTER (
            WHERE sc."conversionType" IN ('booking', 'calendly')
          )::bigint AS paperwork,
          COUNT(DISTINCT sc.id) FILTER (
            WHERE sc."conversionType" = 'chat'
          )::bigint AS chat
        FROM "TrackedSite" site
        JOIN "TrackedSession" ts ON ts."trackedSiteId" = site.id
        LEFT JOIN "SiteConversion" sc
          ON sc."sessionId" = ts."sessionId"
          AND sc."brandId" = ${brandId}
          AND sc."timestamp" >= ${since}
        WHERE site."brandId" = ${brandId}
          AND ts."startedAt" >= ${since}
          AND ts."entryPage" ~ '^/[a-z]{2}(?:-[a-z]{2})?/'
        GROUP BY 1
        HAVING SUBSTRING(ts."entryPage" FROM '^/([a-z]{2}(?:-[a-z]{2})?)/') = ANY(${nonPrimaryLanguages})
      `),
      new Promise<LangConversionRow[]>((_, reject) =>
        setTimeout(
          () => reject(new Error("[bilingual-audit] conversionPathway query timed out")),
          10000,
        ),
      ),
    ]);

    const perLang = rows.map((row) => {
      const sessions = Number(row.sessions);
      const conversions = Number(row.conversions);
      const phone = Number(row.phone);
      const form = Number(row.form);
      const paperwork = Number(row.paperwork);
      const chat = Number(row.chat);
      const convTotal = phone + form + paperwork + chat || 1;
      return {
        code: row.langCode ?? "??",
        sessions,
        conversions,
        conversionRate:
          sessions > 0 ? Math.round((conversions / sessions) * 10000) / 100 : 0,
        channelMix: {
          phone: Math.round((phone / convTotal) * 10000) / 100,
          form: Math.round((form / convTotal) * 10000) / 100,
          paperwork: Math.round((paperwork / convTotal) * 10000) / 100,
          chat: Math.round((chat / convTotal) * 10000) / 100,
        },
      };
    });

    // AI attributed conversions by language — derived from in-memory aiAttribution
    const aiByLang: Record<
      string,
      { aiVisits: number; aiConversions: number; engines: Record<string, number> }
    > = {};
    if (aiAttribution) {
      for (const page of aiAttribution.topPages) {
        const lang = detectUrlLanguage(page.url);
        if (!lang || lang === "en") continue;
        if (!aiByLang[lang]) aiByLang[lang] = { aiVisits: 0, aiConversions: 0, engines: {} };
        aiByLang[lang].aiVisits += page.aiVisits;
        aiByLang[lang].aiConversions += page.conversions;
      }
    }

    const aiAttributed = Object.entries(aiByLang).map(([code, data]) => ({
      code,
      aiVisits: data.aiVisits,
      aiConversions: data.aiConversions,
      aiConversionRate:
        data.aiVisits > 0
          ? Math.round((data.aiConversions / data.aiVisits) * 10000) / 100
          : 0,
      topEngines: Object.entries(data.engines)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 3)
        .map(([engine, visits]) => ({ engine, visits, conversions: 0 })),
    }));

    return { perLanguage: perLang, aiAttributed };
  } catch (err) {
    console.warn(
      "[bilingual-audit] conversionPathway failed:",
      err instanceof Error ? err.message : err,
    );
    return null;
  }
}

// ─── Sub-section E: AI Citation Patterns ─────────────────────────────────────

function computeAiCitationByLanguage(
  aiAttribution: AiAttributionContext | null,
  primaryLanguage: string,
): BilingualAudit["aiCitationByLanguage"] {
  try {
    if (!aiAttribution || aiAttribution.topPages.length === 0) return null;

    const totalAiVisits =
      aiAttribution.totalAiReferrals ||
      aiAttribution.topPages.reduce((s, p) => s + p.aiVisits, 0);
    if (totalAiVisits === 0) return null;

    // Group top pages by language
    const byLang: Record<
      string,
      { pages: Array<{ url: string; aiVisits: number }>; totalVisits: number }
    > = {};

    for (const page of aiAttribution.topPages) {
      const lang = detectUrlLanguage(page.url) ?? primaryLanguage;
      if (!byLang[lang]) byLang[lang] = { pages: [], totalVisits: 0 };
      byLang[lang].pages.push({ url: page.url, aiVisits: page.aiVisits });
      byLang[lang].totalVisits += page.aiVisits;
    }

    const perLanguage = Object.entries(byLang)
      .sort((a, b) => b[1].totalVisits - a[1].totalVisits)
      .map(([code, data]) => ({
        code,
        aiVisits: data.totalVisits,
        sharePercent: Math.round((data.totalVisits / totalAiVisits) * 10000) / 100,
        topCitedPages: data.pages.sort((a, b) => b.aiVisits - a.aiVisits).slice(0, 5),
        perEngine: aiAttribution.topEngines.map((e) => ({ engine: e.engine, visits: e.count })),
      }));

    if (perLanguage.length === 0) return null;
    return { perLanguage };
  } catch (err) {
    console.warn(
      "[bilingual-audit] aiCitationByLanguage failed:",
      err instanceof Error ? err.message : err,
    );
    return null;
  }
}

// ─── Sub-section F: Content Quality ──────────────────────────────────────────

function computeContentQuality(
  contentSamples: ContentSample[],
  primaryLanguage: string,
  nonPrimaryLanguages: string[],
): BilingualAudit["contentQuality"] {
  try {
    const primarySamples = contentSamples.filter(
      (s) => (detectUrlLanguage(s.url) ?? primaryLanguage) === primaryLanguage,
    );
    const primaryAvgWordCount =
      primarySamples.length > 0
        ? Math.round(
            primarySamples.reduce((s, p) => s + (p.wordCount ?? 0), 0) / primarySamples.length,
          )
        : 0;

    const perNonPrimary = nonPrimaryLanguages.map((code) => {
      const samples = contentSamples.filter((s) => detectUrlLanguage(s.url) === code);
      const avgWordCount =
        samples.length > 0
          ? Math.round(
              samples.reduce((s, p) => s + (p.wordCount ?? 0), 0) / samples.length,
            )
          : 0;

      const depthRatio =
        primaryAvgWordCount > 0 && avgWordCount > 0
          ? Math.round((avgWordCount / primaryAvgWordCount) * 100) / 100
          : 0;

      // Check for language-specific schema (FAQ schema as proxy)
      const hasLanguageSpecificSchema = samples.some((s) => s.hasFAQ);

      // Check for bilingual CTAs
      const samplesWithCta = samples.filter((s) => {
        if (code === "es") return detectSpanishCta(s.markdown);
        // For other languages, fall back to hasCTA
        return s.hasCTA;
      });

      const ctaStatus: "present" | "partial" | "absent" =
        samplesWithCta.length === samples.length && samples.length > 0
          ? "present"
          : samplesWithCta.length > 0
            ? "partial"
            : "absent";

      const langName = LANGUAGE_DISPLAY[code] ?? code.toUpperCase();
      const ctaRec =
        ctaStatus === "absent"
          ? `${langName}-language pages lack a localized call-to-action. Add ${langName} appointment, contact, or phone prompts to match visitor intent.`
          : ctaStatus === "partial"
            ? `Some ${langName} pages have localized CTAs but coverage is incomplete. Audit remaining ${langName} pages for missing contact pathways.`
            : `${langName} CTAs are present and consistent across sampled pages.`;

      return {
        code,
        avgWordCount,
        primaryLanguageAvgWordCount: primaryAvgWordCount,
        depthRatio,
        hasLanguageSpecificSchema,
        bilingualCtaStatus: ctaStatus,
        bilingualCtaRecommendation: ctaRec,
      };
    });

    return { perNonPrimaryLanguage: perNonPrimary };
  } catch (err) {
    console.warn(
      "[bilingual-audit] contentQuality failed:",
      err instanceof Error ? err.message : err,
    );
    return { perNonPrimaryLanguage: [] };
  }
}

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

export async function aggregateBilingualAudit(
  pages: PageData[],
  contentSamples: ContentSample[],
  aiAttribution: AiAttributionContext | null,
  brandId: string | null,
): Promise<BilingualAudit | null> {
  // A. Determine language coverage and check if bilingual at all
  const { detectedLanguages, primaryLanguage, languageCoverage, hasBilingualSignal } =
    computeLanguageCoverage(pages);

  // Graceful null for monolingual brands — check content samples for hreflang as secondary signal
  if (!hasBilingualSignal) {
    const hasHreflang = contentSamples.some((s) => /hreflang/i.test(s.markdown));
    if (!hasHreflang) return null;
  }

  const nonPrimaryLanguages = detectedLanguages
    .filter((l) => l.code !== primaryLanguage && DETECT_CODES.includes(l.code))
    .map((l) => l.code);

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

  // B–F: compute all sub-sections
  const contentParity = computeContentParity(pages, primaryLanguage, nonPrimaryLanguages);
  const hreflangImplementation = computeHreflang(pages, contentSamples, nonPrimaryLanguages);
  const aiCitationByLanguage = computeAiCitationByLanguage(aiAttribution, primaryLanguage);
  const contentQuality = computeContentQuality(contentSamples, primaryLanguage, nonPrimaryLanguages);

  const conversionPathway = brandId
    ? await computeConversionPathway(brandId, nonPrimaryLanguages, aiAttribution)
    : null;

  // Determine data confidence based on how many sub-sections have non-trivial data
  let populated = 0;
  if (languageCoverage.perLanguage.length > 1) populated++;
  if (
    contentParity.perNonPrimaryLanguage.some(
      (l) => l.pagesWithEquivalent > 0 || l.pagesMissingEquivalent > 0,
    )
  )
    populated++;
  if (hreflangImplementation.pagesWithHreflang > 0 || hreflangImplementation.commonBugs.length > 0)
    populated++;
  if (conversionPathway && conversionPathway.perLanguage.length > 0) populated++;
  if (aiCitationByLanguage && aiCitationByLanguage.perLanguage.length > 0) populated++;
  if (contentQuality.perNonPrimaryLanguage.some((l) => l.avgWordCount > 0)) populated++;

  const dataConfidence: BilingualAudit["dataConfidence"] =
    populated >= 5 ? "high" : populated >= 3 ? "moderate" : "low";

  console.log(
    `[bilingual-audit] detectedLanguages=${detectedLanguages.map((l) => l.code).join(",")} primary=${primaryLanguage} nonPrimary=${nonPrimaryLanguages.join(",")} confidence=${dataConfidence} populated=${populated}/6`,
  );

  return {
    detectedLanguages,
    primaryLanguage,
    dataConfidence,
    languageCoverage,
    contentParity,
    hreflangImplementation,
    conversionPathway,
    aiCitationByLanguage,
    contentQuality,
  };
}
