/**
 * Import Flexoptix Internal Demand Data
 * ──────────────────────────────────────
 * Decrypts the AES-256-CBC encrypted XLSX export from Flexoptix,
 * parses the "Artikel" sheet, cross-references the transceivers table,
 * and upserts demand data into flexoptix_internal_demand.
 *
 * ⚠️  SECURITY: Run only on local infrastructure.
 *     The XLSX content NEVER leaves local servers.
 *     Output DB is on Erik (private server, not a cloud).
 *
 * Usage:
 *   XLSX_ENC=~/.credentials/flexoptix-stock.xlsx.enc \
 *   CRED_KEY=<key> \
 *   npx tsx scripts/import-flexoptix-demand.ts
 *
 * Or with keychain (macOS):
 *   CRED_KEY=$(security find-generic-password -a fearghas-cred-key -s fearghas-cred-key -w) \
 *   XLSX_ENC=~/.credentials/flexoptix-stock.xlsx.enc \
 *   npx tsx scripts/import-flexoptix-demand.ts
 */

import { execSync } from "child_process";
import { existsSync, writeFileSync, unlinkSync } from "fs";
import { join } from "path";
import { tmpdir, homedir } from "os";
import { Pool } from "pg";
import xlsx from "xlsx";

// ─── Config ──────────────────────────────────────────────────────────────────

const ENC_PATH = (process.env.XLSX_ENC ?? join(homedir(), ".credentials/flexoptix-stock.xlsx.enc"))
  .replace(/^~/, homedir());
const CRED_KEY = process.env.CRED_KEY ?? "";
const TMP_XLSX = join(tmpdir(), `flexoptix-import-${Date.now()}.xlsx`);

const DB_CONFIG = {
  host: process.env.TIP_DB_HOST ?? "82.165.222.127",
  port: parseInt(process.env.TIP_DB_PORT ?? "5432", 10),
  database: process.env.TIP_DB_NAME ?? "transceiver_db",
  user: process.env.TIP_DB_USER ?? "tip",
  password: process.env.TIP_DB_PASS ?? "",
  ssl: false,
};

// Column headers in the XLSX (exact match)
const COL_SKU         = "Artikel";
const COL_DESC        = "Kurzbeschreibung";
const COL_DEMAND_12M  = "Bedarf pro Monat Letzte 12 Monate (FO Lager)";
const COL_DEMAND_3M   = "Bedarf pro Monat Letzte 3 Monate (FO Lager)";

// ─── Types ────────────────────────────────────────────────────────────────────

interface DemandRow {
  sku: string;
  description: string;
  demand_12m: number;
  demand_3m: number;
  demand_trend_pct: number | null;
  velocity_class: "fast_mover" | "regular" | "slow_mover" | "dead_stock";
  transceiver_id: string | null;
}

// ─── Decrypt ──────────────────────────────────────────────────────────────────

function decryptXlsx(): void {
  if (!existsSync(ENC_PATH)) {
    throw new Error(`Encrypted file not found: ${ENC_PATH}`);
  }
  if (!CRED_KEY) {
    throw new Error("CRED_KEY is empty — set env var or source from Keychain");
  }
  console.log(`🔓 Decrypting ${ENC_PATH} → ${TMP_XLSX}`);
  execSync(
    `openssl enc -d -aes-256-cbc -pbkdf2 -iter 310000 -in "${ENC_PATH}" -out "${TMP_XLSX}" -pass env:CRED_KEY`,
    { env: { ...process.env, CRED_KEY }, stdio: "pipe" }
  );
}

function cleanupTmp(): void {
  if (existsSync(TMP_XLSX)) {
    unlinkSync(TMP_XLSX);
    console.log("🗑️  Temp XLSX removed from disk");
  }
}

// ─── Parse ────────────────────────────────────────────────────────────────────

function classifyVelocity(demand12m: number): DemandRow["velocity_class"] {
  if (demand12m >= 100) return "fast_mover";
  if (demand12m >= 10)  return "regular";
  if (demand12m > 0)    return "slow_mover";
  return "dead_stock";
}

function parseXlsx(): DemandRow[] {
  console.log("📊 Parsing XLSX…");
  const wb = xlsx.readFile(TMP_XLSX);
  const ws = wb.Sheets["Artikel"];
  if (!ws) throw new Error('Sheet "Artikel" not found in XLSX');

  const raw = xlsx.utils.sheet_to_json<Record<string, unknown>>(ws, { defval: 0 });
  console.log(`   ${raw.length} rows read`);

  const rows: DemandRow[] = [];

  for (const r of raw) {
    const sku = String(r[COL_SKU] ?? "").trim();
    if (!sku) continue;

    const demand_12m = Math.max(0, parseFloat(String(r[COL_DEMAND_12M] ?? "0")) || 0);
    const demand_3m  = Math.max(0, parseFloat(String(r[COL_DEMAND_3M]  ?? "0")) || 0);
    const description = String(r[COL_DESC] ?? "").trim();

    const demand_trend_pct =
      demand_12m > 0
        ? Math.round(((demand_3m - demand_12m) / demand_12m) * 10000) / 100
        : null;

    rows.push({
      sku,
      description,
      demand_12m,
      demand_3m,
      demand_trend_pct,
      velocity_class: classifyVelocity(demand_12m),
      transceiver_id: null, // resolved in cross-reference step
    });
  }

  const withDemand = rows.filter(r => r.demand_12m > 0 || r.demand_3m > 0);
  console.log(`   ${rows.length} total articles | ${withDemand.length} with demand > 0`);
  return rows;
}

// ─── Cross-reference ──────────────────────────────────────────────────────────

async function crossReference(rows: DemandRow[], pool: Pool): Promise<void> {
  console.log("🔗 Cross-referencing with transceivers table…");

  // Build local lookup: part_number (lower) → uuid
  const { rows: tRows } = await pool.query<{ id: string; part_number: string }>(
    `SELECT id, LOWER(part_number) AS part_number FROM transceivers`
  );
  const lookup = new Map<string, string>(tRows.map(t => [t.part_number, t.id]));

  let matched = 0;
  for (const row of rows) {
    const uuid = lookup.get(row.sku.toLowerCase());
    if (uuid) {
      row.transceiver_id = uuid;
      matched++;
    }
  }

  console.log(`   ${matched}/${rows.length} SKUs matched to transceivers`);
}

// ─── Upsert ───────────────────────────────────────────────────────────────────

async function upsertRows(rows: DemandRow[], pool: Pool): Promise<void> {
  console.log(`💾 Upserting ${rows.length} rows into flexoptix_internal_demand…`);

  const BATCH = 500;
  let inserted = 0;
  let updated = 0;

  for (let i = 0; i < rows.length; i += BATCH) {
    const batch = rows.slice(i, i + BATCH);
    const client = await pool.connect();
    try {
      await client.query("BEGIN");
      for (const row of batch) {
        const r = await client.query(
          `INSERT INTO flexoptix_internal_demand
             (sku, description, transceiver_id, demand_12m, demand_3m, demand_trend_pct, velocity_class, is_internal)
           VALUES ($1,$2,$3,$4,$5,$6,$7,TRUE)
           ON CONFLICT (sku) DO UPDATE SET
             description      = EXCLUDED.description,
             transceiver_id   = EXCLUDED.transceiver_id,
             demand_12m       = EXCLUDED.demand_12m,
             demand_3m        = EXCLUDED.demand_3m,
             demand_trend_pct = EXCLUDED.demand_trend_pct,
             velocity_class   = EXCLUDED.velocity_class,
             imported_at      = NOW()
           RETURNING (xmax = 0) AS is_insert`,
          [row.sku, row.description, row.transceiver_id, row.demand_12m, row.demand_3m, row.demand_trend_pct, row.velocity_class]
        );
        if (r.rows[0]?.is_insert) inserted++;
        else updated++;
      }
      await client.query("COMMIT");
    } catch (err) {
      await client.query("ROLLBACK");
      throw err;
    } finally {
      client.release();
    }
    process.stdout.write(`\r   ${Math.min(i + BATCH, rows.length)}/${rows.length} rows processed…`);
  }

  console.log(`\n   ✅ ${inserted} inserted | ${updated} updated`);
}

// ─── Stats ────────────────────────────────────────────────────────────────────

async function printStats(pool: Pool): Promise<void> {
  const { rows } = await pool.query(`
    SELECT
      COUNT(*)                                                     AS total,
      COUNT(*) FILTER (WHERE demand_12m > 0)                      AS with_demand,
      COUNT(*) FILTER (WHERE transceiver_id IS NOT NULL)          AS matched_transceivers,
      COUNT(*) FILTER (WHERE velocity_class = 'fast_mover')       AS fast_movers,
      COUNT(*) FILTER (WHERE velocity_class = 'regular')           AS regular,
      COUNT(*) FILTER (WHERE velocity_class = 'slow_mover')        AS slow_movers,
      COUNT(*) FILTER (WHERE velocity_class = 'dead_stock')        AS dead_stock,
      ROUND(SUM(demand_12m))                                       AS total_monthly_demand_12m,
      ROUND(SUM(demand_3m))                                        AS total_monthly_demand_3m,
      MAX(demand_12m)                                              AS peak_demand_12m
    FROM flexoptix_internal_demand
  `);

  const s = rows[0]!;
  console.log("\n📈 Import Summary:");
  console.log(`   Total SKUs:           ${s.total}`);
  console.log(`   With demand > 0:      ${s.with_demand}`);
  console.log(`   Matched transceivers: ${s.matched_transceivers}`);
  console.log(`   Fast movers (≥100):   ${s.fast_movers}`);
  console.log(`   Regular (10–99):      ${s.regular}`);
  console.log(`   Slow movers (1–9):    ${s.slow_movers}`);
  console.log(`   Dead stock (0):       ${s.dead_stock}`);
  console.log(`   Total demand/month (12m): ${s.total_monthly_demand_12m} units`);
  console.log(`   Total demand/month (3m):  ${s.total_monthly_demand_3m} units`);
  console.log(`   Peak demand (12m):    ${s.peak_demand_12m} units/month`);
}

// ─── Main ─────────────────────────────────────────────────────────────────────

async function main(): Promise<void> {
  console.log("\n🔐 Flexoptix Internal Demand Importer");
  console.log("   Data stays 100% on local infrastructure.\n");

  if (!DB_CONFIG.password) {
    // Try to read from ~/.pgpass or env
    const pgpass = process.env.PGPASSWORD;
    if (pgpass) DB_CONFIG.password = pgpass;
    else {
      console.error("❌ TIP_DB_PASS / PGPASSWORD not set");
      process.exit(1);
    }
  }

  const pool = new Pool(DB_CONFIG);

  try {
    // 1. Decrypt
    decryptXlsx();

    // 2. Parse
    const rows = parseXlsx();

    // 3. Cross-reference
    await crossReference(rows, pool);

    // 4. Upsert
    await upsertRows(rows, pool);

    // 5. Stats
    await printStats(pool);

    console.log("\n✅ Import complete — XLSX decrypted in-memory only, temp file wiped.\n");
  } finally {
    cleanupTmp();
    await pool.end();
  }
}

main().catch(err => {
  console.error("❌ Import failed:", err);
  cleanupTmp();
  process.exit(1);
});
