// server-only guard'ı bilinçli yok: scripts/tadimlik-uret.ts bu modülü düz
// node (tsx) altında import ediyor; server-only paketi orada fırlar.
import path from "node:path";
import Database from "better-sqlite3";
import { KATEGORILER, kategoriBul } from "./kategoriler";
import {
  PUAN_TURLERI,
  type DilimKey,
  type Program,
  type ProgramNetSatiri,
  type PuanTuruKey,
  type RankResults,
  type SihirbazFacetleri,
  type UniturGrubu,
} from "@/types/yokatlas";

// Dev'de hot-reload başına yeni bağlantı açılmasın diye global cache
const globalForDb = globalThis as unknown as { yokatlasDb?: Database.Database };

export function getDb(): Database.Database {
  if (!globalForDb.yokatlasDb) {
    globalForDb.yokatlasDb = new Database(
      path.join(process.cwd(), "data", "yokatlas.db"),
      { readonly: true, fileMustExist: true }
    );
  }
  return globalForDb.yokatlasDb;
}

export const SELECT_COLS = `id, isim, universite, unitur, il, fakulte, tur, sure,
  sira2025, sira2024, sira2023, sira2022, sira2021,
  puan2025, kontenjan2025, yerlesen2025`;

// 2025 sıralaması yoksa (az yerleşen/yeni program) 2024'e düşer
export const EFEKTIF_SIRA = "COALESCE(sira2025, sira2024)";

/**
 * Kullanıcının başarı sıralamasına göre programları üç dilime ayırır.
 * Sıralamada küçük sayı daha iyidir; program taban sıralaması kullanıcının
 * sıralamasından küçükse geçen yıl oraya yerleşmek daha iyi dereceyle mümkündü
 * demektir (hayal), büyükse daha güvenli demektir (garanti).
 */
function uniturFiltreSql(grup?: UniturGrubu): { sql: string; args: string[] } {
  if (grup === "devlet") return { sql: "AND unitur = ?", args: ["DEVLET"] };
  if (grup === "vakif")
    return { sql: "AND unitur IS NOT NULL AND unitur <> ?", args: ["DEVLET"] };
  return { sql: "", args: [] };
}

/**
 * Dilim sınırları. Pencereler çarpan bazlı DEĞİL, açık uçlu: hayal adayın
 * üstündeki her şeyi, garanti 1.4× eşiğinin altındaki her şeyi kapsar.
 * "Yakından uzağa" sıralama + limit/offset sayesinde uçtaki gerçekçi olmayan
 * programlar ancak sayfalandıkça görünür; eski ×0.5/×3 pencereleri küçük
 * sıralamalarda (ör. 87) hiç sonuç bırakmıyordu.
 */
export function dilimAraligi(
  sira: number,
  dilim: DilimKey
): { alt: number; ust: number | null } {
  const dengeliUst = Math.round(sira * 1.4);
  if (dilim === "hayal") return { alt: 1, ust: sira - 1 };
  if (dilim === "dengeli") return { alt: sira, ust: dengeliUst };
  return { alt: dengeliUst + 1, ust: null };
}

/**
 * Tek dilimi "yakından uzağa" sıralı ve sayfalı getirir: hayal adayın
 * sıralamasından yukarı (taban DESC), dengeli/garanti aşağı (taban ASC).
 * toplam, limit'ten bağımsız olarak dilimdeki tüm eşleşme sayısıdır.
 */
export function dilimAra(
  sira: number,
  turKey: PuanTuruKey,
  dilim: DilimKey,
  opts: {
    il?: string;
    iller?: string[];
    uniturGrubu?: UniturGrubu;
    limit?: number;
    offset?: number;
  } = {}
): { programlar: Program[]; toplam: number } {
  const db = getDb();
  const tur = PUAN_TURLERI[turKey];
  const onlisans = turKey === "tyt" ? 1 : 0;
  const limit = opts.limit ?? 12;
  const offset = opts.offset ?? 0;

  // il (tekil, geriye uyumlu) veya iller (çoklu) — ikisi de verilirse birleşir
  const illerListe = [
    ...(opts.il ? [opts.il] : []),
    ...(opts.iller ?? []),
  ].filter((v, i, a) => v && a.indexOf(v) === i);
  const ilFiltre =
    illerListe.length > 0
      ? `AND il IN (${illerListe.map(() => "?").join(",")})`
      : "";
  const unitur = uniturFiltreSql(opts.uniturGrubu);

  const { alt, ust } = dilimAraligi(sira, dilim);
  if (ust != null && ust < alt) return { programlar: [], toplam: 0 };
  const siraKosulu =
    ust != null ? `${EFEKTIF_SIRA} BETWEEN ? AND ?` : `${EFEKTIF_SIRA} >= ?`;
  const siraArgs = ust != null ? [alt, ust] : [alt];
  const args = [tur, onlisans, ...illerListe, ...unitur.args, ...siraArgs];

  const base = `FROM programs
    WHERE tur = ? AND onlisans = ? ${ilFiltre} ${unitur.sql}
      AND ${EFEKTIF_SIRA} IS NOT NULL
      AND ${siraKosulu}`;
  // hayal: sınıra en yakın (en büyük taban) önce; diğerleri en yakından uzağa
  const yon = dilim === "hayal" ? "DESC" : "ASC";

  const programlar = db
    .prepare(
      `SELECT ${SELECT_COLS} ${base} ORDER BY ${EFEKTIF_SIRA} ${yon} LIMIT ? OFFSET ?`
    )
    .all(...args, limit, offset) as Program[];
  const toplam = (
    db.prepare(`SELECT COUNT(*) AS adet ${base}`).get(...args) as {
      adet: number;
    }
  ).adet;

  return { programlar, toplam };
}

export function searchByRank(
  sira: number,
  turKey: PuanTuruKey,
  opts: {
    il?: string;
    iller?: string[];
    uniturGrubu?: UniturGrubu;
    limitPerBucket?: number;
  } = {}
): RankResults {
  const ortak = {
    il: opts.il,
    iller: opts.iller,
    uniturGrubu: opts.uniturGrubu,
    limit: opts.limitPerBucket ?? 12,
  };
  const hayal = dilimAra(sira, turKey, "hayal", ortak);
  const dengeli = dilimAra(sira, turKey, "dengeli", ortak);
  const garanti = dilimAra(sira, turKey, "garanti", ortak);

  return {
    hayal: hayal.programlar,
    dengeli: dengeli.programlar,
    garanti: garanti.programlar,
    toplam: {
      hayal: hayal.toplam,
      dengeli: dengeli.toplam,
      garanti: garanti.toplam,
    },
  };
}

/**
 * Programa yerleşen son kişinin yıl bazlı netleri, yeniden eskiye sıralı.
 * Veri scripts/detay.ts ile YÖK Atlas /api/netler/search'ten doldurulur.
 */
export function netlerGetir(programId: string): ProgramNetSatiri[] {
  return getDb()
    .prepare(
      `SELECT yil, puanTuru, tabanPuan, obp,
         tytTrkNet, tytSosNet, tytMatNet, tytFenNet,
         aytMatNet, aytFizNet, aytKimNet, aytBioNet,
         aytTdeNet, aytTrh1Net, aytCog1Net, aytTrh2Net, aytCog2Net,
         aytDinNet, aytFelNet, ydtYdilNet
       FROM netler WHERE program_id = ? ORDER BY yil DESC`
    )
    .all(programId) as ProgramNetSatiri[];
}

// Sihirbaz bubble'ları için erişilebilir program penceresi: hayal için
// gerçekçi alt sınırdan (sira*0.5) itibaren AÇIK uçlu — garanti tarafı artık
// sınırsız olduğundan üst kesim yok. Böylece 500B sıradaki aday Tıp bubble'ı
// görmez; 5B sıradaki görür.
//
// Kademeli (cascade) filtre: adımlar birbirini süzer. Kategoriler yalnızca
// pencereden; iller seçili kategorilerden; üniversite tipi kategori+il'den
// hesaplanır. Böylece "Mühendislik + İzmir" seçen aday 3. adımda yalnızca
// İzmir'deki mühendislik programlarının devlet/vakıf dağılımını görür.
export function rankWindowFacets(
  sira: number,
  turKey: PuanTuruKey,
  secim: { kategoriler?: string[]; iller?: string[] } = {}
): SihirbazFacetleri {
  const db = getDb();
  const tur = PUAN_TURLERI[turKey];
  const onlisans = turKey === "tyt" ? 1 : 0;
  const altSinir = Math.round(sira * 0.5);

  const satirlar = db
    .prepare(
      `SELECT isim, il, unitur FROM programs
       WHERE tur = ? AND onlisans = ? AND ${EFEKTIF_SIRA} IS NOT NULL
         AND ${EFEKTIF_SIRA} >= ?`
    )
    .all(tur, onlisans, altSinir) as {
    isim: string;
    il: string | null;
    unitur: string | null;
  }[];

  // isim → kategori eşlemesi pahalı (12 regex); tekrar eden isimler için önbellek
  const kategoriCache = new Map<string, string[]>();
  const kategorileriGetir = (isim: string): string[] => {
    let k = kategoriCache.get(isim);
    if (!k) {
      k = kategoriBul(isim);
      kategoriCache.set(isim, k);
    }
    return k;
  };

  const seciliKategoriler = secim.kategoriler ?? [];
  const kategoriUyar = (isim: string) =>
    seciliKategoriler.length === 0 ||
    kategorileriGetir(isim).some((k) => seciliKategoriler.includes(k));
  const seciliIller = new Set(secim.iller ?? []);
  const ilUyar = (il: string | null) =>
    seciliIller.size === 0 || (il != null && seciliIller.has(il));

  const kategoriSay = new Map<string, number>();
  const ilSay = new Map<string, number>();
  let devlet = 0;
  let vakif = 0;

  for (const { isim, il, unitur } of satirlar) {
    // 1. adım: kategoriler yalnızca pencereye bağlı (filtre uygulanmaz)
    for (const k of kategorileriGetir(isim)) {
      kategoriSay.set(k, (kategoriSay.get(k) ?? 0) + 1);
    }
    if (!kategoriUyar(isim)) continue;
    // 2. adım: iller seçili kategorilerle süzülür
    if (il) ilSay.set(il, (ilSay.get(il) ?? 0) + 1);
    if (!ilUyar(il)) continue;
    // 3. adım: üniversite tipi kategori + il ile süzülür
    if (unitur === "DEVLET") devlet += 1;
    else if (unitur) vakif += 1;
  }

  const kategoriler = KATEGORILER.map((ad) => ({
    ad,
    adet: kategoriSay.get(ad) ?? 0,
  }))
    .filter((k) => k.adet >= 3) // gürültüyü ele
    .sort((a, b) => b.adet - a.adet);

  const iller = [...ilSay.entries()]
    .map(([il, adet]) => ({ il, adet }))
    .sort((a, b) => b.adet - a.adet)
    .slice(0, 15);

  const uniturler: { grup: UniturGrubu; adet: number }[] = [];
  if (devlet > 0) uniturler.push({ grup: "devlet", adet: devlet });
  if (vakif > 0) uniturler.push({ grup: "vakif", adet: vakif });

  return { kategoriler, iller, uniturler, toplam: satirlar.length };
}
