import "server-only";
import { z } from "zod";
import type {
  RaporKapsam,
  RaporParams,
  RaporSonuc,
} from "@/features/rapor/types/rapor";
import { anahtarVar } from "./client";
import { yapilandirilmisUret } from "./cagri";
import {
  havuzOlustur,
  havuzuKompaktJson,
  turLabel,
  dilimHedefiHesapla,
  IDEAL_HEDEF,
  type DilimHedef,
  type Dilim,
  type AdayProgram,
} from "../rapor-havuzu";
import { secimOzeti } from "@/features/sihirbaz/sihirbaz-sabitler";
import { RISK_ETIKET, dilimdenRisk, riskHesapla } from "../risk";

// Yapay zekâdan istenen küçük şema (kullanıcı kararı, 6 Ağu 2026): riskNotu
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; dilim iskeleti
// ve tercih sırası da koddan gelir. Dilim başına SABİT uzunluklu ayrı listeler
// istenir ki dağılım şema düzeyinde (grammar/JSON-schema ile) zorlansın —
// reasoning kapalıyken model serbest dağılımı tutturamıyor (ölçüldü: 13 hayal
// / 0 garanti dönmüştü). Uzunluklar havuza uyarlanmış hedeften gelir (havuzda
// dilim başına yeterli program olmayabiliyor; sabit 5/13/6 o durumda aritmetik
// olarak imkânsızdı ve üretim her seferinde patlıyordu). AI yalnızca seçer +
// gerekce yazar.
const AiTercihSchema = z.object({
  programId: z.string(),
  gerekce: z.string(),
});
/** Sağlayıcıya giden tel şeması: dilim uzunlukları hedefe sabitlenir. */
function aiSemaWire(hedef: DilimHedef) {
  return z.object({
    hayal: z.array(AiTercihSchema).length(hedef.hayal),
    dengeli: z.array(AiTercihSchema).length(hedef.dengeli),
    garanti: z.array(AiTercihSchema).length(hedef.garanti),
    genelDegerlendirme: z.string(),
    uyarilar: z.array(z.string()).min(2).max(3),
  });
}
// Lokal parse şeması: uzunluk kısıtsız. Sağlayıcı tel şemasındaki kısıtları
// yok sayarsa çıktı reddedilmez; normalize + kurtarma katmanı düzeltir.
const AiRaporGevsek = z.object({
  hayal: z.array(AiTercihSchema),
  dengeli: z.array(AiTercihSchema),
  garanti: z.array(AiTercihSchema),
  genelDegerlendirme: z.string(),
  uyarilar: z.array(z.string()),
});

function sistemMetni(hedef: DilimHedef): string {
  const adet = (d: Dilim) =>
    hedef[d] === 0 ? "BOŞ liste [] döndür" : `TAM ${hedef[d]} program`;
  const toplam = hedef.hayal + hedef.dengeli + hedef.garanti;
  return `Sen KolayTercih'in kıdemli YKS tercih danışmanısın. Görevin: sana verilen
GERÇEK YÖK Atlas aday havuzundan adaya en uygun programları seçip üç listeyi
doldurmak: hayal (${adet("hayal")}), dengeli (${adet("dengeli")}), garanti (${adet("garanti")}).

KURALLAR (kesin):
- SADECE havuzdaki programId'leri kullan; havuzda olmayan program ASLA önerme.
  Aynı programId'yi birden fazla kez kullanma.
- Havuzdaki her programın "dilim" alanı var: hayal listesine yalnızca dilim
  değeri "hayal" olan programları koy; dengeli ve garanti için de aynısı.
- Seçerken adayın ilgi alanlarını ve illerini, kontenjan/yerleşen hareketini
  ve bölüm çeşitliliğini gözet.
- METİN DİYETİ: aday ${toplam} satır okuyacak; gerekce TEK kısa cümle (en fazla ~15
  kelime, Türkçe): bu programın bu aday için neden mantıklı olduğu. Dolgu söz
  yok; ASLA "garanti", "kesin", "yüzde yüz" deme.
- Adayın notlarındaki şehir/bölüm tercihlerine saygı göster ama dengeyi bozma.
- genelDegerlendirme: listenin genel stratejisi, EN FAZLA 2 cümle. Uyarıları
  buraya yazma.
- uyarilar: adaya 2-3 pratik, eyleme dönük uyarı; her biri tek kısa cümle.
(Tercih sırası, risk notu ve trend özeti koddan hesaplanır; sen yazmazsın.)`;
}

export class RaporUretimHatasi extends Error {}

// Liste kurmak için gereken mutlak taban. Havuz bunun altındaysa hata ekranı
// meşrudur; üstündeyse "ne olursa olsun liste kurulur" (kullanıcı kararı,
// 7 Ağu 2026) — eksik dilimler uyarlanır ve UI'da dürüstçe belirtilir.
// Not: ACIK_SATIR=3 maskesiyle tam 3 satırlık liste fiilen açık iner;
// kabul edilen kenar durumu.
const MIN_LISTE = 3;

const DILIMLER: Dilim[] = ["hayal", "dengeli", "garanti"];

function kapsamUret(hedef: DilimHedef): RaporKapsam {
  return {
    toplam: hedef.hayal + hedef.dengeli + hedef.garanti,
    dagilim: { ...hedef },
    uyarlandi: DILIMLER.some((d) => hedef[d] !== IDEAL_HEDEF[d]),
  };
}

type AiSecim = z.infer<typeof AiTercihSchema>;

/**
 * AI çıktısını havuza göre normalize eder: havuzda olmayan id'ler ve
 * tekrarlar atılır, her program havuzdaki GERÇEK dilimine yeniden atanır
 * (modelin yanlış listeye koyması artık ret sebebi değil, düzeltilir).
 */
function normalizeEt(
  parsed: z.infer<typeof AiRaporGevsek>,
  havuzMap: Map<string, AdayProgram>,
): Record<Dilim, AiSecim[]> {
  const gruplar: Record<Dilim, AiSecim[]> = {
    hayal: [],
    dengeli: [],
    garanti: [],
  };
  const gorulen = new Set<string>();
  for (const t of [...parsed.hayal, ...parsed.dengeli, ...parsed.garanti]) {
    const p = havuzMap.get(t.programId);
    if (!p || gorulen.has(t.programId)) continue;
    gorulen.add(t.programId);
    gruplar[p.dilim].push(t);
  }
  return gruplar;
}

// --- Deterministik satır metinleri: risk ve trend gerçek YÖK Atlas sıra
// geçmişinden hesaplanır; model bu alanlarda jenerik dolgu yazıyordu, kod
// kesin rakam verir ("45B düzeyinden 38B düzeyine indi").

/** 45000 -> "45B", 1_200_000 -> "1,2M" (şemadaki öğrenci dili). */
function siraKisa(n: number): string {
  if (n >= 1_000_000) {
    return `${(n / 1_000_000).toLocaleString("tr-TR", { maximumFractionDigits: 1 })}M`;
  }
  if (n >= 1000) return `${Math.round(n / 1000)}B`;
  return String(n);
}

function trendOzetiUret(p: AdayProgram): string {
  const ham: Array<[number, number | null | undefined]> = [
    [2021, p.sira2021],
    [2022, p.sira2022],
    [2023, p.sira2023],
    [2024, p.sira2024],
    [2025, p.sira2025],
  ];
  const seri = ham.filter((cift): cift is [number, number] => cift[1] != null);
  if (seri.length < 2) {
    return "Karşılaştırma için yeterli geçmiş yıl verisi yok; trendi temkinli oku.";
  }
  const [ilkYil, ilkSira] = seri[0];
  const [sonYil, sonSira] = seri[seri.length - 1];
  const oran = (sonSira - ilkSira) / ilkSira;
  if (oran <= -0.1) {
    return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine indi; bölüm zorlaşıyor.`;
  }
  if (oran >= 0.1) {
    return `${ilkYil}-${sonYil} arasında taban ${siraKisa(ilkSira)} düzeyinden ${siraKisa(sonSira)} düzeyine çıktı; yerleşmek görece kolaylaşıyor.`;
  }
  return `${ilkYil}-${sonYil} arasında taban sıralaması ${siraKisa(sonSira)} düzeyinde stabil seyretti.`;
}

function riskNotuUret(p: AdayProgram, adaySira: number): string {
  switch (riskHesapla(p.sira2025 ?? p.sira2024, adaySira)) {
    case "guvenli":
      return "Taban sıralaması senin sıranın belirgin altında; yüksek ihtimalle yerleşirsin.";
    case "az-riskli":
      return "Taban sıralaması sırana yakın; orta ihtimalle yerleşilebilir.";
    case "riskli":
      return "Taban sıralaması senin sıranın üstünde kapandı; yerleşme ihtimali düşük.";
    default:
      return "Güncel taban sıralaması verisi yok; ihtimal öngörülemez, temkinli yaklaş.";
  }
}

/** Havuz programını render için gereken bilgiye indirger. */
function programBilgisi(p: AdayProgram): RaporSonuc["programlar"][string] {
  const gecmis = [p.sira2021, p.sira2022, p.sira2023, p.sira2024, p.sira2025];
  return {
    isim: p.isim,
    universite: p.universite,
    il: p.il,
    unitur: p.unitur,
    efektifSira: p.sira2025 ?? p.sira2024,
    siraGecmisi: gecmis.some((s) => s != null) ? gecmis : undefined,
  };
}

// --- Dev mock: API key yokken (yalnızca development) gerçek havuzdan sahte
// ama tutarlı bir rapor üret. Akış (kredi, blur, risk renkleri, revizyon)
// gerçek Yapay Zeka olmadan uçtan uca test edilebilsin diye. Prod'da asla çalışmaz.

function mockAktif(): boolean {
  return process.env.NODE_ENV !== "production" && !anahtarVar();
}

function mockRapor(
  havuz: AdayProgram[],
  params: RaporParams,
  revizyonNotu?: string,
): RaporSonuc {
  // Prod ile aynı hedef hesabı: mock, uyarlanmış dağılımı da birebir taklit eder
  const hedef = dilimHedefiHesapla(havuz);
  const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;
  const efektif = (p: AdayProgram) => p.sira2025 ?? p.sira2024 ?? 0;
  const dilimden = (dilim: AdayProgram["dilim"], adet: number) =>
    havuz
      .filter((p) => p.dilim === dilim)
      .sort((a, b) => efektif(a) - efektif(b))
      .slice(0, adet);

  const secilen = [
    ...dilimden("hayal", hedef.hayal),
    ...dilimden("dengeli", hedef.dengeli),
    ...dilimden("garanti", hedef.garanti),
  ];

  const tercihler = secilen.map((p, i) => ({
    programId: p.id,
    sira: i + 1,
    dilim: p.dilim,
    gerekce: `(Dev mock) ${p.isim}, seçtiğin ilgi alanlarıyla ve ${params.sira.toLocaleString("tr-TR")}. sıralamanla uyumlu bir aday.`,
    // Risk ve trend mock'ta da gerçek veriden: canlı davranışla birebir aynı
    riskNotu: riskNotuUret(p, params.sira),
    trendOzeti: trendOzetiUret(p),
  }));

  const programlar: RaporSonuc["programlar"] = {};
  for (const p of secilen) {
    programlar[p.id] = programBilgisi(p);
  }

  return {
    tercihler,
    genelDegerlendirme: `(Dev mock — Yapay Zeka anahtarı tanımlı değil, bu rapor gerçek Yapay Zeka çıktısı değildir.) ${params.sira.toLocaleString(
      "tr-TR",
    )}. sıra için hayal/dengeli/garanti dağılımlı ${toplamHedef} tercihlik örnek liste kuruldu.${
      revizyonNotu ? ` Revizyon isteğin not alındı: "${revizyonNotu}".` : ""
    }`,
    uyarilar: [
      "(Dev mock) Tercih dönemi kapanmadan listeyi ÖSYM sistemine kendin gir.",
      "(Dev mock) Garanti dilimindeki son tercihleri asla silme.",
    ],
    programlar,
    kapsam: kapsamUret(hedef),
    devMock: true,
  };
}

export async function raporUret(
  params: RaporParams,
  revizyonNotu?: string,
  oncekiListeOzeti?: string,
): Promise<RaporSonuc> {
  const { havuz, gevsetildi } = havuzOlustur(params);
  if (havuz.length < MIN_LISTE) {
    throw new RaporUretimHatasi(
      "Bu sıralama ve filtrelerle yeterli program bulunamadı. Daha geniş kategori veya il seçmeyi dene.",
    );
  }

  // Dilim hedefleri havuzun gerçek dağılımına uyarlanır: sabit 5/13/6 bazı
  // sıralamalarda (ör. çok iyi derece → dengeli dilim boş) imkânsızdı ve
  // üretim her seferinde doğrulamada patlıyordu.
  const hedef = dilimHedefiHesapla(havuz);
  const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;

  const secimVar =
    (params.kategoriler?.length ?? 0) > 0 ||
    (params.iller?.length ?? 0) > 0 ||
    (params.oncelikler?.length ?? 0) > 0 ||
    (params.universiteTipi && params.universiteTipi !== "farketmez");

  const gevsetmeNotu: string[] = [];
  if (gevsetildi.includes("il")) {
    gevsetmeNotu.push(
      `Adayın seçtiği illerde yeterli program yoktu; havuza başka illerden de program eklendi. Seçili illere öncelik ver ama listeyi ${toplamHedef}'e tamamlamak için diğerlerini de kullan.`,
    );
  }
  if (gevsetildi.includes("kategori")) {
    gevsetmeNotu.push(
      "Adayın seçtiği ilgi alanlarında yeterli program yoktu; havuza yakın alanlardan program eklendi. Seçili alanlara öncelik ver.",
    );
  }

  // Dev'de API key yoksa gerçek Yapay Zeka yerine mock rapor (üretim gecikmesi taklidi)
  if (mockAktif()) {
    await new Promise((r) => setTimeout(r, 1500));
    return mockRapor(havuz, params, revizyonNotu);
  }

  const havuzJson = havuzuKompaktJson(havuz);
  const kullaniciMesaji = [
    `Aday bilgisi: başarı sıralaması ${params.sira.toLocaleString("tr-TR")}, puan türü ${turLabel(params.tur)}.`,
    secimVar ? `ADAYIN SİHİRBAZ SEÇİMLERİ:\n${secimOzeti({
      kategoriler: params.kategoriler ?? [],
      iller: params.iller ?? [],
      universiteTipi: params.universiteTipi ?? "farketmez",
      oncelikler: params.oncelikler ?? [],
    })}\nSeçili kategori ve illere öncelik ver; havuzun elverdiği ölçüde bu seçimlere sadık kal.`
      : null,
    params.notlar ? `Adayın ek notları: ${params.notlar}` : null,
    gevsetmeNotu.length > 0 ? gevsetmeNotu.join("\n") : null,
    revizyonNotu
      ? `REVİZYON İSTEĞİ: Aday mevcut listede şu değişikliği istiyor: ${revizyonNotu}\nÖnceki listenin özeti: ${oncekiListeOzeti ?? "yok"}`
      : null,
    `ADAY HAVUZU (yalnızca buradan seç): ${havuzJson}`,
  ]
    .filter(Boolean)
    .join("\n\n");

  const havuzMap = new Map(havuz.map((p) => [p.id, p]));
  const efektif = (id: string) => {
    const p = havuzMap.get(id)!;
    return p.sira2025 ?? p.sira2024 ?? Number.MAX_SAFE_INTEGER;
  };

  let secim: Record<Dilim, AiSecim[]> | null = null;
  let sonSecim: Record<Dilim, AiSecim[]> | null = null;
  let genel = "";
  let uyarilar: string[] = [];
  let sonHata = "";
  for (let deneme = 0; deneme < 2; deneme++) {
    const parsed = await yapilandirilmisUret({
      system: sistemMetni(hedef),
      kullanici:
        deneme === 0
          ? kullaniciMesaji
          : `${kullaniciMesaji}\n\nÖNCEKİ DENEMENDE HATA VARDI: ${sonHata} Kurallara tam uy.`,
      sema: AiRaporGevsek,
      wireSema: aiSemaWire(hedef),
      // Küçük şema (24 satır × gerekce + genel + uyarılar) ~1-1.5k token;
      // reasoning kapalıyken 4000 rahat bir tavan, kaçak uzun üretimi de keser.
      maxTokens: 4000,
    });

    if (!parsed) {
      sonHata = `Çıktı şemaya uymadı (hayal ${hedef.hayal}, dengeli ${hedef.dengeli}, garanti ${hedef.garanti} olmalı).`;
      continue;
    }
    // Halüsinasyon koruması normalize içinde: havuz-dışı id ve tekrarlar
    // atılır, dilimler havuzdan yeniden atanır. Ret yalnızca sayı tutmayınca.
    const norm = normalizeEt(parsed, havuzMap);
    sonSecim = norm;
    genel = parsed.genelDegerlendirme;
    uyarilar = parsed.uyarilar.slice(0, 3);
    if (DILIMLER.every((d) => norm[d].length === hedef[d])) {
      secim = norm;
      break;
    }
    sonHata = `Dilim sayıları tutmadı (beklenen: hayal ${hedef.hayal}, dengeli ${hedef.dengeli}, garanti ${hedef.garanti}; havuz-dışı ve tekrar eden id'ler sayılmaz). Havuzdaki "dilim" alanına ve adetlere birebir uy.`;
  }

  if (!secim) {
    // AI hiç kullanılabilir program seçemedi (parse iki kez null ya da tüm
    // id'ler havuz-dışı): gerçek üretim hatası, hata ekranı meşru.
    if (!sonSecim) {
      throw new RaporUretimHatasi(
        "Rapor üretilemedi (doğrulama iki denemede de başarısız). Tekrar dener misin? Revizyon hakkın kullanılmadı.",
      );
    }
    const son = sonSecim;
    const eldeki = son.hayal.length + son.dengeli.length + son.garanti.length;
    if (eldeki === 0) {
      throw new RaporUretimHatasi(
        "Rapor üretilemedi (doğrulama iki denemede de başarısız). Tekrar dener misin? Revizyon hakkın kullanılmadı.",
      );
    }
    // Kurtarma ("ne olursa olsun liste kurulur"): fazla dilim en iyi taban
    // sıralamalılar kalacak şekilde kırpılır, eksik dilim havuzun seçilmemiş
    // programlarından deterministik tamamlanır. hedef[d] ≤ havuzdaki sayı
    // olduğu için tamamlama her zaman yeterli aday bulur.
    console.warn(
      `[rapor] kurtarma: AI ${eldeki} kullanılabilir program döndü, hedef ${toplamHedef} (hayal ${son.hayal.length}/${hedef.hayal}, dengeli ${son.dengeli.length}/${hedef.dengeli}, garanti ${son.garanti.length}/${hedef.garanti})`,
    );
    const kullanilan = new Set(
      DILIMLER.flatMap((d) => son[d].map((t) => t.programId)),
    );
    const kurtarilan: Record<Dilim, AiSecim[]> = {
      hayal: [],
      dengeli: [],
      garanti: [],
    };
    for (const d of DILIMLER) {
      const liste = [...son[d]]
        .sort((a, b) => efektif(a.programId) - efektif(b.programId))
        .slice(0, hedef[d]);
      if (liste.length < hedef[d]) {
        const adaylar = havuz
          .filter((p) => p.dilim === d && !kullanilan.has(p.id))
          .sort((a, b) => efektif(a.id) - efektif(b.id));
        for (const p of adaylar) {
          if (liste.length >= hedef[d]) break;
          kullanilan.add(p.id);
          liste.push({
            programId: p.id,
            gerekce:
              "Sıralamana ve seçimlerine uygun olduğu için listene eklendi.",
          });
        }
      }
      kurtarilan[d] = liste;
    }
    secim = kurtarilan;
  }

  const programlar: RaporSonuc["programlar"] = {};
  for (const p of havuz) {
    programlar[p.id] = programBilgisi(p);
  }
  // Sıra ve satır metinleri deterministik: dilim içi iyi taban sıralamasından
  // kötüye dizilir (hayal -> dengeli -> garanti zaten bu yönde akar), sira
  // koddan atanır; riskNotu + trendOzeti gerçek veriden üretilir.
  const nihai = secim;
  const tercihler = DILIMLER.flatMap((dilim) =>
    [...nihai[dilim]]
      .sort((a, b) => efektif(a.programId) - efektif(b.programId))
      .map((t) => ({ dilim, ...t })),
  ).map((t, i) => {
    const p = havuzMap.get(t.programId)!;
    return {
      programId: t.programId,
      sira: i + 1,
      dilim: t.dilim,
      gerekce: t.gerekce,
      riskNotu: riskNotuUret(p, params.sira),
      trendOzeti: trendOzetiUret(p),
    };
  });
  return {
    tercihler,
    genelDegerlendirme:
      genel.trim() ||
      `Listen ${toplamHedef} tercihten oluşuyor; hayal, dengeli ve garanti dilimleri sıralamana göre dengelendi.`,
    uyarilar: uyarilar.length > 0 ? uyarilar : undefined,
    programlar,
    kapsam: kapsamUret(hedef),
  };
}

export function listeOzetiCikar(rapor: RaporSonuc, adaySira?: number): string {
  return rapor.tercihler
    .map((t) => {
      const p = rapor.programlar[t.programId];
      const risk =
        (adaySira != null ? riskHesapla(p?.efektifSira, adaySira) : null) ??
        dilimdenRisk(t.dilim);
      const taban =
        p?.efektifSira != null
          ? ` (taban sırası ~${p.efektifSira.toLocaleString("tr-TR")})`
          : "";
      return `${t.sira}. [${t.dilim} | ${RISK_ETIKET[risk]}] ${p?.isim ?? "?"} — ${p?.universite ?? "?"}${taban}`;
    })
    .join("\n");
}

export type { AdayProgram };
