import { Pool, PoolClient } from 'pg';
import {
  FdiskMember,
  FdiskAusbildung,
  FdiskBefoerderung,
  FdiskUntersuchung,
  FdiskFahrgenehmigung,
} from './types';

function log(msg: string) {
  console.log(`[db] ${new Date().toISOString()} ${msg}`);
}

/**
 * Map FDISK Dienstgrad (abbreviation or full name) to the DB enum value.
 * Returns null if no match found — the field will be left unchanged.
 */
function mapDienstgrad(raw: string): string | null {
  const abbrevMap: Record<string, string> = {
    'jfm':  'Jugendfeuerwehrmann',
    'pfm':  'Probefeuerwehrmann',
    'fm':   'Feuerwehrmann',
    'ofm':  'Oberfeuerwehrmann',
    'hfm':  'Hauptfeuerwehrmann',
    'lm':   'Löschmeister',
    'olm':  'Oberlöschmeister',
    'hlm':  'Hauptlöschmeister',
    'bm':   'Brandmeister',
    'obm':  'Oberbrandmeister',
    'hbm':  'Hauptbrandmeister',
    'bi':   'Brandinspektor',
    'obi':  'Oberbrandinspektor',
    'vm':   'Verwaltungsmeister',
    'ovm':  'Oberverwaltungsmeister',
    'hvm':  'Hauptverwaltungsmeister',
    'v':    'Verwalter',
    'sb':   'Sachbearbeiter',
    'asb':  'Abschnittssachbearbeiter',
  };

  const normalized = raw.trim().toLowerCase().replace(/\*/g, '');

  // Direct abbreviation match
  if (abbrevMap[normalized]) return abbrevMap[normalized];

  // Ehrendienstgrad: starts with 'e', rest maps to a known abbreviation
  // e.g. EOLM → Ehren-Oberlöschmeister, EVM → Ehren-Verwaltungsmeister
  if (normalized.startsWith('e') && normalized.length > 1) {
    const base = abbrevMap[normalized.slice(1)];
    if (base) return `Ehren-${base}`;
  }

  // Full name pass-through (case-insensitive)
  const allValues = Object.values(abbrevMap);
  const match = allValues.find(v => v.toLowerCase() === normalized);
  if (match) return match;
  // Also match Ehren- full names
  const ehrenMatch = allValues.find(v => `ehren-${v.toLowerCase()}` === normalized);
  if (ehrenMatch) return `Ehren-${ehrenMatch}`;

  return null;
}

/**
 * Valid Austrian/EU driving license class patterns.
 * Filters out non-class data that the scraper may pick up from FDISK form fields.
 */
const VALID_LICENSE_CLASSES = new Set([
  'A', 'A1', 'A2', 'AM',
  'B', 'B1', 'BE',
  'C', 'C1', 'CE', 'C1E',
  'D', 'D1', 'DE', 'D1E',
  'F', 'G', 'L', 'T',
]);

function isValidLicenseClass(klasse: string): boolean {
  const normalized = klasse.trim().toUpperCase();
  return VALID_LICENSE_CLASSES.has(normalized);
}

export async function syncToDatabase(
  pool: Pool,
  members: FdiskMember[],
  ausbildungen: FdiskAusbildung[],
  befoerderungen: FdiskBefoerderung[],
  untersuchungen: FdiskUntersuchung[],
  fahrgenehmigungen: FdiskFahrgenehmigung[],
  force = false
): Promise<void> {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    let updated = 0;
    let unchanged = 0;
    let forced = 0;
    let created = 0;
    let skipped = 0;

    for (const member of members) {
      // Find the matching mitglieder_profile by fdisk_standesbuch_nr first,
      // then fall back to matching by name (given_name + family_name)
      const profileResult = await client.query<{ user_id: string }>(
        `SELECT mp.user_id
         FROM mitglieder_profile mp
         JOIN users u ON u.id = mp.user_id
         WHERE mp.fdisk_standesbuch_nr = $1`,
        [member.standesbuchNr]
      );

      let userId: string | null = null;

      if (profileResult.rows.length > 0) {
        userId = profileResult.rows[0].user_id;
      } else {
        // Fallback: match by name (case-insensitive), only logged-in users
        const nameResult = await client.query<{ id: string }>(
          `SELECT u.id
           FROM users u
           JOIN mitglieder_profile mp ON mp.user_id = u.id
           WHERE LOWER(u.given_name) = LOWER($1)
             AND LOWER(u.family_name) = LOWER($2)`,
          [member.vorname, member.zuname]
        );

        if (nameResult.rows.length > 1) {
          log(`WARN: skipping ${member.vorname} ${member.zuname} (Standesbuch-Nr ${member.standesbuchNr}) — duplicate name match (${nameResult.rows.length} users)`);
        } else if (nameResult.rows.length === 1) {
          userId = nameResult.rows[0].id;
          // Store the Standesbuch-Nr now that we found a match
          await client.query(
            `UPDATE mitglieder_profile SET fdisk_standesbuch_nr = $1 WHERE user_id = $2`,
            [member.standesbuchNr, userId]
          );
          log(`Linked ${member.vorname} ${member.zuname} → Standesbuch-Nr ${member.standesbuchNr}`);
        }
      }

      if (!userId) {
        // No matching user found — create a new dashboard user pre-seeded from FDISK
        const insertResult = await client.query<{ id: string }>(
          `INSERT INTO users (authentik_sub, email, name, given_name, family_name, is_active, sync_source)
           VALUES ($1, $2, $3, $4, $5, true, 'fdisk')
           ON CONFLICT (authentik_sub) DO NOTHING
           RETURNING id`,
          [
            `fdisk:${member.standesbuchNr}`,
            `fdisk_sync_${member.standesbuchNr}@intern.noreply`,
            `${member.vorname} ${member.zuname}`,
            member.vorname,
            member.zuname,
          ]
        );

        if (insertResult.rows.length > 0) {
          userId = insertResult.rows[0].id;
        } else {
          // ON CONFLICT hit — user already existed (idempotent re-run); fetch it
          const existingResult = await client.query<{ id: string }>(
            `SELECT id FROM users WHERE authentik_sub = $1`,
            [`fdisk:${member.standesbuchNr}`]
          );
          userId = existingResult.rows[0]?.id ?? null;
        }

        if (userId) {
          await client.query(
            `INSERT INTO mitglieder_profile (user_id) VALUES ($1) ON CONFLICT (user_id) DO NOTHING`,
            [userId]
          );
          log(`Created ${member.vorname} ${member.zuname} (${member.standesbuchNr}): new FDISK-only user`);
          created++;
        } else {
          log(`WARN: could not create user for ${member.vorname} ${member.zuname} (${member.standesbuchNr})`);
          skipped++;
          continue;
        }
      }

      // Fetch current values to detect what actually changed
      const currentResult = await client.query<{
        status: string;
        dienstgrad: string | null;
        eintrittsdatum: string | null;
        austrittsdatum: string | null;
        geburtsdatum: string | null;
      }>(
        `SELECT status, dienstgrad,
                to_char(eintrittsdatum, 'YYYY-MM-DD') AS eintrittsdatum,
                to_char(austrittsdatum, 'YYYY-MM-DD') AS austrittsdatum,
                to_char(geburtsdatum,   'YYYY-MM-DD') AS geburtsdatum
         FROM mitglieder_profile WHERE user_id = $1`,
        [userId]
      );
      const cur = currentResult.rows[0];

      // Update mitglieder_profile with FDISK data (core + extended profile fields)
      const dienstgrad = mapDienstgrad(member.dienstgrad);

      await client.query(
        `UPDATE mitglieder_profile SET
           fdisk_standesbuch_nr  = $1,
           status                = $2,
           eintrittsdatum        = COALESCE($3::date, eintrittsdatum),
           austrittsdatum        = $4::date,
           geburtsdatum          = COALESCE($5::date, geburtsdatum),
           geburtsort            = COALESCE($6, geburtsort),
           geschlecht            = COALESCE($7, geschlecht),
           beruf                 = COALESCE($8, beruf),
           wohnort               = COALESCE($9, wohnort),
           plz                   = COALESCE($10, plz),
           ${dienstgrad ? 'dienstgrad = $11,' : ''}
           updated_at            = NOW()
         WHERE user_id = ${dienstgrad ? '$12' : '$11'}`,
        dienstgrad
          ? [member.standesbuchNr, member.status, member.eintrittsdatum, member.abmeldedatum, member.geburtsdatum,
             member.geburtsort, member.geschlecht, member.beruf, member.wohnort, member.plz,
             dienstgrad, userId]
          : [member.standesbuchNr, member.status, member.eintrittsdatum, member.abmeldedatum, member.geburtsdatum,
             member.geburtsort, member.geschlecht, member.beruf, member.wohnort, member.plz,
             userId]
      );

      // Detect and log what changed
      const changes: string[] = [];
      if (cur) {
        if (cur.status !== member.status)
          changes.push(`Status ${cur.status}→${member.status}`);
        if (dienstgrad && cur.dienstgrad !== dienstgrad)
          changes.push(`Dienstgrad ${cur.dienstgrad ?? '—'}→${dienstgrad}`);
        if (member.eintrittsdatum && cur.eintrittsdatum !== member.eintrittsdatum)
          changes.push(`Eintrittsdatum ${cur.eintrittsdatum ?? '—'}→${member.eintrittsdatum}`);
        if (cur.austrittsdatum !== (member.abmeldedatum ?? null))
          changes.push(`Austrittsdatum ${cur.austrittsdatum ?? '—'}→${member.abmeldedatum ?? '—'}`);
        if (member.geburtsdatum && cur.geburtsdatum !== member.geburtsdatum)
          changes.push(`Geburtsdatum ${cur.geburtsdatum ?? '—'}→${member.geburtsdatum}`);
      }

      if (changes.length > 0) {
        log(`Updated ${member.vorname} ${member.zuname} (${member.standesbuchNr}): ${changes.join(', ')}`);
        updated++;
      } else if (force) {
        // Force mode: explicitly update timestamp and log even unchanged rows
        await client.query(
          `UPDATE mitglieder_profile SET updated_at = NOW() WHERE user_id = $1`,
          [userId]
        );
        log(`Forced ${member.vorname} ${member.zuname} (${member.standesbuchNr}): ${member.status}, ${dienstgrad ?? member.dienstgrad}, Eintritt ${member.eintrittsdatum ?? '—'}`);
        forced++;
      } else {
        log(`OK ${member.vorname} ${member.zuname} (${member.standesbuchNr}): ${member.status}, ${dienstgrad ?? member.dienstgrad}, Eintritt ${member.eintrittsdatum ?? '—'}`);
        unchanged++;
      }
    }

    log(`Members: ${updated} changed, ${unchanged} unchanged, ${forced} forced, ${created} created, ${skipped} skipped`);

    // Upsert Ausbildungen
    let ausbildungNew = 0;
    let ausbildungUpdated = 0;
    let ausbildungSkipped = 0;

    for (const ausb of ausbildungen) {
      const result = await client.query<{ user_id: string }>(
        `SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
        [ausb.standesbuchNr]
      );

      if (result.rows.length === 0) {
        ausbildungSkipped++;
        continue;
      }

      const userId = result.rows[0].user_id;

      const upsertResult = await client.query<{ was_inserted: boolean }>(
        `INSERT INTO ausbildung (user_id, kursname, kursnummer, kurs_kurzbezeichnung, erfolgscode, kurs_datum, ablaufdatum, ort, bemerkung, fdisk_sync_key)
         VALUES ($1, $2, $3, $4, $5, $6::date, $7::date, $8, $9, $10)
         ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
           kursname             = EXCLUDED.kursname,
           kursnummer           = EXCLUDED.kursnummer,
           kurs_kurzbezeichnung = EXCLUDED.kurs_kurzbezeichnung,
           erfolgscode          = EXCLUDED.erfolgscode,
           kurs_datum           = EXCLUDED.kurs_datum,
           ablaufdatum          = EXCLUDED.ablaufdatum,
           ort                  = EXCLUDED.ort,
           bemerkung            = EXCLUDED.bemerkung,
           updated_at           = NOW()
         RETURNING (xmax = 0) AS was_inserted`,
        [userId, ausb.kursname, ausb.kursnummer, ausb.kurzbezeichnung, ausb.erfolgscode, ausb.kursDatum, ausb.ablaufdatum, ausb.ort, ausb.bemerkung, ausb.syncKey]
      );

      if (upsertResult.rows[0]?.was_inserted) {
        log(`New Ausbildung: ${ausb.standesbuchNr} — ${ausb.kursname}${ausb.kursDatum ? ` (${ausb.kursDatum})` : ''}`);
        ausbildungNew++;
      } else {
        ausbildungUpdated++;
      }
    }

    log(`Ausbildungen: ${ausbildungNew} neu, ${ausbildungUpdated} unverändert, ${ausbildungSkipped} übersprungen`);

    // Upsert Beförderungen
    const befoerderungStats = await syncBefoerderungen(client, befoerderungen);
    log(`Beförderungen: ${befoerderungStats.neu} neu, ${befoerderungStats.updated} unverändert, ${befoerderungStats.skipped} übersprungen`);

    // Upsert Untersuchungen
    const untersuchungStats = await syncUntersuchungen(client, untersuchungen);
    log(`Untersuchungen: ${untersuchungStats.neu} neu, ${untersuchungStats.updated} unverändert, ${untersuchungStats.skipped} übersprungen`);

    // Upsert Fahrgenehmigungen
    const fahrgenStats = await syncFahrgenehmigungen(client, fahrgenehmigungen);
    log(`Fahrgenehmigungen: ${fahrgenStats.neu} neu, ${fahrgenStats.updated} unverändert, ${fahrgenStats.skipped} übersprungen`);

    await client.query('COMMIT');
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

async function syncBefoerderungen(
  client: PoolClient,
  befoerderungen: FdiskBefoerderung[]
): Promise<{ neu: number; updated: number; skipped: number }> {
  let neu = 0, updated = 0, skipped = 0;

  for (const b of befoerderungen) {
    const result = await client.query<{ user_id: string }>(
      `SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
      [b.standesbuchNr]
    );

    if (result.rows.length === 0) { skipped++; continue; }
    const userId = result.rows[0].user_id;

    const upsertResult = await client.query<{ was_inserted: boolean }>(
      `INSERT INTO befoerderungen (user_id, datum, dienstgrad, fdisk_sync_key)
       VALUES ($1, $2::date, $3, $4)
       ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
         datum      = EXCLUDED.datum,
         dienstgrad = EXCLUDED.dienstgrad,
         updated_at = NOW()
       RETURNING (xmax = 0) AS was_inserted`,
      [userId, b.datum, b.dienstgrad, b.syncKey]
    );

    if (upsertResult.rows[0]?.was_inserted) {
      log(`New Beförderung: ${b.standesbuchNr} — ${b.dienstgrad}${b.datum ? ` (${b.datum})` : ''}`);
      neu++;
    } else {
      updated++;
    }
  }

  return { neu, updated, skipped };
}

async function syncUntersuchungen(
  client: PoolClient,
  untersuchungen: FdiskUntersuchung[]
): Promise<{ neu: number; updated: number; skipped: number }> {
  let neu = 0, updated = 0, skipped = 0;

  for (const u of untersuchungen) {
    const result = await client.query<{ user_id: string }>(
      `SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
      [u.standesbuchNr]
    );

    if (result.rows.length === 0) { skipped++; continue; }
    const userId = result.rows[0].user_id;

    const upsertResult = await client.query<{ was_inserted: boolean }>(
      `INSERT INTO untersuchungen (user_id, datum, anmerkungen, art, ergebnis, fdisk_sync_key)
       VALUES ($1, $2::date, $3, $4, $5, $6)
       ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
         datum       = EXCLUDED.datum,
         anmerkungen = EXCLUDED.anmerkungen,
         art         = EXCLUDED.art,
         ergebnis    = EXCLUDED.ergebnis,
         updated_at  = NOW()
       RETURNING (xmax = 0) AS was_inserted`,
      [userId, u.datum, u.anmerkungen, u.art, u.ergebnis, u.syncKey]
    );

    if (upsertResult.rows[0]?.was_inserted) {
      log(`New Untersuchung: ${u.standesbuchNr} — [${u.art}] ${u.ergebnis ?? '—'}${u.datum ? ` (${u.datum})` : ''} | ${u.anmerkungen ?? ''}`);
      neu++;
    } else {
      updated++;
    }
  }

  return { neu, updated, skipped };
}

async function syncFahrgenehmigungen(
  client: PoolClient,
  fahrgenehmigungen: FdiskFahrgenehmigung[]
): Promise<{ neu: number; updated: number; skipped: number }> {
  let neu = 0, updated = 0, skipped = 0;

  // One-time cleanup: remove wrongly-stored records from broken parsing
  // Includes klasse='Ausstellungsdatum' and any klasse that looks like a date (DD.MM.YYYY)
  const cleaned = await client.query(
    `DELETE FROM fahrgenehmigungen WHERE klasse = 'Ausstellungsdatum' OR klasse ~ '^\\d{2}\\.\\d{2}\\.\\d{4}$'`
  );
  if (cleaned.rowCount && cleaned.rowCount > 0) {
    log(`Cleaned up ${cleaned.rowCount} invalid Fahrgenehmigung records (wrong klasse values)`);
  }

  for (const f of fahrgenehmigungen) {
    // J2: Filter out non-class data that the scraper may pick up
    if (!f.klasse || !isValidLicenseClass(f.klasse)) {
      log(`Skipping Fahrgenehmigung: invalid klasse "${f.klasse}" for StNr ${f.standesbuchNr}`);
      skipped++;
      continue;
    }

    const result = await client.query<{ user_id: string }>(
      `SELECT user_id FROM mitglieder_profile WHERE fdisk_standesbuch_nr = $1`,
      [f.standesbuchNr]
    );

    if (result.rows.length === 0) { skipped++; continue; }
    const userId = result.rows[0].user_id;

    const upsertResult = await client.query<{ was_inserted: boolean }>(
      `INSERT INTO fahrgenehmigungen (user_id, ausstellungsdatum, gueltig_bis, behoerde, nummer, klasse, fdisk_sync_key)
       VALUES ($1, $2::date, $3::date, $4, $5, $6, $7)
       ON CONFLICT (user_id, fdisk_sync_key) DO UPDATE SET
         ausstellungsdatum = EXCLUDED.ausstellungsdatum,
         gueltig_bis       = EXCLUDED.gueltig_bis,
         behoerde          = EXCLUDED.behoerde,
         nummer            = EXCLUDED.nummer,
         klasse            = EXCLUDED.klasse,
         updated_at        = NOW()
       RETURNING (xmax = 0) AS was_inserted`,
      [userId, f.ausstellungsdatum, f.gueltigBis, f.behoerde, f.nummer, f.klasse, f.syncKey]
    );

    if (upsertResult.rows[0]?.was_inserted) {
      log(`New Fahrgenehmigung: ${f.standesbuchNr} — [${f.klasse}]${f.ausstellungsdatum ? ` (${f.ausstellungsdatum})` : ''}`);
      neu++;
    } else {
      updated++;
    }
  }

  return { neu, updated, skipped };
}
