import type Database from 'better-sqlite3';
import { expandCuisineTags } from '@/lib/cuisine';

// Gemeinsamer Kern-Filter fuer die Restaurant-Auswahl. Wird sowohl vom
// Restaurants-Endpoint (Kartenstapel) als auch vom Joker-Gate ("wer hat noch
// Karten im Stapel?") genutzt, damit beide Stellen garantiert dieselbe
// Auswahl sehen und nicht auseinanderlaufen.

export const DIET_TAG_COLUMN: Record<string, string> = {
  vegan: 'diet_vegan',
  vegetarian: 'diet_vegetarian',
  halal: 'diet_halal',
  gluten_free: 'diet_gluten_free',
};

function haversineKm(lat1: number, lon1: number, lat2: number, lon2: number): number {
  const R = 6371;
  const dLat = ((lat2 - lat1) * Math.PI) / 180;
  const dLon = ((lon2 - lon1) * Math.PI) / 180;
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

let radiusFunctionRegistered = false;
export function ensureRadiusFunction(db: Database.Database) {
  if (radiusFunctionRegistered) return;
  db.function('distance_km', (lat1: number, lon1: number, lat2: number, lon2: number) => {
    if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) return null;
    return haversineKm(lat1, lon1, lat2, lon2);
  });
  radiusFunctionRegistered = true;
}

// Filter-relevante Spalten einer sessions-Zeile.
export interface SessionFilter {
  lat?: number | null;
  lon?: number | null;
  exclude_fast_food?: number;
  exclude_chains?: number;
  price_min?: number | null;
  price_max?: number | null;
  diet_filter?: string | null;
  cuisine_tags?: string | null;
}

// Erwartet eine sessions-Zeile mit den Filter-Spalten. Liefert die zusaetzlichen
// WHERE-Bedingungen (ohne führendes WHERE) plus die zugehoerigen Bind-Argumente.
// Die Bedingungen referenzieren Spalten der "restaurants"-Tabelle OHNE Alias,
// damit sie sowohl in `SELECT * FROM restaurants` als auch in Unterabfragen
// (`SELECT 1 FROM restaurants WHERE ...`) funktionieren.
export function buildCoreRestaurantConditions(
  session: SessionFilter,
  effectiveRadiusKm: number,
): { conditions: string; args: any[] } {
  let conditions = '';
  const args: any[] = [];

  if (session.lat && session.lon) {
    conditions += ' AND lat IS NOT NULL AND lon IS NOT NULL AND distance_km(lat, lon, ?, ?) <= ?';
    args.push(session.lat, session.lon, effectiveRadiusKm);
  }
  if (session.exclude_fast_food) conditions += ' AND is_fast_food = 0';
  if (session.exclude_chains) {
    conditions += ` AND NOT EXISTS (
      SELECT 1 FROM chain_blacklist
      WHERE LOWER(restaurants.name) LIKE '%' || LOWER(chain_blacklist.name) || '%'
    )`;
  }
  if (session.price_min) {
    conditions += ' AND (price_rating IS NULL OR price_rating >= ?)';
    args.push(session.price_min);
  }
  if (session.price_max) {
    conditions += ' AND (price_rating IS NULL OR price_rating <= ?)';
    args.push(session.price_max);
  }
  if (session.diet_filter && DIET_TAG_COLUMN[session.diet_filter]) {
    conditions += ` AND ${DIET_TAG_COLUMN[session.diet_filter]} IN ('yes', 'only')`;
  }

  const cuisineTags: string[] = session.cuisine_tags ? JSON.parse(session.cuisine_tags) : [];
  const cuisineAliases = expandCuisineTags(cuisineTags);
  if (cuisineAliases.length > 0) {
    const cuisineConditions = cuisineAliases.map(() => 'cuisine LIKE ?').join(' OR ');
    conditions += ` AND (${cuisineConditions})`;
    cuisineAliases.forEach((tag) => args.push(`%${tag}%`));
  }

  return { conditions, args };
}

export function effectiveRadiusKm(session: { radius_km?: number | null }, expandKm: number): number {
  return (session.radius_km || 3) + expandKm;
}

// Liefert die aktiven Teilnehmer, die noch mindestens eine verfuegbare Karte im
// Stapel haben (also: ein Restaurant im aktuellen Filter-Umfang, das weder
// gevoted noch gematcht und von diesem Teilnehmer noch nicht geswipet wurde).
// Genutzt als harter Gate fuer den Schicksals-Joker ("alle fertig?").
export function getUnfinishedParticipants(
  db: Database.Database,
  session: SessionFilter & {
    id: number;
    radius_km?: number | null;
    expand_level?: number | null;
  },
): { id: number; display_name: string | null }[] {
  ensureRadiusFunction(db);
  const expandLevel = Number(session.expand_level || 0);
  const expandKm = Number.isFinite(expandLevel) && expandLevel > 0 ? Math.min(expandLevel, 3) * 5 : 0;
  const radius = effectiveRadiusKm(session, expandKm);
  const { conditions, args } = buildCoreRestaurantConditions(session, radius);

  // WICHTIG: Der Kartenstapel ist begrenzt (40 bzw. 60 Karten, siehe
  // Restaurants-Endpoint). Der Gate muss gegen DIESELBE begrenzte Auswahl
  // pruefen - sonst gilt ein Teilnehmer, der seinen kompletten Stapel
  // durchgeswiped hat, trotzdem als "unfertig", weil es im Umkreis ja noch
  // weitere Restaurants gibt, die er nie gesehen hat (Solo-Session koennte
  // den Joker dann nie ziehen). Sponsoren-Karten werden vom Endpoint separat
  // injiziert und frueh geswiped, daher hier ebenfalls ausgeschlossen.
  const limit = expandKm > 0 ? 60 : 40;

  const rows = db
    .prepare(
      `SELECT p.id, p.display_name FROM participants p
       WHERE p.session_id = ? AND p.is_active = 1
         AND EXISTS (
           SELECT 1 FROM (
             SELECT restaurants.id AS rid FROM restaurants
             WHERE 1=1 ${conditions}
               AND restaurants.id NOT IN (SELECT restaurant_id FROM vetos WHERE session_id = ?)
               AND restaurants.id NOT IN (SELECT restaurant_id FROM matches WHERE session_id = ?)
               AND restaurants.id NOT IN (
                 SELECT restaurant_id FROM sponsored_restaurants
                 WHERE restaurant_id IS NOT NULL AND active = 1
                   AND (active_from IS NULL OR active_from <= datetime('now'))
                   AND (active_until IS NULL OR active_until >= datetime('now'))
               )
             LIMIT ${limit}
           ) AS avail
           WHERE avail.rid NOT IN (SELECT restaurant_id FROM swipes WHERE session_id = ? AND participant_id = p.id)
         )`,
    )
    .all(session.id, ...args, session.id, session.id, session.id) as {
    id: number;
    display_name: string | null;
  }[];

  return rows;
}
