// ─── Lost Opportunity Calculator ─────────────────────────────────────────────
// Produces directional revenue impact ESTIMATES for each priority finding.
// All outputs are estimates based on industry benchmark data — not measurements.

import { getBenchmarkForIndustry } from "../data/industry-benchmarks";
import type { IndustryContext } from "../synthesizers/industry-classifier";
import type { SeoAction, PrioritizedFinding, AiSnippetEligibility, SchemaOpportunityForecast, LostOpportunityCalculation, LostOpportunityEstimate } from "../types";
import type { SerpAnalysis } from "./serp-analysis";

// ─── Industry monthly query volume baselines (estimated) ──────────────────────
// These are rough baseline figures for total addressable organic query volume
// per month for an average business in each vertical. Used when SERP data is absent.
const INDUSTRY_MONTHLY_QUERY_BASELINE: Record<string, number> = {
  "local-services": 4500,
  "healthcare-medical": 6000,
  "healthcare-dental": 4200,
  "b2b-saas": 12000,
  "ecommerce": 35000,
  "professional-services": 5500,
  "finance": 18000,
  "legal": 7500,
  "real-estate": 9000,
  "nonprofit": 3500,
  "hospitality": 8000,
  "higher-ed": 22000,
  "other": 4000,
};

// ─── Finding-type query volume allocations ────────────────────────────────────
// What fraction of the total monthly query baseline is affected by each finding type.
const FINDING_VOLUME_FRACTION: Record<string, number> = {
  schema: 0.35,          // schema affects rich snippet eligibility across most pages
  content: 0.25,         // content depth affects informational queries
  eeat: 0.40,            // E-E-A-T affects authority-dependent queries broadly
  technical: 0.20,       // technical issues affect all queries proportionally
  snippet: 0.15,         // AI snippet eligibility affects featured snippet queries
  ai_visibility: 0.20,  // AI search visibility affects conversational query set
  local: 0.30,           // local SEO gaps affect geo-targeted queries
  meta: 0.25,            // meta/title optimization affects CTR across pages
  backlink: 0.30,        // authority gaps affect competitive queries
  default: 0.15,
};

// ─── Recoverable traffic % by competitive context ─────────────────────────────
function estimateRecoverablePercent(
  findingType: string,
  serpAnalysis: SerpAnalysis | null,
): number {
  const hasCompetitors = (serpAnalysis?.competitorDomains.length ?? 0) > 0;
  const aiOverviewTotal = serpAnalysis?.aiOverviewPresence.length ?? 0;
  const aiOverviewCount = serpAnalysis?.aiOverviewPresence.filter((a) => a.hasAiOverview).length ?? 0;
  const aiOverviewRate = aiOverviewTotal > 0 ? aiOverviewCount / aiOverviewTotal : 0;

  // Competitive context lowers recoverability (others are already capturing it)
  const competitionMultiplier = hasCompetitors ? 0.75 : 1.0;

  const baseRates: Record<string, number> = {
    schema: aiOverviewRate > 0.5 ? 0.22 : 0.18,
    content: 0.15,
    eeat: 0.20,
    technical: 0.12,
    snippet: 0.25,
    ai_visibility: 0.18,
    local: 0.25,
    meta: 0.20,
    backlink: 0.12,
    default: 0.12,
  };

  return Math.round((baseRates[findingType] ?? baseRates.default) * competitionMultiplier * 100) / 100;
}

// ─── Classify finding type from title/section ─────────────────────────────────
function classifyFindingType(title: string, section: string): string {
  const combined = `${title} ${section}`.toLowerCase();
  if (/schema|structured data|json-ld|faq|how-?to|breadcrumb|review|organization/.test(combined)) return "schema";
  if (/snippet|featured|ai overview|people also/.test(combined)) return "snippet";
  if (/e-e-a-t|eeat|authority|expertise|trust|author|credential/.test(combined)) return "eeat";
  if (/content|thin|word count|depth|topical/.test(combined)) return "content";
  if (/local|geo|location|maps|gbp|google business/.test(combined)) return "local";
  if (/speed|core web vital|lcp|cls|inp|performance|mobile|technical/.test(combined)) return "technical";
  if (/backlink|domain rank|referring domain|authority score/.test(combined)) return "backlink";
  if (/meta|title tag|description|h1|heading/.test(combined)) return "meta";
  if (/ai search|ai engine|conversational|mention rate/.test(combined)) return "ai_visibility";
  return "default";
}

// ─── Time-to-implement from effortHours ──────────────────────────────────────
function effortToLabel(hours?: number): string {
  if (!hours) return "1–2 weeks";
  if (hours <= 4) return "1 day";
  if (hours <= 16) return "2–3 days";
  if (hours <= 40) return "1 week";
  if (hours <= 80) return "2–4 weeks";
  return "4+ weeks";
}

// ─── Per-finding estimate ──────────────────────────────────────────────────────
function estimateForFinding(
  title: string,
  section: string,
  effortHours: number | undefined,
  industryVertical: string,
  avgCustomerValue: number,
  avgConversionRate: number,
  serpAnalysis: SerpAnalysis | null,
  baselineQueryVolume: number,
): LostOpportunityEstimate | null {
  // Skip findings with zero customer value (nonprofit handled separately)
  const effectiveCustomerValue = avgCustomerValue === 0 ? 128 : avgCustomerValue; // $128 avg nonprofit donation
  if (effectiveCustomerValue === 0) return null;

  const findingType = classifyFindingType(title, section);
  const volumeFraction = FINDING_VOLUME_FRACTION[findingType] ?? FINDING_VOLUME_FRACTION.default;
  const affectedVolume = Math.round(baselineQueryVolume * volumeFraction);
  const recoverablePct = estimateRecoverablePercent(findingType, serpAnalysis);
  const recoverableSessions = Math.round(affectedVolume * recoverablePct);
  const conversions = Math.round(recoverableSessions * avgConversionRate * 10) / 10;
  const revenue = Math.round(conversions * effectiveCustomerValue);

  // Skip findings with negligible computed impact
  if (revenue < 50) return null;

  const hasSerpData = (serpAnalysis?.positions.length ?? 0) > 0;
  const confidenceLevel: "high" | "moderate" | "low" =
    hasSerpData && ["schema", "snippet", "meta"].includes(findingType)
      ? "moderate"
      : hasSerpData
        ? "moderate"
        : "low";

  return {
    findingTitle: title,
    findingSection: section,
    affectedQueryVolumeMonthly: affectedVolume,
    estimatedRecoverableTrafficPercent: Math.round(recoverablePct * 100),
    estimatedRecoverableSessionsMonthly: recoverableSessions,
    estimatedConversionsMonthly: conversions,
    estimatedRevenueMonthly: revenue,
    confidenceLevel,
    methodologyNote: `${industryVertical} baseline: ${Math.round(baselineQueryVolume / 1000)}k mo. queries × ${Math.round(volumeFraction * 100)}% finding scope × ${Math.round(recoverablePct * 100)}% recoverable → ${recoverableSessions} sessions × ${(avgConversionRate * 100).toFixed(1)}% CVR × $${effectiveCustomerValue.toLocaleString()} LTV`,
  };
}

// ─── Main aggregation function ────────────────────────────────────────────────

export async function aggregateLostOpportunity(
  industryContext: IndustryContext | null | undefined,
  priorityActions: SeoAction[],
  prioritizedFindings: PrioritizedFinding[] | undefined,
  serpAnalysis: SerpAnalysis | null,
  schemaForecast: SchemaOpportunityForecast | null | undefined,
  aiSnippetEligibility: AiSnippetEligibility | null | undefined,
): Promise<LostOpportunityCalculation | null> {
  if (!industryContext) return null;

  const industry = industryContext.industry;
  const benchmarks = getBenchmarkForIndustry(industry);
  const { avgCustomerValue, avgConversionRate, sourceNote } = benchmarks;

  const baselineVolume = INDUSTRY_MONTHLY_QUERY_BASELINE[industry] ?? INDUSTRY_MONTHLY_QUERY_BASELINE["other"]!;

  // Collect all candidate findings + actions to estimate
  const candidates: Array<{ title: string; section: string; effort?: number }> = [];

  for (const action of priorityActions.slice(0, 8)) {
    candidates.push({
      title: action.title,
      section: "Priority Actions",
      effort: action.effortHours,
    });
  }

  if (prioritizedFindings) {
    for (const f of prioritizedFindings.slice(0, 8)) {
      // Skip if already covered by priority actions
      if (!candidates.some((c) => c.title === f.title)) {
        candidates.push({ title: f.title, section: f.priority ? `${f.priority} priority finding` : "Key Findings" });
      }
    }
  }

  // Additional findings from schema forecast
  if (schemaForecast?.prioritySchemaTypes?.length) {
    const top = schemaForecast.prioritySchemaTypes.find((s) => !s.present);
    if (top && !candidates.some((c) => c.title.includes(top.schemaType))) {
      candidates.push({ title: `${top.schemaType} schema implementation`, section: "Schema Opportunities" });
    }
  }

  // AI snippet eligibility — surface if eligibility rate is low
  if (aiSnippetEligibility && aiSnippetEligibility.aggregateStats.eligibilityRatePercent < 50) {
    candidates.push({ title: "Featured snippet eligibility gap", section: "AI Snippet Eligibility" });
  }

  // Estimate each candidate
  const estimates: LostOpportunityEstimate[] = [];
  const seen = new Set<string>();
  for (const c of candidates) {
    if (seen.has(c.title)) continue;
    seen.add(c.title);
    const est = estimateForFinding(
      c.title,
      c.section,
      c.effort,
      industry,
      avgCustomerValue,
      avgConversionRate,
      serpAnalysis,
      baselineVolume,
    );
    if (est) estimates.push(est);
  }

  if (!estimates.length) return null;

  // Sort by revenue impact descending
  estimates.sort((a, b) => b.estimatedRevenueMonthly - a.estimatedRevenueMonthly);

  const totalMonthly = estimates.reduce((sum, e) => sum + e.estimatedRevenueMonthly, 0);

  // Overall confidence: lowest if any low-confidence finding in top 3; moderate if SERP data available; low otherwise
  const hasSerpData = (serpAnalysis?.positions.length ?? 0) > 0;
  const top3Confidence = estimates.slice(0, 3).map((e) => e.confidenceLevel);
  const overallConfidence: "high" | "moderate" | "low" = top3Confidence.some((c) => c === "low") && !hasSerpData
    ? "low"
    : hasSerpData
      ? "moderate"
      : "low";

  const isNonprofit = avgCustomerValue === 0;

  return {
    hasCalculation: true,
    windowMonths: 1,
    confidence: overallConfidence,
    industryBaselines: {
      industryVertical: industry,
      avgCustomerValue: isNonprofit ? 128 : avgCustomerValue,
      avgConversionRate,
      sourceNote,
    },
    perFindingEstimates: estimates,
    aggregateOpportunity: {
      totalMonthlyOpportunityUsd: totalMonthly,
      annualizedOpportunityUsd: totalMonthly * 12,
      topThreeMonthlyImpact: estimates.slice(0, 3).map((e) => ({
        findingTitle: e.findingTitle,
        monthlyImpactUsd: e.estimatedRevenueMonthly,
        timeToImplement: effortToLabel(priorityActions.find((a) => a.title === e.findingTitle)?.effortHours),
      })),
    },
    disclosures: {
      estimationDisclosure:
        "These figures are directional estimates based on industry benchmark conversion rates and traffic baselines, not measurements from your specific brand. Actual results vary by execution quality, content quality, and market conditions.",
      assumptionsNote: isNonprofit
        ? `Nonprofit baseline: avg online donation of $128 used in place of customer value. Conversion rate (${(avgConversionRate * 100).toFixed(1)}%) reflects donation conversion. ${sourceNote}`
        : `Industry: ${industry}. Avg customer value $${avgCustomerValue.toLocaleString()}, conversion rate ${(avgConversionRate * 100).toFixed(1)}%. ${sourceNote}`,
      confidenceFactors: [
        hasSerpData ? "SERP analysis data available — improves volume accuracy" : "No SERP analysis — volume based on industry baseline only",
        estimates.length >= 5 ? "Multiple findings increase aggregate accuracy" : "Fewer findings reduce aggregate confidence",
        isNonprofit ? "Nonprofit special case: donation value substituted for customer LTV" : "",
      ].filter(Boolean),
    },
  };
}
