/**
 * Audit Scoring Engine
 * ─────────────────────
 * Deterministic dimension scoring and headline health score derivation.
 * All scoring is code-side — no AI model calls.
 *
 * Dimension weights (both audit types):
 *   tech×0.40 + content×0.20 + aiVis×0.20 + conv×0.10 + eng×0.10
 *
 * Source of each dimension varies by audit type:
 *   1st-party: aiVis from Site Tag referral telemetry; conv/eng from behavioral + RUM data
 *   3rd-party: aiVis from public SERP + schema + authority signals; conv/eng from crawl + content proxies
 *
 * Hard caps:
 *   - aiVis (1st-party), technicalSeo, contentMetadata: 90
 *   - aiVis (3rd-party), conversionOptimization, userEngagement: 70 (proxy data; no upper-bound certainty)
 *   - Degraded-mode dimensions (no crawl data) default to 55.
 */

import type {
  SeoFindings,
  DimensionScores,
  AiAttributionContext,
  BehavioralContext,
  WebVitalEntry,
} from "../types";

const DIMENSION_CAP = 90;

// ─── Technical SEO ────────────────────────────────────────────────────────────

function scoreCwv(webVitalsContext: WebVitalEntry[]): number {
  if (webVitalsContext.length === 0) return 60;
  const metricScores: number[] = [];
  for (const entry of webVitalsContext) {
    if (entry.lcp75 != null) metricScores.push(entry.lcp75 < 2500 ? 100 : entry.lcp75 < 4000 ? 60 : 20);
    if (entry.inp75 != null) metricScores.push(entry.inp75 < 200 ? 100 : entry.inp75 < 500 ? 60 : 20);
    if (entry.cls75 != null) metricScores.push(entry.cls75 < 0.1 ? 100 : entry.cls75 < 0.25 ? 60 : 20);
    if (entry.ttfb75 != null) metricScores.push(entry.ttfb75 < 800 ? 100 : entry.ttfb75 < 1800 ? 60 : 20);
  }
  if (metricScores.length === 0) return 60;
  return Math.round(metricScores.reduce((a, b) => a + b, 0) / metricScores.length);
}

function scoreCrawlability(findings: SeoFindings): number {
  let score = 100;
  if (!findings.technical.hasSitemap) score -= 20;
  if (!findings.technical.hasRobotsTxt) score -= 15;
  if (findings.technical.mobileFriendly === false) score -= 20;
  score -= Math.min(20, findings.brokenLinks.total * 4);
  score -= Math.min(10, findings.technical.redirectChains.length * 2);
  score -= Math.min(15, findings.technical.nonIndexablePages * 3);
  return Math.max(0, score);
}

export function scoreTechnicalSeo(
  findings: SeoFindings,
  webVitalsContext: WebVitalEntry[] = [],
  degradedMode = false,
): number {
  // No crawl data available — return conservative default
  if (degradedMode || findings.metaTitles.total === 0) return 55;

  const totalPages = Math.max(1, findings.metaTitles.total);
  const schemaScore = findings.schema.percentCoverage;
  const cwvScore = scoreCwv(webVitalsContext);
  const crawlability = scoreCrawlability(findings);
  const h1Score = Math.round(Math.max(0, 100 - (findings.headings.missingH1 / totalPages) * 100));
  const metaDescScore = Math.round(Math.max(0, 100 - (findings.metaDescriptions.missing / totalPages) * 100));

  const raw = Math.round(
    schemaScore * 0.25 +
    cwvScore * 0.25 +
    crawlability * 0.20 +
    h1Score * 0.15 +
    metaDescScore * 0.15,
  );
  return Math.max(0, Math.min(DIMENSION_CAP, raw));
}

// ─── Content & Metadata ───────────────────────────────────────────────────────

export function scoreContentMetadata(findings: SeoFindings, degradedMode = false): number {
  // No crawl data available — return conservative default
  if (degradedMode || findings.metaTitles.total === 0) return 55;

  const totalPages = Math.max(1, findings.metaTitles.total);
  let score = 100;
  score -= (findings.metaTitles.missing / totalPages) * 30;
  score -= (findings.metaDescriptions.missing / totalPages) * 25;
  score -= (findings.headings.missingH1 / totalPages) * 20;
  score -= (findings.content.thinContentPages.length / Math.max(1, findings.content.totalContentPages)) * 25;
  return Math.max(0, Math.min(DIMENSION_CAP, Math.round(score)));
}

// ─── AI Visibility (1st-party only) ──────────────────────────────────────────

export function scoreAiVisibility(ctx: AiAttributionContext): number {
  const referrals = ctx.totalAiReferrals;

  // 50% — absolute volume tiers
  const volumeScore =
    referrals === 0 ? 5 :
    referrals <= 10 ? 20 :
    referrals <= 30 ? 40 :
    referrals <= 75 ? 55 :
    referrals <= 150 ? 70 :
    referrals <= 500 ? 82 : 88;

  // 25% — engine diversity
  const engineCount = ctx.topEngines.length;
  const diversityScore = engineCount <= 1 ? 30 : engineCount <= 3 ? 60 : 85;

  // 25% — AI conversion rate
  const convRate = referrals > 0 ? (ctx.totalConversions30d / referrals) * 100 : 0;
  const convScore = convRate === 0 ? 30 : convRate < 3 ? 60 : 85;

  let score = volumeScore * 0.50 + diversityScore * 0.25 + convScore * 0.25;

  // Growth modifier: +5 if 50-200% growth, +10 if > 200% growth
  if (ctx.trendRatio !== null && ctx.trendRatio > 1.5) {
    score += ctx.trendRatio > 3 ? 10 : 5;
  }

  return Math.max(0, Math.min(DIMENSION_CAP, Math.round(score)));
}

// ─── Conversion Optimization (1st-party only) ─────────────────────────────────
//
// Three-component rubric:
//   40% — total conversion volume (tiered, log-diminishing)
//   30% — channel diversity (AI share of total conversions as a proxy)
//   30% — AI conversion rate (organic + AI channels' quality signal)
//
// This rubric penalizes channel concentration: a brand with 1,700 conversions
// entirely from one paid channel will score ~60, not 100.

export function scoreConversionOptimization(
  ctx: BehavioralContext,
  aiCtx?: AiAttributionContext,
): number {
  const totalConversions = ctx.pages.reduce((sum, p) => sum + p.conversionCount, 0);

  if (totalConversions === 0) return 10;

  // Volume component (40%) — high volume helps but has diminishing returns
  const volumeScore =
    totalConversions <= 10 ? 30 :
    totalConversions <= 50 ? 50 :
    totalConversions <= 200 ? 65 :
    totalConversions <= 500 ? 75 :
    totalConversions <= 1000 ? 80 : 85;

  // Channel diversity (30%) — uses AI conversion share as paid-vs-organic proxy
  let diversityScore = 60;
  if (aiCtx) {
    const aiConversions = aiCtx.totalConversions30d;
    const aiShare = totalConversions > 0 ? aiConversions / totalConversions : 0;
    diversityScore =
      (aiShare < 0.01 && totalConversions > 50) ? 30 :  // strong paid concentration
      aiShare < 0.05 ? 50 :
      aiShare < 0.15 ? 70 : 88;
  }

  // AI conversion rate (30%) — quality of organic+AI funnel
  let aiRateScore = 60;
  if (aiCtx && aiCtx.totalAiReferrals > 0) {
    const aiRate = (aiCtx.totalConversions30d / aiCtx.totalAiReferrals) * 100;
    aiRateScore =
      aiRate === 0 ? 20 :
      aiRate < 1 ? 35 :
      aiRate < 3 ? 55 :
      aiRate < 7 ? 75 : 88;
  }

  const raw = volumeScore * 0.40 + diversityScore * 0.30 + aiRateScore * 0.30;
  return Math.max(5, Math.min(DIMENSION_CAP, Math.round(raw)));
}

// ─── User Engagement (1st-party only) ────────────────────────────────────────

export function scoreUserEngagement(ctx: BehavioralContext): number {
  if (ctx.pages.length === 0) return 20;

  const withBounce = ctx.pages.filter((p) => p.bounceRate !== null);
  const avgBounce = withBounce.length > 0
    ? withBounce.reduce((s, p) => s + p.bounceRate!, 0) / withBounce.length
    : null;

  const withDuration = ctx.pages.filter((p) => p.avgSessionDuration !== null);
  const avgDuration = withDuration.length > 0
    ? withDuration.reduce((s, p) => s + p.avgSessionDuration!, 0) / withDuration.length
    : null;

  let score = 50;
  if (avgBounce !== null) score += (0.5 - avgBounce) * 50;
  if (avgDuration !== null) score += Math.min(20, Math.max(-15, (avgDuration - 30) / 6));
  return Math.max(0, Math.min(DIMENSION_CAP, Math.round(score)));
}

// ─── AI Visibility (3rd-party) ───────────────────────────────────────────────
//
// Proxy-based score from public SERP data, schema coverage, and domain authority.
// Cap at 70 — no first-party referral data means no upper-bound certainty.
//
// Components:
//   40% AI Overview presence rate (0%→0, 30%+→70, linear)
//   30% Brand citation rate in AI Overviews (0%→0, 50%+→70, linear)
//   20% Schema AI-readiness (priority schema types present vs industry baseline)
//   10% Domain authority proxy (log-scale referringDomains + totalBacklinks)

const AI_VIS_3RD_CAP = 70;

export interface ThirdPartyAiVisibilityInput {
  aiOverviewCoveragePercent: number;    // 0-100: % of tracked queries where AI Overview surfaced
  aiOverviewBrandCitationRate: number;  // 0-100: % of those AI Overviews citing the brand
  queriesWithAiOverview: number;        // raw count used for rationale
  totalQueriesChecked: number;
  schemaTypesDetected: string[];        // schema types found on the site
  schemaPercentCoverage: number;        // raw crawl coverage % (fallback when no schemaPriorities)
  schemaPriorities: string[];           // industry-priority types (empty = use raw coverage)
  referringDomains: number | null;
  totalBacklinks: number | null;
}

export function scoreAiVisibilityThirdParty(input: ThirdPartyAiVisibilityInput): number {
  const {
    aiOverviewCoveragePercent, aiOverviewBrandCitationRate, queriesWithAiOverview,
    schemaTypesDetected, schemaPercentCoverage, schemaPriorities,
    referringDomains, totalBacklinks,
  } = input;

  // 40% — presence rate: linear 0%→0, 30%+→AI_VIS_3RD_CAP
  const presenceScore = Math.min(AI_VIS_3RD_CAP, Math.round((aiOverviewCoveragePercent / 30) * AI_VIS_3RD_CAP));

  // 30% — citation rate: linear 0%→0, 50%+→AI_VIS_3RD_CAP; 0 if no overviews surfaced
  const citationScore = queriesWithAiOverview === 0
    ? 0
    : Math.min(AI_VIS_3RD_CAP, Math.round((aiOverviewBrandCitationRate / 50) * AI_VIS_3RD_CAP));

  // 20% — schema AI-readiness
  let schemaScore: number;
  if (schemaPriorities.length > 0) {
    const detectedLower = new Set(schemaTypesDetected.map((t) => t.toLowerCase()));
    const matched = schemaPriorities.filter((p) => detectedLower.has(p.toLowerCase())).length;
    schemaScore = Math.round((matched / schemaPriorities.length) * AI_VIS_3RD_CAP);
  } else {
    schemaScore = Math.round((schemaPercentCoverage / 100) * AI_VIS_3RD_CAP);
  }

  // 10% — domain authority proxy
  let authorityScore = 30;
  if (referringDomains != null && totalBacklinks != null && referringDomains > 0 && totalBacklinks > 0) {
    authorityScore = Math.min(AI_VIS_3RD_CAP, Math.round(Math.log10(referringDomains) * 15 + Math.log10(totalBacklinks) * 10));
  } else if (referringDomains != null && referringDomains > 0) {
    authorityScore = Math.min(AI_VIS_3RD_CAP, Math.round(Math.log10(referringDomains) * 20));
  }

  const raw = presenceScore * 0.40 + citationScore * 0.30 + schemaScore * 0.20 + authorityScore * 0.10;
  return Math.max(0, Math.min(AI_VIS_3RD_CAP, Math.round(raw)));
}

// ─── Conversion Optimization (3rd-party) ─────────────────────────────────────
//
// Proxy score of conversion architecture quality from public crawl signals.
// Cap at 70 — no measured conversion rate without first-party telemetry.
//
// Components:
//   35% CTA presence (% of crawled pages with a CTA)
//   25% Form usability proxy (CTA variety + click-to-call alternative)
//   20% Click-to-call presence (% of crawled pages with phone link)
//   20% Conversion path completeness vs industry expectation

const CONV_3RD_CAP = 70;

export interface ThirdPartyConversionInput {
  ctaPageCount: number;
  phonePageCount: number;
  totalSamples: number;
  allCtaExamples: string[];
  conversionPathExpectation: string[];
}

export function scoreConversionThirdParty(input: ThirdPartyConversionInput): number {
  const { ctaPageCount, phonePageCount, totalSamples, allCtaExamples, conversionPathExpectation } = input;

  if (totalSamples === 0) return 25;

  // 35% — CTA presence: linear 0%→0, 100%→CONV_3RD_CAP
  const ctaPct = (ctaPageCount / totalSamples) * 100;
  const ctaScore = Math.round((ctaPct / 100) * CONV_3RD_CAP);

  // 25% — Form usability proxy: CTA type diversity + phone alternative
  const phonePresencePct = (phonePageCount / totalSamples) * 100;
  const hasFormLikeCta = allCtaExamples.some((ex) =>
    /\b(contact|request|book|schedule|apply|sign up|register|get a quote|free consultation)\b/i.test(ex),
  );
  let formScore: number;
  if (ctaPageCount === 0) {
    formScore = 0;
  } else if (!hasFormLikeCta && phonePresencePct < 20) {
    formScore = 20;
  } else if (hasFormLikeCta && phonePresencePct < 20) {
    formScore = 40;
  } else if (hasFormLikeCta && phonePresencePct < 60) {
    formScore = 55;
  } else {
    formScore = CONV_3RD_CAP;
  }

  // 20% — Click-to-call: linear 0%→0, 100%→CONV_3RD_CAP
  const phoneScore = Math.round((phonePresencePct / 100) * CONV_3RD_CAP);

  // 20% — Conversion path completeness vs industry expectation
  let pathScore = 35;
  if (conversionPathExpectation.length > 0 && allCtaExamples.length > 0) {
    const ctaLower = allCtaExamples.map((s) => s.toLowerCase());
    const matched = conversionPathExpectation.filter((exp) => {
      const keyword = exp.toLowerCase().split(" ")[0];
      return ctaLower.some((cta) => cta.includes(keyword));
    }).length;
    pathScore = Math.round((matched / conversionPathExpectation.length) * CONV_3RD_CAP);
  }

  const raw = ctaScore * 0.35 + formScore * 0.25 + phoneScore * 0.20 + pathScore * 0.20;
  return Math.max(0, Math.min(CONV_3RD_CAP, Math.round(raw)));
}

// ─── User Engagement (3rd-party) ──────────────────────────────────────────────
//
// Proxy + estimation from content depth, page breadth, and internal linking.
// CWV component returns a fixed neutral (30) since 3rd-party audits have no
// real-user measurement data — it degrades to content/structure signals only.
// Cap at 70.
//
// Components:
//   40% Core Web Vitals P75 (no 3rd-party RUM → 30 neutral)
//   30% Content depth proxy (avgWordCount vs 800-word ideal)
//   15% Page count breadth (vs SMB/mid/enterprise baselines)
//   15% Internal linking density (avg inbound internal links per page)

const ENG_3RD_CAP = 70;
const WORD_COUNT_IDEAL = 800;

export interface ThirdPartyEngagementInput {
  avgWordCount: number;
  totalPages: number;
  avgInternalLinks: number;
}

export function scoreEngagementThirdParty(input: ThirdPartyEngagementInput): number {
  const { avgWordCount, totalPages, avgInternalLinks } = input;

  // 40% — CWV: no 3rd-party real-user data → 30 neutral
  const cwvScore = 30;

  // 30% — Content depth: linear 0→0, WORD_COUNT_IDEAL→ENG_3RD_CAP
  const contentScore = Math.min(ENG_3RD_CAP, Math.round((avgWordCount / WORD_COUNT_IDEAL) * ENG_3RD_CAP));

  // 15% — Page breadth
  const pageScore =
    totalPages <= 5 ? 0 :
    totalPages <= 10 ? 20 :
    totalPages <= 20 ? 40 :
    totalPages <= 100 ? 60 : ENG_3RD_CAP;

  // 15% — Internal linking density
  const linkScore =
    avgInternalLinks < 1 ? 0 :
    avgInternalLinks < 3 ? 20 :
    avgInternalLinks < 7 ? 45 :
    avgInternalLinks < 10 ? 60 : ENG_3RD_CAP;

  const raw = cwvScore * 0.40 + contentScore * 0.30 + pageScore * 0.15 + linkScore * 0.15;
  return Math.max(0, Math.min(ENG_3RD_CAP, Math.round(raw)));
}

// ─── Composite scoring ────────────────────────────────────────────────────────

export interface ThirdPartyDimensionInput {
  aiVisibility: ThirdPartyAiVisibilityInput;
  conversion: ThirdPartyConversionInput;
  engagement: ThirdPartyEngagementInput;
}

export function computeDimensionScores(
  findings: SeoFindings,
  aiAttributionContext?: AiAttributionContext,
  behavioralContext?: BehavioralContext,
  webVitalsContext?: WebVitalEntry[],
  degradedMode = false,
): DimensionScores {
  return {
    technicalSeo: scoreTechnicalSeo(findings, webVitalsContext ?? [], degradedMode),
    contentMetadata: scoreContentMetadata(findings, degradedMode),
    aiVisibility: aiAttributionContext !== undefined ? scoreAiVisibility(aiAttributionContext) : null,
    conversionOptimization: behavioralContext !== undefined
      ? scoreConversionOptimization(behavioralContext, aiAttributionContext)
      : null,
    userEngagement: behavioralContext !== undefined ? scoreUserEngagement(behavioralContext) : null,
  };
}

export function computeThirdPartyDimensionScores(
  findings: SeoFindings,
  thirdParty: ThirdPartyDimensionInput,
  webVitalsContext: WebVitalEntry[] = [],
  degradedMode = false,
): DimensionScores {
  return {
    technicalSeo: scoreTechnicalSeo(findings, webVitalsContext, degradedMode),
    contentMetadata: scoreContentMetadata(findings, degradedMode),
    aiVisibility: scoreAiVisibilityThirdParty(thirdParty.aiVisibility),
    conversionOptimization: scoreConversionThirdParty(thirdParty.conversion),
    userEngagement: scoreEngagementThirdParty(thirdParty.engagement),
  };
}

export function computeHealthScore(dims: DimensionScores): number {
  if (dims.aiVisibility !== null && dims.conversionOptimization !== null && dims.userEngagement !== null) {
    return Math.round(
      dims.technicalSeo * 0.40 +
      dims.contentMetadata * 0.20 +
      dims.aiVisibility * 0.20 +
      dims.conversionOptimization * 0.10 +
      dims.userEngagement * 0.10,
    );
  }
  // Legacy fallback for audits stored before 3rd-party scoring was wired (null dimensions)
  return Math.round(dims.technicalSeo * 0.60 + dims.contentMetadata * 0.40);
}

/**
 * Maximum achievable headline score for a given audit type, given the dimension caps.
 *
 * 1st-party: all 5 dimensions cap at 90 → max = 90 × 1.00 = 90
 * 3rd-party: Tech + Content cap at 90, AI Vis + Conv + Eng cap at 70
 *            max = 90×0.40 + 90×0.20 + 70×0.20 + 70×0.10 + 70×0.10 = 82
 */
export function getMaxPossibleScore(auditType: "1st" | "3rd"): number {
  if (auditType === "1st") return 90;
  return Math.round(90 * 0.40 + 90 * 0.20 + 70 * 0.20 + 70 * 0.10 + 70 * 0.10); // 82
}
