import { createServerFn } from "@tanstack/react-start";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";
import { z } from "zod";
import { LIMIT_ERROR_PREFIX, PLAN_LIMITS, type PlanId } from "@/lib/plans";
import {
  netWorth,
  monthTotals,
  observedMonthlySurplus,
  isSameMonth,
  monthsToGoal,
  goalProbability,
  type TxLike,
} from "@/lib/finance";
import {
  buildFinancialState,
  scoreDecision,
  detectAlerts,
  nextBestAction,
  intelligenceSummary,
} from "@/lib/intelligence";


/** Server-side monthly quota enforcement. Throws PRUDENTIA_LIMIT:<kind> when exhausted. */
async function assertQuota(supabase: any, userId: string, kind: "decisions" | "simulations") {
  const { data: sub } = await supabase.from("subscriptions").select("plan,status,current_period_end").eq("user_id", userId).maybeSingle();
  const periodEnd = sub?.current_period_end ? new Date(sub.current_period_end).getTime() : null;
  const active = sub ? ["active", "trialing", "past_due"].includes(sub.status) : false;
  const plan: PlanId = active && (periodEnd == null || periodEnd > Date.now()) ? (sub?.plan as PlanId) ?? "free" : "free";
  const limit = PLAN_LIMITS[plan]?.[kind] ?? PLAN_LIMITS.free[kind];
  if (limit == null) return;
  const d = new Date();
  const since = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)).toISOString();
  const { count } = await supabase
    .from(kind === "decisions" ? "decisions" : "simulations")
    .select("id", { count: "exact", head: true })
    .eq("user_id", userId)
    .gte("created_at", since);
  if ((count ?? 0) >= limit) throw new Error(`${LIMIT_ERROR_PREFIX}:${kind}`);
}


const LOVABLE_URL = "https://ai.gateway.lovable.dev/v1/chat/completions";
const DEFAULT_MODEL = "google/gemini-2.5-flash";

async function callAI(messages: Array<{ role: string; content: string }>, opts?: { json?: boolean; model?: string }) {
  const key = process.env.LOVABLE_API_KEY;
  if (!key) throw new Error("LOVABLE_API_KEY not set");
  const res = await fetch(LOVABLE_URL, {
    method: "POST",
    headers: { "content-type": "application/json", "Lovable-API-Key": key },
    body: JSON.stringify({
      model: opts?.model ?? DEFAULT_MODEL,
      messages,
      ...(opts?.json ? { response_format: { type: "json_object" } } : {}),
    }),
  });
  if (!res.ok) {
    const t = await res.text();
    if (res.status === 429) throw new Error("AI rate limit reached. Try again shortly.");
    if (res.status === 402) throw new Error("AI credits exhausted. Please add credits.");
    throw new Error(`AI error ${res.status}: ${t}`);
  }
  const data = await res.json() as { choices: Array<{ message: { content: string } }> };
  return data.choices[0]?.message?.content ?? "";
}

async function loadContext(supabase: any, userId: string) {
  const [{ data: profile }, { data: txs }, { data: budgets }, { data: goals }] = await Promise.all([
    supabase.from("profiles").select("*").eq("id", userId).maybeSingle(),
    supabase.from("transactions").select("*").eq("user_id", userId).order("occurred_at", { ascending: false }).limit(1000),
    supabase.from("budgets").select("*").eq("user_id", userId),
    supabase.from("goals").select("*").eq("user_id", userId),
  ]);
  return { profile, txs: txs ?? [], budgets: budgets ?? [], goals: goals ?? [] };
}

function contextSummary(ctx: Awaited<ReturnType<typeof loadContext>>) {
  const p = ctx.profile ?? {};
  const income = Number(p.monthly_income ?? 0);
  const baseline = Number(p.savings ?? 0);
  const txs = ctx.txs as TxLike[];

  // Same math the dashboards use — see src/lib/finance.ts
  const worth = netWorth(baseline, txs);
  const month = monthTotals(txs);
  const surplus = observedMonthlySurplus(txs);

  return {
    profile: {
      name: p.name, country: p.country, currency: p.currency,
      monthly_income: income, savings_baseline: baseline,
      net_worth: Math.round(worth),
      risk: p.risk_tolerance, goals: p.financial_goals, occupation: p.occupation,
    },
    this_month: { spent: month.spent, earned: month.earned, net: month.net, transaction_count: txs.filter((t) => isSameMonth(t.occurred_at)).length },
    observed_monthly_surplus: Math.round(surplus),
    budgets: ctx.budgets.map((b: any) => ({
      category: b.category,
      limit: Number(b.monthly_limit),
      spent_this_month: txs
        .filter((t) => t.type === "expense" && t.category === b.category && isSameMonth(t.occurred_at))
        .reduce((s, t) => s + Number(t.amount), 0),
    })),
    goals: ctx.goals.map((g: any) => {
      const target = Number(g.target_amount);
      const current = Number(g.current_amount);
      const perMonth = Number(g.monthly_contribution ?? 0) || surplus;
      const remaining = Math.max(0, target - current);
      return {
        name: g.name, target, current, by: g.target_date,
        months_to_goal: monthsToGoal(remaining, perMonth),
        on_time_odds: goalProbability(remaining, perMonth, g.target_date),
      };
    }),
    recent_transactions: txs.slice(0, 10).map((t: any) => ({ amount: Number(t.amount), type: t.type, category: t.category, merchant: t.merchant, when: t.occurred_at })),
    calculation_note: "net_worth = savings_baseline + all recorded transactions. These figures match the app dashboards exactly; never recompute them differently.",
  };
}


/** Turns the user's saved coaching preferences into extra system guidance. */
function coachStyle(preferences: any): string {
  const p = preferences ?? {};
  const depth = p.coach_depth === "detailed"
    ? "Give thorough answers with reasoning and trade-offs."
    : p.coach_depth === "brief"
      ? "Answer in at most 3 short sentences. No preamble."
      : "Keep answers focused: a short answer plus a concrete next step.";
  const tone = p.coach_tone === "direct"
    ? "Tone: blunt and direct. Say the hard thing."
    : p.coach_tone === "encouraging"
      ? "Tone: encouraging and supportive."
      : "Tone: calm and neutral.";
  const numbers = p.coach_numbers === false ? "Prefer plain language over heavy number breakdowns." : "Back claims with the user's real numbers.";
  return `Style guidance: ${depth} ${tone} ${numbers}`;
}



/** Morning brief */
export const morningBrief = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const ctx = await loadContext(context.supabase, context.userId);
    const summary = contextSummary(ctx);
    const currency = ctx.profile?.currency ?? "USD";
    const content = await callAI([
      { role: "system", content: `You are PRUDENTIA, a calm, concise AI financial coach. Return a 2-3 sentence morning briefing. Reference specific numbers in ${currency}. No emojis. No bullet points. Address the user by first name if available.` },
      { role: "user", content: `Here is my financial context as JSON:\n${JSON.stringify(summary)}\n\nWrite my briefing.` },
    ]);
    return { text: content.trim() };
  });

/** Structured morning brief — returns discrete sections + status chips. */
export const structuredBrief = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const ctx = await loadContext(context.supabase, context.userId);
    const summary = contextSummary(ctx);
    const currency = ctx.profile?.currency ?? "USD";
    const firstName = (ctx.profile?.name ?? "there").split(" ")[0];

    const raw = await callAI([
      { role: "system", content: `You are PRUDENTIA, a calm AI financial coach. Return STRICT JSON with keys: greeting (string, one short line), snapshot (string, 1 sentence overview), mission (string, one actionable daily mission), opportunity (string, one positive action), risk (string, one thing to watch), recommendation (string, 2 sentences with a concrete next step), upcoming_bills (string, brief), goal_progress (string, brief). Reference numbers in ${currency}. Never invent numbers. No emojis. No markdown. Address user by first name "${firstName}".` },
      { role: "user", content: `Context JSON:\n${JSON.stringify(summary)}\n\nGenerate my structured briefing JSON.` },
    ], { json: true });
    let parsed: any = {};
    try { parsed = JSON.parse(raw); } catch { parsed = { snapshot: raw.slice(0, 240) }; }
    return {
      greeting: parsed.greeting ?? `Good morning, ${firstName}.`,
      snapshot: parsed.snapshot ?? "",
      mission: parsed.mission ?? "",
      opportunity: parsed.opportunity ?? "",
      risk: parsed.risk ?? "",
      recommendation: parsed.recommendation ?? "",
      upcoming_bills: parsed.upcoming_bills ?? "",
      goal_progress: parsed.goal_progress ?? "",
    };
  });

/** Seed / clear demo dataset for onboarding new accounts. */
export const seedDemoData = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const { supabase, userId } = context;
    // Clear existing demo (we won't touch user's non-demo rows other than delete-all here).
    await Promise.all([
      supabase.from("transactions").delete().eq("user_id", userId),
      supabase.from("budgets").delete().eq("user_id", userId),
      supabase.from("goals").delete().eq("user_id", userId),
    ]);

    const now = new Date();
    const merchants = {
      Groceries: ["Whole Foods", "Trader Joe's", "Aldi", "Sprouts"],
      Dining: ["Blue Bottle", "Sweetgreen", "Chipotle", "Local Bistro"],
      Transport: ["Uber", "Lyft", "Shell", "Metro"],
      Shopping: ["Amazon", "Apple", "Uniqlo", "IKEA"],
      Bills: ["Verizon", "Con Edison", "Rent", "Internet"],
      Entertainment: ["Netflix", "Spotify", "Steam", "Cinema"],
      Health: ["Pharmacy", "Clinic", "Gym"],
    } as const;
    const txs: any[] = [];
    for (let i = 0; i < 90; i++) {
      const day = new Date(now.getTime() - i * 86_400_000);
      // 1-3 expenses per day
      const n = 1 + Math.floor(Math.random() * 3);
      for (let k = 0; k < n; k++) {
        const cats = Object.keys(merchants) as (keyof typeof merchants)[];
        const cat = cats[Math.floor(Math.random() * cats.length)];
        const ms = merchants[cat];
        const amt = cat === "Bills" ? 40 + Math.random() * 200 : 8 + Math.random() * 70;
        txs.push({
          user_id: userId, type: "expense", amount: Number(amt.toFixed(2)),
          category: cat, merchant: ms[Math.floor(Math.random() * ms.length)],
          occurred_at: day.toISOString(),
        });
      }
      // biweekly salary
      if (i % 14 === 0) txs.push({
        user_id: userId, type: "income", amount: 2400, category: "Salary",
        merchant: "Employer", occurred_at: day.toISOString(),
      });
    }
    const { error: txErr } = await supabase.from("transactions").insert(txs);
    if (txErr) throw new Error(txErr.message);

    await supabase.from("budgets").insert([
      { user_id: userId, category: "Groceries", monthly_limit: 600 },
      { user_id: userId, category: "Dining", monthly_limit: 300 },
      { user_id: userId, category: "Transport", monthly_limit: 200 },
      { user_id: userId, category: "Shopping", monthly_limit: 250 },
      { user_id: userId, category: "Entertainment", monthly_limit: 120 },
    ]);
    await supabase.from("goals").insert([
      { user_id: userId, name: "Emergency Fund", target_amount: 12000, current_amount: 4200, category: "safety" },
      { user_id: userId, name: "Japan Trip", target_amount: 6000, current_amount: 1800, category: "travel" },
      { user_id: userId, name: "New Laptop", target_amount: 2500, current_amount: 900, category: "tech" },
    ]);
    return { ok: true, transactions: txs.length };
  });

export const clearAllData = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const { supabase, userId } = context;
    await Promise.all([
      supabase.from("transactions").delete().eq("user_id", userId),
      supabase.from("budgets").delete().eq("user_id", userId),
      supabase.from("goals").delete().eq("user_id", userId),
      supabase.from("decisions").delete().eq("user_id", userId),
      supabase.from("simulations").delete().eq("user_id", userId),
      supabase.from("ai_chats").delete().eq("user_id", userId),
    ]);
    return { ok: true };
  });


/** Financial health score */
export const healthScore = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const ctx = await loadContext(context.supabase, context.userId);
    const s = contextSummary(ctx);
    // Deterministic 0-100 blend so it doesn't need AI for every load
    const savingsMonths = s.profile.monthly_income > 0 ? Math.max(0, s.profile.net_worth) / s.profile.monthly_income : 0;
    const savingsScore = Math.min(1, savingsMonths / 6) * 40;
    const cashflowScore = s.this_month.earned > 0 ? Math.max(0, Math.min(1, s.this_month.net / s.this_month.earned)) * 35 : 15;
    const goalsScore = s.goals.length ? Math.min(1, s.goals.reduce((a: number, g: any) => a + (g.current / (g.target || 1)), 0) / s.goals.length) * 25 : 10;
    const score = Math.round(savingsScore + cashflowScore + goalsScore);
    return { score: Math.max(1, Math.min(100, score)), breakdown: { savingsMonths, net: s.this_month.net, goalsCount: s.goals.length } };
  });

/** Decision engine */
export const askDecision = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({ question: z.string().min(4), context: z.string().optional() }).parse(v))
  .handler(async ({ context, data }) => {
    await assertQuota(context.supabase, context.userId, "decisions");
    const ctx = await loadContext(context.supabase, context.userId);

    const summary = contextSummary(ctx);
    const currency = ctx.profile?.currency ?? "USD";

    // 1. AI only extracts the shape of the decision and phrases the reasoning.
    const raw = await callAI(
      [
        { role: "system", content: `You are PRUDENTIA's Decision Engine. Analyze a personal financial decision using the user's actual context, in ${currency}. Return STRICT JSON with keys: verdict ("yes"|"no"|"maybe"), recommendation (string, 2-3 sentences), confidence (0-100 integer), pros (array of 3 strings), cons (array of 3 strings), opportunity_cost (string), long_term_impact (string), risk_level ("low"|"medium"|"high"), cash_flow_impact_monthly (number, negative if it costs money each month, 0 if none), upfront_cost (number, 0 if none), break_even_months (number or null), affordability_score (0-100 integer), alternatives (array of 2-3 concrete alternative options as strings), milestones (array of 3-4 objects {month: integer, label: string}), watch_outs (array of 2 strings). Estimate upfront_cost from the question if the user named a price. No extra keys, no prose outside JSON.` },
        { role: "user", content: `User context JSON:\n${JSON.stringify(summary)}\n\nQuestion: ${data.question}\nExtra context: ${data.context ?? "(none)"}` },
      ],
      { json: true },
    );
    let parsed: any;
    try { parsed = JSON.parse(raw); } catch { parsed = { verdict: "maybe", recommendation: raw.slice(0, 400), confidence: 50, pros: [], cons: [], opportunity_cost: "", long_term_impact: "", risk_level: "medium" }; }
    const num = (x: any, f = 0) => (Number.isFinite(Number(x)) ? Number(x) : f);

    // 2. Every figure below is computed by Prudentia, never by the model.
    const state = buildFinancialState(ctx.profile, ctx.txs as TxLike[], ctx.goals as any[]);
    const upfront = Math.max(0, num(parsed.upfront_cost));
    const monthlyCost = Math.abs(num(parsed.cash_flow_impact_monthly));
    const verdict = scoreDecision(state, upfront, monthlyCost);
    const buyNow = verdict.scenarios[0];

    const analysis = {
      cash_flow_impact_monthly: -monthlyCost,
      upfront_cost: upfront,
      break_even_months: parsed.break_even_months == null ? null : num(parsed.break_even_months),
      affordability_score: verdict.components.find((c) => c.key === "affordability")?.score ?? 50,
      alternatives: Array.isArray(parsed.alternatives) ? parsed.alternatives.slice(0, 3) : [],
      milestones: Array.isArray(parsed.milestones)
        ? parsed.milestones.slice(0, 4).map((m: any) => ({ month: num(m?.month), label: String(m?.label ?? "") }))
        : [],
      watch_outs: Array.isArray(parsed.watch_outs) ? parsed.watch_outs.slice(0, 2) : [],
      currency,
      headline: verdict.headline,
      changes_the_answer: verdict.changesTheAnswer,
      better_option: verdict.betterOption?.label ?? null,
      state_snapshot: intelligenceSummary(state),
    };

    const { error, data: inserted } = await context.supabase.from("decisions").insert({
      user_id: context.userId,
      question: data.question,
      context: data.context ?? null,
      recommendation: parsed.recommendation,
      verdict: verdict.action === "BUY" ? "yes" : verdict.action === "DONT" ? "no" : "maybe",
      confidence: verdict.score,
      pros: parsed.pros,
      cons: parsed.cons,
      opportunity_cost: parsed.opportunity_cost,
      long_term_impact: parsed.long_term_impact,
      risk_level: verdict.components.find((c) => c.key === "risk")!.score >= 70 ? "low" : verdict.components.find((c) => c.key === "risk")!.score >= 45 ? "medium" : "high",
      analysis,
      score: verdict.score,
      action: verdict.action,
      components: verdict.components,
      scenarios: verdict.scenarios,
      assumptions: verdict.assumptions,
      predicted_impact: {
        cash_after: buyNow.cashAfter,
        reserve_months_after: buyNow.reserveMonthsAfter,
        surplus_after: buyNow.surplusAfter,
        goal_delay_weeks: buyNow.goalDelayWeeks,
        net_worth_12m: buyNow.netWorth12m,
      },
      status: "open",
    }).select().single();
    if (error) throw new Error(error.message);
    return inserted;
  });

/** Record what the user actually did, so predictions can be measured later. */
export const recordDecisionOutcome = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({
    id: z.string().uuid(),
    chosen_option: z.string().min(1).max(80),
    actual_outcome: z.string().max(500).optional(),
  }).parse(v))
  .handler(async ({ context, data }) => {
    const { error } = await context.supabase
      .from("decisions")
      .update({
        chosen_option: data.chosen_option,
        actual_outcome: data.actual_outcome ?? null,
        status: "recorded",
        reviewed_at: new Date().toISOString(),
      })
      .eq("id", data.id)
      .eq("user_id", context.userId);
    if (error) throw new Error(error.message);
    return { ok: true };
  });

/** Deterministic alerts + next best action for Home. No AI involved. */
export const getIntelligenceBrief = createServerFn({ method: "GET" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const ctx = await loadContext(context.supabase, context.userId);
    const state = buildFinancialState(ctx.profile, ctx.txs as TxLike[], ctx.goals as any[]);
    const alerts = detectAlerts(state, ctx.txs as TxLike[]);
    return { state, alerts, nextAction: nextBestAction(state, alerts) };
  });



/** Coach chat — conversation aware */
export const chatWithCoach = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({
    message: z.string().min(1),
    conversationId: z.string().uuid().optional(),
  }).parse(v))
  .handler(async ({ context, data }) => {
    const { supabase, userId } = context;
    const ctx = await loadContext(supabase, userId);
    const summary = contextSummary(ctx);

    // Resolve or create the conversation
    let conversationId = data.conversationId;
    if (!conversationId) {
      const title = data.message.length > 48 ? `${data.message.slice(0, 48)}…` : data.message;
      const { data: conv, error: convErr } = await supabase
        .from("ai_conversations")
        .insert({ user_id: userId, title })
        .select("id")
        .single();
      if (convErr) throw new Error(convErr.message);
      conversationId = conv.id;
    }

    const { error: userErr } = await supabase.from("ai_chats").insert({
      user_id: userId, role: "user", content: data.message, conversation_id: conversationId,
    });
    if (userErr) throw new Error(userErr.message);

    const { data: history } = await supabase
      .from("ai_chats").select("role, content")
      .eq("user_id", userId)
      .eq("conversation_id", conversationId)
      .order("created_at", { ascending: true })
      .limit(40);

    const reply = await callAI([
      { role: "system", content: `You are PRUDENTIA, a calm, thoughtful AI financial coach. Be concise, warm, precise. Use the user's actual financial context. Reference numbers in ${ctx.profile?.currency ?? "USD"}. Use markdown lightly. Never invent numbers not in the context.\n\n${coachStyle(ctx.profile?.preferences)}\n\nContext JSON:\n${JSON.stringify(summary)}` },
      ...(history ?? []).map((h: any) => ({ role: h.role, content: h.content })),
    ]);

    const { data: assistant, error: aErr } = await supabase.from("ai_chats").insert({
      user_id: userId, role: "assistant", content: reply, conversation_id: conversationId,
    }).select().single();
    if (aErr) throw new Error(aErr.message);

    await supabase.from("ai_conversations")
      .update({ last_message_at: new Date().toISOString() })
      .eq("id", conversationId);

    return { message: assistant, conversationId };
  });

/** Auto-title a conversation from its first exchange. */
export const titleConversation = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({ conversationId: z.string().uuid() }).parse(v))
  .handler(async ({ context, data }) => {
    const { supabase, userId } = context;
    const { data: msgs } = await supabase.from("ai_chats")
      .select("role, content").eq("user_id", userId)
      .eq("conversation_id", data.conversationId)
      .order("created_at", { ascending: true }).limit(4);
    if (!msgs?.length) return { title: null };
    const title = (await callAI([
      { role: "system", content: "Write a 2-5 word title for this financial coaching conversation. Title case. No quotes, no punctuation at the end." },
      { role: "user", content: msgs.map((m: any) => `${m.role}: ${m.content}`).join("\n").slice(0, 2000) },
    ])).trim().replace(/^["']|["'.]$/g, "").slice(0, 60);
    await supabase.from("ai_conversations").update({ title }).eq("id", data.conversationId);
    return { title };
  });

/** Housekeeping: drop archived, unpinned conversations older than the retention window. */
export const cleanupCoachHistory = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({ retentionDays: z.number().min(1).max(3650).optional() }).parse(v ?? {}))
  .handler(async ({ context, data }) => {
    const { supabase, userId } = context;
    const cutoff = new Date(Date.now() - (data.retentionDays ?? 30) * 86_400_000).toISOString();
    const { data: removed, error } = await supabase.from("ai_conversations")
      .delete()
      .eq("user_id", userId)
      .eq("archived", true)
      .eq("pinned", false)
      .lt("last_message_at", cutoff)
      .select("id");
    if (error) throw new Error(error.message);
    return { removed: removed?.length ?? 0 };
  });

export const clearCoach = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    await context.supabase.from("ai_chats").delete().eq("user_id", context.userId);
    await context.supabase.from("ai_conversations").delete().eq("user_id", context.userId);
    return { ok: true };
  });

/** Proactive recommendations — deterministic signals, AI phrasing, persisted. */
export const generateRecommendations = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const { supabase, userId } = context;
    const ctx = await loadContext(supabase, userId);
    const summary = contextSummary(ctx);
    const currency = ctx.profile?.currency ?? "USD";

    const raw = await callAI([
      { role: "system", content: `You are PRUDENTIA's proactive intelligence engine. From the user's real context, produce STRICT JSON: {"items":[{"title":string (max 60 chars),"body":string (1-2 sentences, concrete and actionable),"severity":"info"|"success"|"warning"|"critical","impact":string (short expected outcome)}]} with 3-5 items. Reference real numbers in ${currency}. Never invent data.` },
      { role: "user", content: `Context JSON:\n${JSON.stringify(summary)}\n\nGenerate my recommendations JSON.` },
    ], { json: true });

    let items: any[] = [];
    try { items = JSON.parse(raw).items ?? []; } catch { items = []; }
    if (!items.length) return { items: [] };

    await supabase.from("ai_insights").delete().eq("user_id", userId).eq("kind", "recommendation");
    const { data: saved, error } = await supabase.from("ai_insights").insert(
      items.slice(0, 6).map((i) => ({
        user_id: userId, kind: "recommendation",
        title: String(i.title ?? "Recommendation").slice(0, 120),
        body: String(i.body ?? ""),
        severity: ["info", "success", "warning", "critical"].includes(i.severity) ? i.severity : "info",
        data: { impact: i.impact ?? "" },
      })),
    ).select();
    if (error) throw new Error(error.message);
    return { items: saved ?? [] };
  });

/** Financial DNA — behavioural profile learned from transaction history. */
export const generateFinancialDNA = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .handler(async ({ context }) => {
    const { supabase, userId } = context;
    const ctx = await loadContext(supabase, userId);
    const summary = contextSummary(ctx);

    // Deterministic behavioural traits
    const expenses = ctx.txs.filter((t: any) => t.type === "expense");
    const byCat = new Map<string, number>();
    const byWeekday = new Array(7).fill(0);
    let weekendSpend = 0, totalSpend = 0, small = 0;
    expenses.forEach((t: any) => {
      const amt = Number(t.amount);
      totalSpend += amt;
      if (amt < 20) small += 1;
      byCat.set(t.category, (byCat.get(t.category) ?? 0) + amt);
      const d = new Date(t.occurred_at).getDay();
      byWeekday[d] += amt;
      if (d === 0 || d === 6) weekendSpend += amt;
    });
    const topCats = [...byCat.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5)
      .map(([category, amount]) => ({ category, amount: Math.round(amount), share: totalSpend ? Math.round((amount / totalSpend) * 100) : 0 }));
    const traits = {
      weekend_share: totalSpend ? Math.round((weekendSpend / totalSpend) * 100) : 0,
      impulse_share: expenses.length ? Math.round((small / expenses.length) * 100) : 0,
      avg_transaction: expenses.length ? Math.round(totalSpend / expenses.length) : 0,
      busiest_day: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][byWeekday.indexOf(Math.max(...byWeekday))],
      top_categories: topCats,
      savings_rate: summary.this_month.earned > 0 ? Math.round((summary.this_month.net / summary.this_month.earned) * 100) : 0,
    };

    const raw = await callAI([
      { role: "system", content: `You are PRUDENTIA's behaviour analyst. Given computed spending traits, return STRICT JSON: {"archetype":string (2-3 words, e.g. "Steady Builder"),"summary":string (2 sentences),"strengths":[3 strings],"patterns":[3 strings],"blind_spots":[2 strings]}. Base everything on the numbers provided. No invented figures.` },
      { role: "user", content: `Traits:\n${JSON.stringify(traits)}\nContext:\n${JSON.stringify(summary)}` },
    ], { json: true });
    let parsed: any = {};
    try { parsed = JSON.parse(raw); } catch { parsed = {}; }

    await supabase.from("ai_insights").delete().eq("user_id", userId).eq("kind", "dna");
    const { data: saved, error } = await supabase.from("ai_insights").insert({
      user_id: userId, kind: "dna",
      title: parsed.archetype ?? "Your Financial DNA",
      body: parsed.summary ?? "",
      severity: "info",
      data: {
        traits,
        strengths: parsed.strengths ?? [],
        patterns: parsed.patterns ?? [],
        blind_spots: parsed.blind_spots ?? [],
      },
    }).select().single();
    if (error) throw new Error(error.message);
    return saved;
  });

/** Natural-language money story for a week or month. */
export const generateStory = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({ period: z.enum(["week", "month"]) }).parse(v))
  .handler(async ({ context, data }) => {
    const { supabase, userId } = context;
    const ctx = await loadContext(supabase, userId);
    const currency = ctx.profile?.currency ?? "USD";
    const days = data.period === "week" ? 7 : 30;
    const start = new Date(Date.now() - days * 86_400_000);
    const periodTxs = ctx.txs.filter((t: any) => new Date(t.occurred_at) >= start);
    const income = periodTxs.filter((t: any) => t.type === "income").reduce((s: number, t: any) => s + Number(t.amount), 0);
    const expense = periodTxs.filter((t: any) => t.type === "expense").reduce((s: number, t: any) => s + Number(t.amount), 0);
    const byCat = new Map<string, number>();
    periodTxs.filter((t: any) => t.type === "expense").forEach((t: any) => byCat.set(t.category, (byCat.get(t.category) ?? 0) + Number(t.amount)));
    const stats = {
      period: data.period, income: Math.round(income), expense: Math.round(expense), net: Math.round(income - expense),
      transactions: periodTxs.length,
      categories: [...byCat.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6).map(([c, v]) => ({ category: c, amount: Math.round(v) })),
      currency,
    };

    const raw = await callAI([
      { role: "system", content: `You are PRUDENTIA's narrator. Write the user's money story for the last ${data.period} as STRICT JSON: {"title":string (max 60 chars),"story":string (3 short paragraphs separated by \\n\\n, warm and specific, referencing real numbers in ${currency}),"highlight":string (one sentence win),"watch":string (one sentence caution)}. Never invent numbers.` },
      { role: "user", content: `Period stats:\n${JSON.stringify(stats)}` },
    ], { json: true });
    let parsed: any = {};
    try { parsed = JSON.parse(raw); } catch { parsed = { story: raw }; }

    const { data: saved, error } = await supabase.from("ai_insights").insert({
      user_id: userId, kind: data.period === "week" ? "story_week" : "story_month",
      title: parsed.title ?? `Your ${data.period} in money`,
      body: parsed.story ?? "",
      severity: "info",
      data: { stats, highlight: parsed.highlight ?? "", watch: parsed.watch ?? "" },
      period_start: start.toISOString().slice(0, 10),
      period_end: new Date().toISOString().slice(0, 10),
    }).select().single();
    if (error) throw new Error(error.message);
    return saved;
  });


/** Simulator */
export const runSimulation = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((v: unknown) => z.object({
    kind: z.string(),
    name: z.string(),
    inputs: z.record(z.any()),
    months: z.number().min(12).max(360).optional(),
  }).parse(v))
  .handler(async ({ context, data }) => {
    await assertQuota(context.supabase, context.userId, "simulations");
    const ctx = await loadContext(context.supabase, context.userId);

    const summary = contextSummary(ctx);
    const currency = ctx.profile?.currency ?? "USD";
    const n = (k: string, f = 0) => {
      const x = Number((data.inputs as any)[k]);
      return Number.isFinite(x) ? x : f;
    };
    const income = Number(summary.profile.monthly_income ?? 0);
    const savings = Number(summary.profile.net_worth ?? 0);
    const months = data.months ?? 60;
    const rateAnnual = n("returnRate", 0.06);
    const inflation = n("inflation", 0.03);
    const monthlySavings = n("monthlySavings", Math.max(0, income * 0.2));

    // Scenario-specific cash-flow shape
    let upfront = 0;
    let monthlyOutflow = 0;
    let monthlyInflow = 0;
    let assetValue = 0;
    let assetGrowth = 0; // annual
    let payoffMonths = 0;

    const amortize = (principal: number, annualRate: number, years: number) => {
      const r = annualRate / 12;
      const nMonths = Math.max(1, Math.round(years * 12));
      if (r <= 0) return principal / nMonths;
      return (principal * r) / (1 - Math.pow(1 + r, -nMonths));
    };

    switch (data.kind) {
      case "car": {
        const price = n("price", 25000);
        const down = n("downPayment", price * 0.2);
        const apr = n("apr", 0.07);
        const years = n("loanYears", 5);
        upfront = down;
        payoffMonths = Math.round(years * 12);
        monthlyOutflow = amortize(price - down, apr, years) + n("insurance", 120) + n("fuelMaintenance", 180);
        assetValue = price;
        assetGrowth = -0.15; // depreciation
        break;
      }
      case "house": {
        const price = n("price", 350000);
        const down = n("downPayment", price * 0.2);
        const apr = n("apr", 0.065);
        const years = n("loanYears", 30);
        upfront = down + price * 0.03;
        payoffMonths = Math.round(years * 12);
        monthlyOutflow = amortize(price - down, apr, years) + n("taxesInsurance", price * 0.015 / 12) + n("maintenance", price * 0.01 / 12);
        monthlyInflow = n("rentSaved", 0);
        assetValue = price;
        assetGrowth = n("appreciation", 0.03);
        break;
      }
      case "loan": {
        const amount = n("amount", 15000);
        const apr = n("apr", 0.09);
        const years = n("loanYears", 3);
        payoffMonths = Math.round(years * 12);
        monthlyOutflow = amortize(amount, apr, years);
        monthlyInflow = 0;
        upfront = -amount; // cash received now
        break;
      }
      case "debt": {
        const balance = n("balance", 8000);
        const apr = n("apr", 0.22);
        const payment = n("monthlyPayment", 400);
        monthlyOutflow = payment;
        let b = balance;
        let m = 0;
        while (b > 0 && m < 600) { b = b * (1 + apr / 12) - payment; m++; }
        payoffMonths = m;
        break;
      }
      case "purchase": {
        upfront = n("oneTimeCost", 0);
        monthlyOutflow = n("recurringCost", 0);
        break;
      }
      case "salary": {
        monthlyInflow = n("salaryDelta", 0) / 12;
        break;
      }
      case "retirement": {
        monthlyInflow = 0;
        break;
      }
      default:
        break;
    }

    const buildSeries = (withScenario: boolean) => {
      const points: Array<{ month: number; value: number; liquid?: number }> = [];
      let liquid = savings - (withScenario ? upfront : 0);
      let asset = withScenario ? assetValue : 0;
      let debtRemaining = withScenario && data.kind === "debt" ? n("balance", 8000) : 0;
      for (let m = 1; m <= months; m++) {
        const outflow = withScenario && (payoffMonths === 0 || m <= payoffMonths) ? monthlyOutflow : 0;
        const inflow = withScenario ? monthlyInflow : 0;
        const contribution = monthlySavings + inflow - outflow;
        liquid = (liquid + contribution) * (1 + rateAnnual / 12) * (1 - inflation / 12);
        if (asset > 0) asset = asset * (1 + assetGrowth / 12);
        if (debtRemaining > 0) debtRemaining = Math.max(0, debtRemaining * (1 + n("apr", 0.22) / 12) - monthlyOutflow);
        points.push({ month: m, value: Math.round(liquid + asset - debtRemaining), liquid: Math.round(liquid) });
      }
      return points;
    };

    const points = buildSeries(true);
    const baselinePoints = buildSeries(false);
    const finalV = points.at(-1)?.value ?? 0;
    const baselineFinal = baselinePoints.at(-1)?.value ?? 0;
    const metrics = {
      currency,
      months,
      upfront: Math.round(upfront),
      monthly_impact: Math.round(monthlyInflow - monthlyOutflow),
      payoff_months: payoffMonths,
      final_value: finalV,
      baseline_final: baselineFinal,
      delta_vs_baseline: finalV - baselineFinal,
      total_paid: Math.round(monthlyOutflow * (payoffMonths || months) + Math.max(0, upfront)),
    };

    const summaryText = await callAI([
      { role: "system", content: `You are PRUDENTIA. In 2-3 short sentences, summarize the outcome of this financial simulation versus doing nothing. Reference key numbers in ${currency}. Be direct. No emojis.` },
      { role: "user", content: JSON.stringify({ kind: data.kind, name: data.name, inputs: data.inputs, metrics }) },
    ]);

    const { data: saved, error } = await context.supabase.from("simulations").insert({
      user_id: context.userId, kind: data.kind, name: data.name,
      inputs: data.inputs,
      outputs: { points, metrics },
      baseline: { points: baselinePoints },
      summary: summaryText.trim(),
    }).select().single();
    if (error) throw new Error(error.message);
    return saved;
  });

