import { z } from "zod";
import { anahtarVar } from "./client";
import { yapilandirilmisUret } from "./cagri";
import {
  havuzOlustur,
  havuzuKompaktJson,
  turLabel,
  type RaporParams,
  type AdayProgram,
} from "../rapor-havuzu";
import { secimOzeti } from "../sihirbaz";
import { RISK_ETIKET, dilimdenRisk, riskHesapla } from "../risk";

const TercihSchema = z.object({
  programId: z.string(),
  sira: z.number().int(),
  dilim: z.enum(["hayal", "dengeli", "garanti"]),
  gerekce: z.string(),
  riskNotu: z.string(),
  trendOzeti: z.string(),
});

const RaporSchema = z.object({
  tercihler: z.array(TercihSchema),
  genelDegerlendirme: z.string(),
  uyarilar: z.array(z.string()).min(2).max(3),
});

// 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; 5/13/6'lık
// 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ü). AI yalnızca seçer + gerekce yazar.
const AiTercihSchema = z.object({
  programId: z.string(),
  gerekce: z.string(),
});
const AiRaporSchema = z.object({
  hayal: z.array(AiTercihSchema).length(5),
  dengeli: z.array(AiTercihSchema).length(13),
  garanti: z.array(AiTercihSchema).length(6),
  genelDegerlendirme: z.string(),
  uyarilar: z.array(z.string()).min(2).max(3),
});

// uyarilar şemada zorunlu ama eski kayıtlı raporlarda yok → tipte opsiyonel
export type RaporSonuc = Omit<z.infer<typeof RaporSchema>, "uyarilar"> & {
  uyarilar?: string[];
  // Render için havuzdan zenginleştirilmiş program bilgisi
  // efektifSira: COALESCE(sira2025, sira2024) — eski kayıtlı raporlarda yok
  // siraGecmisi: [2021..2025] taban sıralamaları — deterministik trend oku
  programlar: Record<
    string,
    {
      isim: string;
      universite: string;
      il: string | null;
      unitur: string | null;
      efektifSira?: number | null;
      siraGecmisi?: (number | null)[];
    }
  >;
};

const SISTEM = `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 (TAM 5 program), dengeli (TAM 13), garanti (TAM 6).

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 24 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 {}

// --- 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 {
  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);

  // 5 hayal + 13 dengeli + 6 garanti; eksik dilim varsa havuzdan tamamla
  const secilen = [
    ...dilimden("hayal", 5),
    ...dilimden("dengeli", 13),
    ...dilimden("garanti", 6),
  ];
  for (const p of havuz) {
    if (secilen.length >= 24) break;
    if (!secilen.includes(p)) secilen.push(p);
  }

  const tercihler = secilen.slice(0, 24).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.slice(0, 24)) {
    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ı 24 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 3 tercihi asla silme.",
    ],
    programlar,
  };
}

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

  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 24'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]));

  let sonuc: z.infer<typeof AiRaporSchema> | null = null;
  let sonHata = "";
  for (let deneme = 0; deneme < 2; deneme++) {
    const parsed = await yapilandirilmisUret({
      system: SISTEM,
      kullanici:
        deneme === 0
          ? kullaniciMesaji
          : `${kullaniciMesaji}\n\nÖNCEKİ DENEMENDE HATA VARDI: ${sonHata} Kurallara tam uy.`,
      sema: AiRaporSchema,
      // 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) {
      // Dilim uzunlukları (5/13/6) şemada sabit: sayı tutmazsa da buraya düşer
      sonHata = "Çıktı şemaya uymadı (hayal 5, dengeli 13, garanti 6 olmalı).";
      continue;
    }
    // Halüsinasyon koruması: id'ler havuzda, benzersiz ve doğru dilimde olmalı
    const gruplar = [
      ["hayal", parsed.hayal],
      ["dengeli", parsed.dengeli],
      ["garanti", parsed.garanti],
    ] as const;
    const idler = gruplar.flatMap(([, liste]) => liste.map((t) => t.programId));
    const bilinmeyen = idler.filter((id) => !havuzMap.has(id));
    if (bilinmeyen.length > 0) {
      sonHata = `Havuzda olmayan programId kullanıldı: ${bilinmeyen.slice(0, 3).join(", ")}.`;
      continue;
    }
    if (new Set(idler).size !== idler.length) {
      sonHata = "Aynı programId birden fazla kez kullanıldı; her program tek kez seçilmeli.";
      continue;
    }
    const yanlisDilim = gruplar.flatMap(([dilim, liste]) =>
      liste
        .filter((t) => havuzMap.get(t.programId)!.dilim !== dilim)
        .map((t) => `${t.programId} (${dilim} listesinde ama havuzdaki dilimi farklı)`),
    );
    if (yanlisDilim.length > 0) {
      sonHata = `Programlar yanlış dilim listesine konmuş: ${yanlisDilim.slice(0, 3).join(", ")}. Havuzdaki "dilim" alanına birebir uy.`;
      continue;
    }
    sonuc = parsed;
    break;
  }

  if (!sonuc) {
    throw new RaporUretimHatasi(
      "Rapor üretilemedi (doğrulama iki denemede de başarısız). Tekrar dener misin? Revizyon hakkın kullanılmadı.",
    );
  }

  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
  // 1-24 koddan atanır; riskNotu + trendOzeti gerçek veriden üretilir.
  const efektif = (id: string) => {
    const p = havuzMap.get(id)!;
    return p.sira2025 ?? p.sira2024 ?? Number.MAX_SAFE_INTEGER;
  };
  const tercihler = (
    [
      ["hayal", sonuc.hayal],
      ["dengeli", sonuc.dengeli],
      ["garanti", sonuc.garanti],
    ] as const
  ).flatMap(([dilim, liste]) =>
    [...liste].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: sonuc.genelDegerlendirme,
    uyarilar: sonuc.uyarilar,
    programlar,
  };
}

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 };
