import pool from '../config/database';
import logger from '../utils/logger';

// ---------------------------------------------------------------------------
// Categories (ausruestung_kategorien_katalog) — hierarchical with parent_id
// ---------------------------------------------------------------------------

async function getKategorien() {
  try {
    const result = await pool.query(
      'SELECT * FROM ausruestung_kategorien_katalog ORDER BY parent_id NULLS FIRST, name',
    );
    return result.rows;
  } catch {
    return [];
  }
}

async function createKategorie(name: string, parentId?: number | null) {
  const result = await pool.query(
    'INSERT INTO ausruestung_kategorien_katalog (name, parent_id) VALUES ($1, $2) RETURNING *',
    [name, parentId ?? null],
  );
  return result.rows[0];
}

async function updateKategorie(id: number, data: { name?: string; parent_id?: number | null }) {
  const fields: string[] = [];
  const params: unknown[] = [];

  if (data.name !== undefined) {
    params.push(data.name);
    fields.push(`name = $${params.length}`);
  }
  if (data.parent_id !== undefined) {
    params.push(data.parent_id);
    fields.push(`parent_id = $${params.length}`);
  }

  if (fields.length === 0) return null;

  params.push(id);
  const result = await pool.query(
    `UPDATE ausruestung_kategorien_katalog SET ${fields.join(', ')} WHERE id = $${params.length} RETURNING *`,
    params,
  );
  return result.rows[0] || null;
}

async function deleteKategorie(id: number) {
  await pool.query('DELETE FROM ausruestung_kategorien_katalog WHERE id = $1', [id]);
}

// ---------------------------------------------------------------------------
// Catalog Items (ausruestung_artikel)
// ---------------------------------------------------------------------------

async function getItems(filters?: { kategorie?: string; kategorie_id?: number; aktiv?: boolean; search?: string }) {
  const conditions: string[] = [];
  const params: unknown[] = [];

  if (filters?.kategorie) {
    params.push(filters.kategorie);
    conditions.push(`aa.kategorie = $${params.length}`);
  }
  if (filters?.kategorie_id) {
    params.push(filters.kategorie_id);
    conditions.push(`aa.kategorie_id = $${params.length}`);
  }
  if (filters?.aktiv !== undefined) {
    params.push(filters.aktiv);
    conditions.push(`aa.aktiv = $${params.length}`);
  }
  if (filters?.search) {
    params.push(`%${filters.search}%`);
    conditions.push(`aa.bezeichnung ILIKE $${params.length}`);
  }

  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
  const result = await pool.query(
    `SELECT aa.*, aa.bevorzugter_lieferant_id, l.name AS bevorzugter_lieferant_name
     FROM ausruestung_artikel aa
     LEFT JOIN lieferanten l ON l.id = aa.bevorzugter_lieferant_id
     ${where}
     ORDER BY aa.kategorie, aa.bezeichnung`,
    params,
  );

  // Enrich with kategorie_name and eigenschaften_count if tables exist
  const rows = result.rows;
  try {
    const katRows = await pool.query('SELECT id, name FROM ausruestung_kategorien_katalog');
    const katMap = new Map(katRows.rows.map((k: { id: number; name: string }) => [k.id, k.name]));
    for (const row of rows) {
      row.kategorie_name = row.kategorie_id ? katMap.get(row.kategorie_id) || null : null;
    }
  } catch { /* table doesn't exist yet */ }

  try {
    const eigCounts = await pool.query(
      'SELECT artikel_id, COUNT(*)::int AS cnt FROM ausruestung_artikel_eigenschaften GROUP BY artikel_id',
    );
    const eigMap = new Map(eigCounts.rows.map((e: { artikel_id: number; cnt: number }) => [e.artikel_id, e.cnt]));
    for (const row of rows) {
      row.eigenschaften_count = eigMap.get(row.id) || 0;
    }
  } catch { /* table doesn't exist yet */ }

  return rows;
}

async function getItemById(id: number) {
  const result = await pool.query(
    `SELECT aa.*, aa.bevorzugter_lieferant_id, l.name AS bevorzugter_lieferant_name
     FROM ausruestung_artikel aa
     LEFT JOIN lieferanten l ON l.id = aa.bevorzugter_lieferant_id
     WHERE aa.id = $1`,
    [id],
  );
  if (!result.rows[0]) return null;

  const row = result.rows[0];

  // Enrich with kategorie_name
  if (row.kategorie_id) {
    try {
      const kat = await pool.query('SELECT name FROM ausruestung_kategorien_katalog WHERE id = $1', [row.kategorie_id]);
      row.kategorie_name = kat.rows[0]?.name || null;
    } catch { /* table doesn't exist */ }
  }

  // Load eigenschaften
  try {
    const eigenschaften = await pool.query(
      'SELECT * FROM ausruestung_artikel_eigenschaften WHERE artikel_id = $1 ORDER BY sort_order, id',
      [id],
    );
    row.eigenschaften = eigenschaften.rows;
  } catch {
    row.eigenschaften = [];
  }

  return row;
}

async function createItem(
  data: {
    bezeichnung: string;
    beschreibung?: string;
    kategorie?: string;
    kategorie_id?: number;
    geschaetzter_preis?: number;
    aktiv?: boolean;
    bevorzugter_lieferant_id?: number | null;
  },
  userId: string,
) {
  // Build column list dynamically based on whether kategorie_id column exists
  const cols = ['bezeichnung', 'beschreibung', 'kategorie', 'geschaetzter_preis', 'aktiv', 'erstellt_von', 'bevorzugter_lieferant_id'];
  const vals = [data.bezeichnung, data.beschreibung || null, data.kategorie || null, data.geschaetzter_preis || null, data.aktiv ?? true, userId, data.bevorzugter_lieferant_id ?? null];

  if (data.kategorie_id) {
    cols.push('kategorie_id');
    vals.push(data.kategorie_id as unknown as string);
  }

  const placeholders = vals.map((_, i) => `$${i + 1}`).join(', ');
  const result = await pool.query(
    `INSERT INTO ausruestung_artikel (${cols.join(', ')}) VALUES (${placeholders}) RETURNING *`,
    vals,
  );
  return result.rows[0];
}

async function updateItem(
  id: number,
  data: {
    bezeichnung?: string;
    beschreibung?: string;
    kategorie?: string;
    kategorie_id?: number | null;
    geschaetzter_preis?: number;
    aktiv?: boolean;
    bevorzugter_lieferant_id?: number | null;
  },
  _userId: string,
) {
  const fields: string[] = [];
  const params: unknown[] = [];

  if (data.bezeichnung !== undefined) {
    params.push(data.bezeichnung);
    fields.push(`bezeichnung = $${params.length}`);
  }
  if (data.beschreibung !== undefined) {
    params.push(data.beschreibung);
    fields.push(`beschreibung = $${params.length}`);
  }
  if (data.kategorie !== undefined) {
    params.push(data.kategorie);
    fields.push(`kategorie = $${params.length}`);
  }
  if (data.kategorie_id !== undefined) {
    params.push(data.kategorie_id);
    fields.push(`kategorie_id = $${params.length}`);
  }
  if (data.geschaetzter_preis !== undefined) {
    params.push(data.geschaetzter_preis);
    fields.push(`geschaetzter_preis = $${params.length}`);
  }
  if (data.aktiv !== undefined) {
    params.push(data.aktiv);
    fields.push(`aktiv = $${params.length}`);
  }
  if (data.bevorzugter_lieferant_id !== undefined) {
    params.push(data.bevorzugter_lieferant_id);
    fields.push(`bevorzugter_lieferant_id = $${params.length}`);
  }

  if (fields.length === 0) {
    return getItemById(id);
  }

  params.push(new Date());
  fields.push(`aktualisiert_am = $${params.length}`);

  params.push(id);
  const result = await pool.query(
    `UPDATE ausruestung_artikel SET ${fields.join(', ')} WHERE id = $${params.length} RETURNING *`,
    params,
  );
  return result.rows[0] || null;
}

async function deleteItem(id: number) {
  await pool.query('DELETE FROM ausruestung_artikel WHERE id = $1', [id]);
}

async function getCategories() {
  const result = await pool.query(
    'SELECT DISTINCT kategorie FROM ausruestung_artikel WHERE kategorie IS NOT NULL ORDER BY kategorie',
  );
  return result.rows.map((r: { kategorie: string }) => r.kategorie);
}

// ---------------------------------------------------------------------------
// Artikel Eigenschaften (characteristics)
// ---------------------------------------------------------------------------

async function getArtikelEigenschaften(artikelId: number) {
  try {
    const result = await pool.query(
      'SELECT * FROM ausruestung_artikel_eigenschaften WHERE artikel_id = $1 ORDER BY sort_order, id',
      [artikelId],
    );
    return result.rows;
  } catch {
    return [];
  }
}

async function upsertArtikelEigenschaft(
  artikelId: number,
  data: { id?: number; name: string; typ: string; optionen?: string[]; pflicht?: boolean; sort_order?: number },
) {
  if (data.id) {
    const result = await pool.query(
      `UPDATE ausruestung_artikel_eigenschaften
       SET name = $1, typ = $2, optionen = $3, pflicht = $4, sort_order = $5
       WHERE id = $6 AND artikel_id = $7
       RETURNING *`,
      [data.name, data.typ, data.optionen || null, data.pflicht ?? false, data.sort_order ?? 0, data.id, artikelId],
    );
    return result.rows[0] || null;
  }
  const result = await pool.query(
    `INSERT INTO ausruestung_artikel_eigenschaften (artikel_id, name, typ, optionen, pflicht, sort_order)
     VALUES ($1, $2, $3, $4, $5, $6)
     RETURNING *`,
    [artikelId, data.name, data.typ, data.optionen || null, data.pflicht ?? false, data.sort_order ?? 0],
  );
  return result.rows[0];
}

async function deleteArtikelEigenschaft(id: number) {
  await pool.query('DELETE FROM ausruestung_artikel_eigenschaften WHERE id = $1', [id]);
}

// ---------------------------------------------------------------------------
// Users (for "order on behalf of" autocomplete)
// ---------------------------------------------------------------------------

async function getAllUsers() {
  const result = await pool.query(
    `SELECT id, COALESCE(given_name || ' ' || family_name, name) AS name
     FROM users
     WHERE is_active = true
     ORDER BY name`,
  );
  return result.rows;
}

// ---------------------------------------------------------------------------
// Requests (ausruestung_anfragen)
// ---------------------------------------------------------------------------

async function getRequests(filters?: { status?: string; anfrager_id?: string }) {
  const conditions: string[] = [];
  const params: unknown[] = [];

  if (filters?.status) {
    params.push(filters.status);
    conditions.push(`a.status = $${params.length}`);
  }
  if (filters?.anfrager_id) {
    params.push(filters.anfrager_id);
    conditions.push(`a.anfrager_id = $${params.length}`);
  }

  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
  const result = await pool.query(
    `SELECT a.*,
            COALESCE(u.given_name || ' ' || u.family_name, u.name) AS anfrager_name,
            COALESCE(u2.given_name || ' ' || u2.family_name, u2.name) AS bearbeitet_von_name,
            a.fuer_benutzer_name,
            (SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id) AS positionen_count,
            (SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.geliefert) AS geliefert_count,
            (SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.im_haus) AS im_haus_count,
            EXISTS(
              SELECT 1 FROM ausruestung_anfrage_bestellung ab
              JOIN bestellungen b ON b.id = ab.bestellung_id
              WHERE ab.anfrage_id = a.id
              AND b.status IN ('lieferung_pruefen', 'abgeschlossen')
            ) AS im_haus
     FROM ausruestung_anfragen a
     LEFT JOIN users u  ON u.id  = a.anfrager_id
     LEFT JOIN users u2 ON u2.id = a.bearbeitet_von
     ${where}
     ORDER BY a.erstellt_am DESC`,
    params,
  );
  return result.rows;
}

async function getMyRequests(userId: string) {
  const result = await pool.query(
    `SELECT a.*,
            (SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id) AS positionen_count,
            (SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.geliefert) AS geliefert_count,
            (SELECT COUNT(*)::int FROM ausruestung_anfrage_positionen p WHERE p.anfrage_id = a.id AND p.im_haus) AS im_haus_count,
            EXISTS(
              SELECT 1 FROM ausruestung_anfrage_bestellung ab
              JOIN bestellungen b ON b.id = ab.bestellung_id
              WHERE ab.anfrage_id = a.id
              AND b.status IN ('lieferung_pruefen', 'abgeschlossen')
            ) AS im_haus
     FROM ausruestung_anfragen a
     WHERE a.anfrager_id = $1
     ORDER BY a.erstellt_am DESC`,
    [userId],
  );
  return result.rows;
}

async function getRequestById(id: number) {
  const reqResult = await pool.query(
    `SELECT a.*,
            COALESCE(u.given_name || ' ' || u.family_name, u.name) AS anfrager_name,
            COALESCE(u2.given_name || ' ' || u2.family_name, u2.name) AS bearbeitet_von_name,
            a.fuer_benutzer_name
     FROM ausruestung_anfragen a
     LEFT JOIN users u  ON u.id  = a.anfrager_id
     LEFT JOIN users u2 ON u2.id = a.bearbeitet_von
     WHERE a.id = $1`,
    [id],
  );
  if (reqResult.rows.length === 0) return null;

  const positionen = await pool.query(
    `SELECT p.*, sa.bezeichnung AS artikel_bezeichnung, sa.kategorie AS artikel_kategorie
     FROM ausruestung_anfrage_positionen p
     LEFT JOIN ausruestung_artikel sa ON sa.id = p.artikel_id
     WHERE p.anfrage_id = $1
     ORDER BY p.id`,
    [id],
  );

  // Load eigenschaft values per position
  const positionIds = positionen.rows.map((p: { id: number }) => p.id);
  let eigenschaftenMap: Record<number, { eigenschaft_id: number; eigenschaft_name: string; wert: string }[]> = {};
  if (positionIds.length > 0) {
    try {
      const eigenschaftenResult = await pool.query(
        `SELECT pe.position_id, pe.eigenschaft_id, ae.name AS eigenschaft_name, pe.wert
         FROM ausruestung_position_eigenschaften pe
         JOIN ausruestung_artikel_eigenschaften ae ON ae.id = pe.eigenschaft_id
         WHERE pe.position_id = ANY($1)
         ORDER BY ae.sort_order, ae.id`,
        [positionIds],
      );
      for (const row of eigenschaftenResult.rows) {
        if (!eigenschaftenMap[row.position_id]) eigenschaftenMap[row.position_id] = [];
        eigenschaftenMap[row.position_id].push({
          eigenschaft_id: row.eigenschaft_id,
          eigenschaft_name: row.eigenschaft_name,
          wert: row.wert,
        });
      }
    } catch { /* table may not exist */ }
  }

  // Load per-position vendor order info (anfrage_position_id FK, added in migration 096)
  let positionBestellInfoMap: Record<number, {
    bestellung_id: number;
    bestellung_bezeichnung: string;
    bestellung_status: string;
    bestellung_laufende_nummer: number | null;
    bestellung_erstellt_am: string;
    erhalten_menge: number;
    bestellt_menge: number;
  }> = {};
  if (positionIds.length > 0) {
    try {
      const bpResult = await pool.query(
        `SELECT bp.anfrage_position_id,
                bp.bestellung_id,
                bp.erhalten_menge,
                bp.menge AS bestellt_menge,
                b.bezeichnung AS bestellung_bezeichnung,
                b.status AS bestellung_status,
                b.laufende_nummer AS bestellung_laufende_nummer,
                b.erstellt_am AS bestellung_erstellt_am
         FROM bestellpositionen bp
         JOIN bestellungen b ON b.id = bp.bestellung_id
         WHERE bp.anfrage_position_id = ANY($1)`,
        [positionIds],
      );
      for (const row of bpResult.rows) {
        positionBestellInfoMap[row.anfrage_position_id] = {
          bestellung_id: row.bestellung_id,
          bestellung_bezeichnung: row.bestellung_bezeichnung,
          bestellung_status: row.bestellung_status,
          bestellung_laufende_nummer: row.bestellung_laufende_nummer ?? null,
          bestellung_erstellt_am: row.bestellung_erstellt_am,
          erhalten_menge: Number(row.erhalten_menge),
          bestellt_menge: Number(row.bestellt_menge),
        };
      }
    } catch { /* bestellpositionen.anfrage_position_id may not exist */ }
  }

  const positionenWithEigenschaften = positionen.rows.map((p: { id: number }) => ({
    ...p,
    eigenschaften: eigenschaftenMap[p.id] || [],
    bestellung_info: positionBestellInfoMap[p.id] ?? null,
  }));

  // Load linked bestellungen with delivery totals
  let linkedBestellungen: unknown[] = [];
  try {
    const bestellungen = await pool.query(
      `SELECT b.id, b.bezeichnung, b.status, b.laufende_nummer, b.erstellt_am,
              COALESCE(SUM(bp.erhalten_menge), 0)::int AS total_received,
              COALESCE(SUM(bp.menge), 0)::int AS total_ordered
       FROM ausruestung_anfrage_bestellung ab
       JOIN bestellungen b ON b.id = ab.bestellung_id
       LEFT JOIN bestellpositionen bp ON bp.bestellung_id = b.id
       WHERE ab.anfrage_id = $1
       GROUP BY b.id, b.bezeichnung, b.status, b.laufende_nummer, b.erstellt_am`,
      [id],
    );
    linkedBestellungen = bestellungen.rows;
  } catch { /* table may not exist */ }

  // Determine im_haus: true if any linked bestellung has status lieferung_pruefen or abgeschlossen
  const imHaus = (linkedBestellungen as { status: string }[]).some(
    (b) => b.status === 'lieferung_pruefen' || b.status === 'abgeschlossen',
  );

  return {
    anfrage: reqResult.rows[0],
    positionen: positionenWithEigenschaften,
    linked_bestellungen: linkedBestellungen,
    im_haus: imHaus,
  };
}

async function createRequest(
  userId: string,
  items: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string; ist_ersatz?: boolean; eigenschaften?: { eigenschaft_id: number; wert: string }[]; persoenlich_id?: string; neuer_zustand?: string }[],
  notizen?: string,
  bezeichnung?: string,
  fuerBenutzerName?: string,
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    const currentYear = new Date().getFullYear();

    // Try with bestell_nummer/bestell_jahr (migration 050), fallback without
    let anfrage: Record<string, unknown>;
    try {
      await client.query('SAVEPOINT sp_bestell_nr');
      const maxResult = await client.query(
        `SELECT COALESCE(MAX(bestell_nummer), 0) + 1 AS next_nr
         FROM ausruestung_anfragen
         WHERE bestell_jahr = $1`,
        [currentYear],
      );
      const nextNr = maxResult.rows[0].next_nr;
      const anfrageResult = await client.query(
        `INSERT INTO ausruestung_anfragen (anfrager_id, notizen, bezeichnung, bestell_nummer, bestell_jahr, fuer_benutzer_name)
         VALUES ($1, $2, $3, $4, $5, $6)
         RETURNING *`,
        [userId, notizen || null, bezeichnung || null, nextNr, currentYear, fuerBenutzerName || null],
      );
      await client.query('RELEASE SAVEPOINT sp_bestell_nr');
      anfrage = anfrageResult.rows[0];
    } catch {
      await client.query('ROLLBACK TO SAVEPOINT sp_bestell_nr');
      const anfrageResult = await client.query(
        `INSERT INTO ausruestung_anfragen (anfrager_id, notizen, bezeichnung, fuer_benutzer_name)
         VALUES ($1, $2, $3, $4)
         RETURNING *`,
        [userId, notizen || null, bezeichnung || null, fuerBenutzerName || null],
      );
      anfrage = anfrageResult.rows[0];
    }

    for (const item of items) {
      let itemBezeichnung = item.bezeichnung;
      let itemArtikelId = item.artikel_id || null;
      let itemPersoenlichId: string | null = null;
      let itemAktuellerZustand: string | null = null;
      let itemNeuerZustand: string | null = null;

      if (item.persoenlich_id) {
        // Load personal item details
        const persResult = await client.query(
          'SELECT bezeichnung, zustand, artikel_id FROM persoenliche_ausruestung WHERE id = $1',
          [item.persoenlich_id],
        );
        if (persResult.rows.length > 0) {
          const pers = persResult.rows[0];
          itemBezeichnung = pers.bezeichnung;
          itemArtikelId = pers.artikel_id || itemArtikelId;
          itemPersoenlichId = item.persoenlich_id;
          itemAktuellerZustand = pers.zustand || null;
          itemNeuerZustand = item.neuer_zustand || null;
        }
      } else if (item.artikel_id) {
        const artikelResult = await client.query(
          'SELECT bezeichnung FROM ausruestung_artikel WHERE id = $1',
          [item.artikel_id],
        );
        if (artikelResult.rows.length > 0) {
          itemBezeichnung = artikelResult.rows[0].bezeichnung;
        }
      }

      await client.query(
        `INSERT INTO ausruestung_anfrage_positionen (anfrage_id, artikel_id, bezeichnung, menge, notizen, ist_ersatz, persoenlich_id, aktueller_zustand, neuer_zustand)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
        [anfrage.id, itemArtikelId, itemBezeichnung, item.menge, item.notizen || null, item.ist_ersatz ?? false, itemPersoenlichId, itemAktuellerZustand, itemNeuerZustand],
      );

      // NOTE: eigenschaft values are NOT saved in the transaction to avoid
      // aborting the transaction if the table doesn't exist.
    }

    await client.query('COMMIT');

    // Save eigenschaft values OUTSIDE the transaction so a missing table
    // won't rollback the entire request creation.
    for (const item of items) {
      if (!item.eigenschaften || item.eigenschaften.length === 0) continue;
      // Find the position ID we just inserted
      const posRes = await pool.query(
        `SELECT id FROM ausruestung_anfrage_positionen
         WHERE anfrage_id = $1 AND COALESCE(artikel_id, 0) = $2
         ORDER BY id DESC LIMIT 1`,
        [anfrage.id, item.artikel_id || 0],
      );
      if (posRes.rows.length === 0) continue;
      const posId = posRes.rows[0].id;
      for (const e of item.eigenschaften) {
        try {
          await pool.query(
            `INSERT INTO ausruestung_position_eigenschaften (position_id, eigenschaft_id, wert)
             VALUES ($1, $2, $3)
             ON CONFLICT (position_id, eigenschaft_id) DO UPDATE SET wert = $3`,
            [posId, e.eigenschaft_id, e.wert],
          );
        } catch { /* table may not exist */ }
      }
    }

    return getRequestById(anfrage.id as number);
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('ausruestungsanfrageService.createRequest failed', { error });
    throw error;
  } finally {
    client.release();
  }
}

async function updateRequest(
  id: number,
  data: {
    bezeichnung?: string;
    notizen?: string;
    items?: { artikel_id?: number; bezeichnung: string; menge: number; notizen?: string; ist_ersatz?: boolean; eigenschaften?: { eigenschaft_id: number; wert: string }[] }[];
  },
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    const fields: string[] = [];
    const params: unknown[] = [];

    if (data.bezeichnung !== undefined) {
      params.push(data.bezeichnung || null);
      fields.push(`bezeichnung = $${params.length}`);
    }
    if (data.notizen !== undefined) {
      params.push(data.notizen || null);
      fields.push(`notizen = $${params.length}`);
    }

    if (fields.length > 0) {
      params.push(new Date());
      fields.push(`aktualisiert_am = $${params.length}`);
      params.push(id);
      await client.query(
        `UPDATE ausruestung_anfragen SET ${fields.join(', ')} WHERE id = $${params.length}`,
        params,
      );
    }

    if (data.items) {
      // Preserve geliefert state for matching items (by artikel_id + bezeichnung)
      const existingPositionen = await client.query(
        'SELECT artikel_id, bezeichnung, geliefert FROM ausruestung_anfrage_positionen WHERE anfrage_id = $1',
        [id],
      );
      const geliefertMap = new Map<string, boolean>();
      for (const p of existingPositionen.rows) {
        geliefertMap.set(`${p.artikel_id ?? 0}:${p.bezeichnung}`, p.geliefert);
      }

      await client.query('DELETE FROM ausruestung_anfrage_positionen WHERE anfrage_id = $1', [id]);
      for (const item of data.items) {
        let itemBezeichnung = item.bezeichnung;
        if (item.artikel_id) {
          const artikelResult = await client.query(
            'SELECT bezeichnung FROM ausruestung_artikel WHERE id = $1',
            [item.artikel_id],
          );
          if (artikelResult.rows.length > 0) {
            itemBezeichnung = artikelResult.rows[0].bezeichnung;
          }
        }
        const prevGeliefert = geliefertMap.get(`${item.artikel_id ?? 0}:${itemBezeichnung}`) ?? false;
        await client.query(
          `INSERT INTO ausruestung_anfrage_positionen (anfrage_id, artikel_id, bezeichnung, menge, notizen, geliefert, ist_ersatz)
           VALUES ($1, $2, $3, $4, $5, $6, $7)`,
          [id, item.artikel_id || null, itemBezeichnung, item.menge, item.notizen || null, prevGeliefert, item.ist_ersatz ?? false],
        );
      }
    }

    await client.query('COMMIT');

    // Save eigenschaft values outside transaction
    if (data.items) {
      for (const item of data.items) {
        if (!item.eigenschaften || item.eigenschaften.length === 0) continue;
        const posRes = await pool.query(
          `SELECT id FROM ausruestung_anfrage_positionen
           WHERE anfrage_id = $1 AND COALESCE(artikel_id, 0) = $2
           ORDER BY id DESC LIMIT 1`,
          [id, item.artikel_id || 0],
        );
        if (posRes.rows.length === 0) continue;
        const posId = posRes.rows[0].id;
        for (const e of item.eigenschaften) {
          try {
            await pool.query(
              `INSERT INTO ausruestung_position_eigenschaften (position_id, eigenschaft_id, wert)
               VALUES ($1, $2, $3)
               ON CONFLICT (position_id, eigenschaft_id) DO UPDATE SET wert = $3`,
              [posId, e.eigenschaft_id, e.wert],
            );
          } catch { /* table may not exist */ }
        }
      }
    }

    return getRequestById(id);
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('ausruestungsanfrageService.updateRequest failed', { error });
    throw error;
  } finally {
    client.release();
  }
}

async function updatePositionGeliefert(positionId: number, geliefert: boolean) {
  const result = await pool.query(
    `UPDATE ausruestung_anfrage_positionen SET geliefert = $1 WHERE id = $2 RETURNING *`,
    [geliefert, positionId],
  );
  const position = result.rows[0];
  if (!position) return null;

  // Auto-complete: if all positions are geliefert, set anfrage status to 'erledigt'
  if (geliefert) {
    const check = await pool.query(
      `SELECT COUNT(*) FILTER (WHERE NOT geliefert)::int AS remaining
       FROM ausruestung_anfrage_positionen
       WHERE anfrage_id = $1`,
      [position.anfrage_id],
    );
    if (check.rows[0].remaining === 0) {
      await pool.query(
        `UPDATE ausruestung_anfragen SET status = 'erledigt', aktualisiert_am = NOW() WHERE id = $1`,
        [position.anfrage_id],
      );
    }
  }

  return position;
}

async function updatePositionZurueckgegeben(positionId: number, zurueckgegeben: boolean) {
  const result = await pool.query(
    `UPDATE ausruestung_anfrage_positionen SET altes_geraet_zurueckgegeben = $1 WHERE id = $2 RETURNING *`,
    [zurueckgegeben, positionId],
  );
  return result.rows[0] || null;
}

async function updateRequestStatus(
  id: number,
  status: string,
  adminNotizen?: string,
  bearbeitetVon?: string,
) {
  // Use aktualisiert_am (always exists) + try bearbeitet_am (added in migration 050)
  let updated;
  try {
    const result = await pool.query(
      `UPDATE ausruestung_anfragen
       SET status = $1,
           admin_notizen = COALESCE($2, admin_notizen),
           bearbeitet_von = COALESCE($3, bearbeitet_von),
           bearbeitet_am = NOW(),
           aktualisiert_am = NOW()
       WHERE id = $4
       RETURNING *`,
      [status, adminNotizen || null, bearbeitetVon || null, id],
    );
    updated = result.rows[0] || null;
  } catch {
    // Fallback if bearbeitet_am column doesn't exist yet (migration 050 not run)
    const result = await pool.query(
      `UPDATE ausruestung_anfragen
       SET status = $1,
           admin_notizen = COALESCE($2, admin_notizen),
           bearbeitet_von = COALESCE($3, bearbeitet_von),
           aktualisiert_am = NOW()
       WHERE id = $4
       RETURNING *`,
      [status, adminNotizen || null, bearbeitetVon || null, id],
    );
    updated = result.rows[0] || null;
  }

  // When status changes to 'erledigt', mark all positions as geliefert
  if (status === 'erledigt') {
    try {
      await pool.query(
        `UPDATE ausruestung_anfrage_positionen SET geliefert = true WHERE anfrage_id = $1`,
        [id],
      );
    } catch { /* column may not exist yet */ }

    // Apply personal equipment status changes
    const statusChangePositionen = await pool.query(
      `SELECT persoenlich_id, neuer_zustand FROM ausruestung_anfrage_positionen
       WHERE anfrage_id = $1 AND persoenlich_id IS NOT NULL AND neuer_zustand IS NOT NULL`,
      [id],
    );
    for (const row of statusChangePositionen.rows) {
      await pool.query(
        `UPDATE persoenliche_ausruestung SET zustand = $1 WHERE id = $2`,
        [row.neuer_zustand, row.persoenlich_id],
      );
    }
  }

  return updated;
}

async function deleteRequest(id: number) {
  await pool.query('DELETE FROM ausruestung_anfragen WHERE id = $1', [id]);
}

// ---------------------------------------------------------------------------
// Linking (ausruestung_anfrage_bestellung)
// ---------------------------------------------------------------------------

async function linkToOrder(anfrageId: number, bestellungId: number) {
  await pool.query(
    `INSERT INTO ausruestung_anfrage_bestellung (anfrage_id, bestellung_id)
     VALUES ($1, $2)
     ON CONFLICT DO NOTHING`,
    [anfrageId, bestellungId],
  );
}

async function unlinkFromOrder(anfrageId: number, bestellungId: number) {
  await pool.query(
    'DELETE FROM ausruestung_anfrage_bestellung WHERE anfrage_id = $1 AND bestellung_id = $2',
    [anfrageId, bestellungId],
  );
}

async function getLinkedOrders(anfrageId: number) {
  const result = await pool.query(
    `SELECT b.*
     FROM ausruestung_anfrage_bestellung ab
     JOIN bestellungen b ON b.id = ab.bestellung_id
     WHERE ab.anfrage_id = $1`,
    [anfrageId],
  );
  return result.rows;
}

async function createOrdersFromRequest(
  anfrageId: number,
  orders: Array<{
    lieferant_id: number;
    bezeichnung: string;
    positionen: Array<{ position_id: number; bezeichnung: string; menge: number; einheit?: string; notizen?: string }>;
  }>,
  userId: string,
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    const createdBestellungen: Array<{ id: number; bezeichnung: string; lieferant_name: string }> = [];

    // Fetch anfrager_id so we can set mitglied_id on created orders
    const anfrageResult = await client.query(
      'SELECT anfrager_id FROM ausruestung_anfragen WHERE id = $1',
      [anfrageId]
    );
    const anfragerId: string | null = anfrageResult.rows[0]?.anfrager_id ?? null;

    for (const orderData of orders) {
      const nrResult = await client.query(
        `SELECT COALESCE(MAX(laufende_nummer), 0) + 1 AS next_nr
         FROM bestellungen
         WHERE EXTRACT(YEAR FROM erstellt_am) = EXTRACT(YEAR FROM NOW())`
      );
      const laufendeNummer = nrResult.rows[0].next_nr;

      const bestellungResult = await client.query(
        `INSERT INTO bestellungen (bezeichnung, lieferant_id, status, mitglied_id, laufende_nummer, erstellt_von)
         VALUES ($1, $2, 'wartet_auf_genehmigung', $3, $4, $5)
         RETURNING id, bezeichnung`,
        [orderData.bezeichnung, orderData.lieferant_id, anfragerId, laufendeNummer, userId]
      );
      const bestellung = bestellungResult.rows[0];

      for (const pos of orderData.positionen) {
        // Look up the anfrage position to get artikel_id and eigenschaften
        let artikelId: number | null = null;
        let spezifikationen: string[] = [];
        if (pos.position_id) {
          const posResult = await client.query(
            `SELECT p.artikel_id FROM ausruestung_anfrage_positionen p WHERE p.id = $1`,
            [pos.position_id]
          );
          if (posResult.rows.length > 0) {
            artikelId = posResult.rows[0].artikel_id || null;
          }
          // Load eigenschaften and map to spezifikationen strings
          try {
            const eigResult = await client.query(
              `SELECT ae.name AS eigenschaft_name, pe.wert
               FROM ausruestung_position_eigenschaften pe
               JOIN ausruestung_artikel_eigenschaften ae ON ae.id = pe.eigenschaft_id
               WHERE pe.position_id = $1
               ORDER BY ae.sort_order, ae.id`,
              [pos.position_id]
            );
            spezifikationen = eigResult.rows.map((e: { eigenschaft_name: string; wert: string }) => `${e.eigenschaft_name}: ${e.wert}`);
          } catch { /* table may not exist */ }
        }

        await client.query(
          `INSERT INTO bestellpositionen (bestellung_id, bezeichnung, menge, einheit, notizen, artikel_id, spezifikationen, anfrage_position_id)
           VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)`,
          [bestellung.id, pos.bezeichnung, pos.menge, pos.einheit || 'Stk', pos.notizen || null, artikelId, JSON.stringify(spezifikationen), pos.position_id || null]
        );
      }

      await client.query(
        `INSERT INTO ausruestung_anfrage_bestellung (anfrage_id, bestellung_id)
         VALUES ($1, $2) ON CONFLICT DO NOTHING`,
        [anfrageId, bestellung.id]
      );

      const vendorResult = await client.query('SELECT name FROM lieferanten WHERE id = $1', [orderData.lieferant_id]);
      const lieferantName = vendorResult.rows[0]?.name ?? '';

      createdBestellungen.push({ id: bestellung.id, bezeichnung: bestellung.bezeichnung, lieferant_name: lieferantName });
    }

    await client.query(
      `UPDATE ausruestung_anfragen SET status = 'bestellt', aktualisiert_am = NOW() WHERE id = $1`,
      [anfrageId]
    );

    await client.query('COMMIT');
    return createdBestellungen;
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('AusruestungsanfrageService.createOrdersFromRequest failed', { error });
    throw new Error('Bestellungen konnten nicht erstellt werden');
  } finally {
    client.release();
  }
}

// ---------------------------------------------------------------------------
// Overview (aggregated)
// ---------------------------------------------------------------------------

async function getOverview() {
  const aggregated = await pool.query(
    `SELECT p.bezeichnung,
            SUM(p.menge)::int AS total_menge,
            COUNT(DISTINCT p.anfrage_id)::int AS anfrage_count
     FROM ausruestung_anfrage_positionen p
     JOIN ausruestung_anfragen a ON a.id = p.anfrage_id
     WHERE a.status IN ('offen', 'genehmigt')
     GROUP BY p.bezeichnung
     ORDER BY total_menge DESC, p.bezeichnung`,
  );

  const counts = await pool.query(
    `SELECT
       COUNT(*) FILTER (WHERE status = 'offen')::int AS pending_count,
       COUNT(*) FILTER (WHERE status = 'genehmigt')::int AS approved_count,
       COUNT(*) FILTER (WHERE status = 'offen' AND bearbeitet_von IS NULL)::int AS unhandled_count,
       COALESCE(SUM(sub.total), 0)::int AS total_items
     FROM ausruestung_anfragen a
     LEFT JOIN LATERAL (
       SELECT SUM(p.menge) AS total
       FROM ausruestung_anfrage_positionen p
       WHERE p.anfrage_id = a.id
     ) sub ON true
     WHERE a.status IN ('offen', 'genehmigt')`,
  );

  return {
    items: aggregated.rows,
    ...counts.rows[0],
  };
}

// ---------------------------------------------------------------------------
// Widget overview (lightweight counts only)
// ---------------------------------------------------------------------------

async function getWidgetOverview() {
  const result = await pool.query(
    `SELECT
       COUNT(*)::int AS total_count,
       COUNT(*) FILTER (WHERE status = 'offen')::int AS pending_count,
       COUNT(*) FILTER (WHERE status = 'genehmigt')::int AS approved_count,
       COUNT(*) FILTER (WHERE status = 'offen' AND bearbeitet_von IS NULL)::int AS unhandled_count
     FROM ausruestung_anfragen`,
  );
  return result.rows[0];
}

// ---------------------------------------------------------------------------
// Assignment of delivered items
// ---------------------------------------------------------------------------

interface AssignmentInput {
  positionId: number;
  typ: 'ausruestung' | 'persoenlich' | 'keine';
  fahrzeugId?: string;
  standort?: string;
  userId?: string;
  benutzerName?: string;
  groesse?: string;
  kategorie?: string;
  eigenschaften?: { eigenschaft_id?: number; name: string; wert: string }[];
}

async function assignDeliveredItems(
  anfrageId: number,
  requestingUserId: string,
  assignments: AssignmentInput[],
) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // Load anfrage to get anfrager_id (default user for personal assignments)
    const anfrageResult = await client.query(
      'SELECT anfrager_id FROM ausruestung_anfragen WHERE id = $1',
      [anfrageId],
    );
    if (anfrageResult.rows.length === 0) {
      throw new Error('Anfrage nicht gefunden');
    }
    const anfragerId = anfrageResult.rows[0].anfrager_id;

    let assigned = 0;

    for (const a of assignments) {
      // Load position details
      const posResult = await client.query(
        'SELECT bezeichnung, artikel_id FROM ausruestung_anfrage_positionen WHERE id = $1 AND anfrage_id = $2',
        [a.positionId, anfrageId],
      );
      if (posResult.rows.length === 0) continue;
      const pos = posResult.rows[0];

      if (a.typ === 'ausruestung') {
        // Look up kategorie_id from artikel if available
        let kategorieId: string | null = null;
        if (pos.artikel_id) {
          const artikelResult = await client.query(
            'SELECT kategorie_id FROM ausruestung_artikel WHERE id = $1',
            [pos.artikel_id],
          );
          if (artikelResult.rows[0]?.kategorie_id) {
            // artikel has kategorie_id (int FK to ausruestung_kategorien_katalog), but
            // ausruestung.kategorie_id is UUID FK to ausruestung_kategorien — look up by name
            const katNameResult = await client.query(
              'SELECT name FROM ausruestung_kategorien_katalog WHERE id = $1',
              [artikelResult.rows[0].kategorie_id],
            );
            if (katNameResult.rows[0]?.name) {
              const katResult = await client.query(
                'SELECT id FROM ausruestung_kategorien WHERE name = $1 LIMIT 1',
                [katNameResult.rows[0].name],
              );
              kategorieId = katResult.rows[0]?.id ?? null;
            }
          }
        }
        // Fallback: pick first category
        if (!kategorieId) {
          const fallback = await client.query('SELECT id FROM ausruestung_kategorien LIMIT 1');
          kategorieId = fallback.rows[0]?.id ?? null;
        }

        const insertResult = await client.query(
          `INSERT INTO ausruestung (
             id, bezeichnung, kategorie_id, fahrzeug_id, standort, status, ist_wichtig
           ) VALUES (gen_random_uuid(), $1, $2, $3, $4, 'einsatzbereit', false)
           RETURNING id`,
          [pos.bezeichnung, kategorieId, a.fahrzeugId ?? null, a.standort ?? 'Lager'],
        );
        const newId = insertResult.rows[0].id;

        await client.query(
          `UPDATE ausruestung_anfrage_positionen
           SET zuweisung_typ = 'ausruestung', zuweisung_ausruestung_id = $1
           WHERE id = $2`,
          [newId, a.positionId],
        );
      } else if (a.typ === 'persoenlich') {
        const insertResult = await client.query(
          `INSERT INTO persoenliche_ausruestung (
             bezeichnung, kategorie, groesse, user_id, benutzer_name,
             anfrage_id, anfrage_position_id, artikel_id, erstellt_von
           ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
           RETURNING id`,
          [
            pos.bezeichnung,
            a.kategorie ?? null,
            a.groesse ?? null,
            a.userId ?? anfragerId,
            a.benutzerName ?? null,
            anfrageId,
            a.positionId,
            pos.artikel_id ?? null,
            requestingUserId,
          ],
        );
        const newId = insertResult.rows[0].id;

        // Copy eigenschaften: use frontend-provided values if any, otherwise copy from position
        const providedEig = a.eigenschaften && a.eigenschaften.length > 0;
        if (providedEig) {
          for (const e of a.eigenschaften!) {
            await client.query(
              `INSERT INTO persoenliche_ausruestung_eigenschaften (persoenlich_id, eigenschaft_id, name, wert)
               VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`,
              [newId, e.eigenschaft_id ?? null, e.name, e.wert],
            );
          }
        } else {
          const posEigResult = await client.query(
            `SELECT poe.eigenschaft_id, aae.name, poe.wert
             FROM ausruestung_position_eigenschaften poe
             JOIN ausruestung_artikel_eigenschaften aae ON aae.id = poe.eigenschaft_id
             WHERE poe.position_id = $1`,
            [a.positionId],
          );
          for (const row of posEigResult.rows) {
            await client.query(
              `INSERT INTO persoenliche_ausruestung_eigenschaften (persoenlich_id, eigenschaft_id, name, wert)
               VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`,
              [newId, row.eigenschaft_id, row.name, row.wert],
            );
          }
        }

        await client.query(
          `UPDATE ausruestung_anfrage_positionen
           SET zuweisung_typ = 'persoenlich', zuweisung_persoenlich_id = $1
           WHERE id = $2`,
          [newId, a.positionId],
        );
      } else {
        // typ === 'keine'
        await client.query(
          `UPDATE ausruestung_anfrage_positionen SET zuweisung_typ = 'keine' WHERE id = $1`,
          [a.positionId],
        );
      }

      assigned++;
    }

    await client.query('COMMIT');
    return { assigned };
  } catch (error) {
    await client.query('ROLLBACK');
    logger.error('ausruestungsanfrageService.assignDeliveredItems failed', { error, anfrageId });
    throw error;
  } finally {
    client.release();
  }
}

// ---------------------------------------------------------------------------
// Unassigned positions + position artikel update
// ---------------------------------------------------------------------------

async function getUnassignedPositions() {
  const result = await pool.query(`
    SELECT
      p.id, p.bezeichnung, p.menge, p.artikel_id,
      a.id AS anfrage_id,
      a.bezeichnung AS anfrage_bezeichnung,
      a.bestell_nummer,
      a.bestell_jahr,
      COALESCE(a.fuer_benutzer_name,
        NULLIF(TRIM(COALESCE(u.given_name,'') || ' ' || COALESCE(u.family_name,'')), ''),
        u.name) AS fuer_wen
    FROM ausruestung_anfrage_positionen p
    JOIN ausruestung_anfragen a ON a.id = p.anfrage_id
    LEFT JOIN users u ON u.id = a.anfrager_id
    WHERE p.geliefert = true AND (p.zuweisung_typ IS NULL OR p.zuweisung_typ = 'keine')
    ORDER BY a.bestell_jahr DESC NULLS LAST, a.bestell_nummer DESC NULLS LAST, p.bezeichnung
  `);
  return result.rows;
}

async function updatePositionArtikelId(positionId: number, artikelId: number) {
  const result = await pool.query(
    `UPDATE ausruestung_anfrage_positionen SET artikel_id = $1 WHERE id = $2 RETURNING *`,
    [artikelId, positionId],
  );
  return result.rows[0] || null;
}

export default {
  getAllUsers,
  getKategorien,
  createKategorie,
  updateKategorie,
  deleteKategorie,
  getItems,
  getItemById,
  createItem,
  updateItem,
  deleteItem,
  getCategories,
  getArtikelEigenschaften,
  upsertArtikelEigenschaft,
  deleteArtikelEigenschaft,
  getRequests,
  getMyRequests,
  getRequestById,
  updatePositionGeliefert,
  updatePositionZurueckgegeben,
  createRequest,
  updateRequest,
  updateRequestStatus,
  deleteRequest,
  linkToOrder,
  unlinkFromOrder,
  getLinkedOrders,
  createOrdersFromRequest,
  getOverview,
  getWidgetOverview,
  assignDeliveredItems,
  getUnassignedPositions,
  updatePositionArtikelId,
};
