/**
 * Scraper pentru ANAF — Lista contribuabililor cu obligații restante (datornici).
 * Source primară: https://www.anaf.ro/restante/  (publicare trimestrială, Ord. 558/2016)
 *
 * STARE LA 2026-05-09 (citește înainte de orice modificare):
 *
 *  ANAF expune lista live la /restante/index.xhtml — aplicație JSF/PrimeFaces
 *  cu CAPTCHA pe submit. Am încercat (a) JSF AJAX submit fără CAPTCHA →
 *  rowCount=0 silent; (b) sesiune cu cookie + ViewState valid → CAPTCHA mereu
 *  cerută. Bulk scrape automat NU e fezabil fără captcha-solver extern
 *  (anti-captcha.com / 2captcha) — vezi PROMPTS.md §0b notes.
 *
 *  data.gov.ro publică UN SINGUR snapshot oficial: Q1-2016 (mari/mijlocii/
 *  micijuridice CSV). Total ~140K rânduri. E baseline-ul nostru istoric.
 *
 *  ListaFirme.eu agregă comercial ANAF datornici dincolo de paywall — sub-
 *  scripție necesară pentru API.
 *
 * STRATEGIA INGEST IMPLEMENTATĂ:
 *
 *  Modul A (default, FUNCȚIONAL): import data.gov.ro CSV snapshot Q1-2016.
 *    - Descarcă cele 3 CSV-uri (mari, mijlocii, micijuridice — tab-separated)
 *    - Parsare headers EXACT match: 14 coloane (cif + nume + 4 bugete × 3 sume)
 *    - Calculează debt_total / debt_principal / debt_penalty agregate
 *    - INSERT INTO anaf.datornici ON CONFLICT (cui, publication_date) DO UPDATE
 *
 *  Modul B (TODO, planificat): live scrape din anaf.ro/restante/ via captcha
 *    solver. Stub gol — vezi scrapeAnafLive() comentat.
 *
 * Run:
 *   npx tsx src/scrape-anaf-datornici.ts                      # default Modul A
 *   npx tsx src/scrape-anaf-datornici.ts --source=datagov2016 # explicit
 *   npx tsx src/scrape-anaf-datornici.ts --dry-run            # parsare fără DB writes
 */
import { Pool } from 'pg';

const USER_AGENT = 'gov-agreg/1.0 vreaudigital.ro (contact: m.tarau@beletage.ro)';

const args = process.argv.slice(2);
const argSource = args.find((a) => a.startsWith('--source='))?.split('=')[1] || 'datagov2016';
const argDryRun = args.includes('--dry-run');

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 4,
});

// ── Sursa de date: data.gov.ro Q1-2016 ─────────────────────────────────────
// Dataset: https://data.gov.ro/dataset/datoriile-catre-bugetul-de-stat
// 3 CSV-uri (TSV de fapt, separator = TAB, encoding UTF-8, quotes = " )
const DATAGOV_2016_SOURCES = [
  {
    category: 'mari',
    url: 'https://data.gov.ro/dataset/238727a2-ffd4-4aa9-9655-814a86721c97/resource/125297c1-78df-4c82-a437-0f6f52aeda86/download/mari.csv',
  },
  {
    category: 'mijlocii',
    url: 'https://data.gov.ro/dataset/238727a2-ffd4-4aa9-9655-814a86721c97/resource/bb24c535-83d8-469b-bf5c-b345d8619ad0/download/mijlocii.csv',
  },
  {
    category: 'mici',
    url: 'https://data.gov.ro/dataset/238727a2-ffd4-4aa9-9655-814a86721c97/resource/1a23968b-de59-4e42-88e0-d78155c6706a/download/micijuridice.csv',
  },
];

const DATAGOV_2016_PUB_DATE = '2016-03-31'; // Sfârșitul T1 2016 — așa apare în PDF-urile sursa
const DATAGOV_2016_LABEL = 'T1 2016';

// ── Tip rând (post-parsare) ────────────────────────────────────────────────
interface DatornicRow {
  cui: string;
  name: string;
  judet: string | null;
  category: string;
  budgetState: { principal: number; penalty: number; contested: number };
  budgetSocial: { principal: number; penalty: number; contested: number };
  budgetUnemployment: { principal: number; penalty: number; contested: number };
  budgetHealth: { principal: number; penalty: number; contested: number };
}

// ── Parser TSV cu quote handling ───────────────────────────────────────────
// Format observat: '"199150"\t"FARMEC SA"\t"667619"\t"0"...\t"0"\n'
// Toate field-urile sunt quoted, separator = TAB (chr 9). Numere fără zecimale,
// fără separator de mii. UTF-8 plain.
function parseTsvLine(line: string): string[] {
  const fields: string[] = [];
  let inQuote = false;
  let cur = '';
  for (let i = 0; i < line.length; i++) {
    const ch = line[i];
    if (ch === '"') {
      // ANAF nu folosește escape "" — quote=delimitator strict
      inQuote = !inQuote;
      continue;
    }
    if (ch === '\t' && !inQuote) {
      fields.push(cur);
      cur = '';
      continue;
    }
    cur += ch;
  }
  fields.push(cur);
  return fields;
}

function toNum(s: string): number {
  if (!s || s === '') return 0;
  const n = Number(s.replace(',', '.').trim());
  return Number.isFinite(n) ? n : 0;
}

async function fetchAndParseCsv(
  url: string,
  category: string
): Promise<DatornicRow[]> {
  console.log(`[fetch] ${category} from ${url}`);
  const res = await fetch(url, {
    headers: { 'User-Agent': USER_AGENT, Accept: 'text/csv,*/*' },
    signal: AbortSignal.timeout(60000),
  });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} fetching ${url}`);
  }
  const text = await res.text();
  const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
  console.log(`[parse] ${category}: ${lines.length} lines (incl header)`);

  // Header expected:
  // cod identificare fiscala | denumire |
  // 4 × {principal, accesorii, contestate} pentru:
  //   bugetul de stat
  //   bugetul asigurarilor sociale
  //   bugetul asigurarilor de somaj
  //   bugetul de sanatate
  const header = parseTsvLine(lines[0]);
  if (header.length !== 14) {
    throw new Error(`Unexpected header column count for ${category}: ${header.length} (expected 14)`);
  }

  const rows: DatornicRow[] = [];
  for (let i = 1; i < lines.length; i++) {
    const f = parseTsvLine(lines[i]);
    if (f.length !== 14) {
      console.warn(`[warn] line ${i + 1} in ${category} has ${f.length} fields, skipping`);
      continue;
    }
    const cui = f[0].trim();
    if (!cui) continue;
    rows.push({
      cui,
      name: f[1].trim(),
      judet: null, // CSV-urile data.gov.ro nu includ județ
      category,
      budgetState: { principal: toNum(f[2]), penalty: toNum(f[3]), contested: toNum(f[4]) },
      budgetSocial: { principal: toNum(f[5]), penalty: toNum(f[6]), contested: toNum(f[7]) },
      budgetUnemployment: { principal: toNum(f[8]), penalty: toNum(f[9]), contested: toNum(f[10]) },
      budgetHealth: { principal: toNum(f[11]), penalty: toNum(f[12]), contested: toNum(f[13]) },
    });
  }
  console.log(`[parse] ${category}: ${rows.length} valid rows`);
  return rows;
}

async function upsertBatch(
  rows: DatornicRow[],
  pubDate: string,
  periodLabel: string,
  sourceUrl: string
): Promise<{ inserted: number; updated: number; skipped: number }> {
  let inserted = 0;
  let updated = 0;
  let skipped = 0;

  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    for (const r of rows) {
      const totalPrincipal =
        r.budgetState.principal +
        r.budgetSocial.principal +
        r.budgetUnemployment.principal +
        r.budgetHealth.principal;
      const totalPenalty =
        r.budgetState.penalty +
        r.budgetSocial.penalty +
        r.budgetUnemployment.penalty +
        r.budgetHealth.penalty;
      const totalContested =
        r.budgetState.contested +
        r.budgetSocial.contested +
        r.budgetUnemployment.contested +
        r.budgetHealth.contested;
      const total = totalPrincipal + totalPenalty;

      const result = await client.query<{ inserted: boolean }>(
        `
        INSERT INTO anaf.datornici (
          cui, name, judet, publication_date, period_label, debtor_category,
          debt_total, debt_principal, debt_penalty, debt_contested,
          budget_state_principal, budget_state_penalty, budget_state_contested,
          budget_social_principal, budget_social_penalty, budget_social_contested,
          budget_unemployment_principal, budget_unemployment_penalty, budget_unemployment_contested,
          budget_health_principal, budget_health_penalty, budget_health_contested,
          source_url
        ) VALUES (
          $1, $2, $3, $4, $5, $6,
          $7, $8, $9, $10,
          $11, $12, $13,
          $14, $15, $16,
          $17, $18, $19,
          $20, $21, $22,
          $23
        )
        ON CONFLICT (cui, publication_date) DO UPDATE SET
          name = EXCLUDED.name,
          judet = COALESCE(EXCLUDED.judet, anaf.datornici.judet),
          debtor_category = EXCLUDED.debtor_category,
          debt_total = EXCLUDED.debt_total,
          debt_principal = EXCLUDED.debt_principal,
          debt_penalty = EXCLUDED.debt_penalty,
          debt_contested = EXCLUDED.debt_contested,
          budget_state_principal = EXCLUDED.budget_state_principal,
          budget_state_penalty = EXCLUDED.budget_state_penalty,
          budget_state_contested = EXCLUDED.budget_state_contested,
          budget_social_principal = EXCLUDED.budget_social_principal,
          budget_social_penalty = EXCLUDED.budget_social_penalty,
          budget_social_contested = EXCLUDED.budget_social_contested,
          budget_unemployment_principal = EXCLUDED.budget_unemployment_principal,
          budget_unemployment_penalty = EXCLUDED.budget_unemployment_penalty,
          budget_unemployment_contested = EXCLUDED.budget_unemployment_contested,
          budget_health_principal = EXCLUDED.budget_health_principal,
          budget_health_penalty = EXCLUDED.budget_health_penalty,
          budget_health_contested = EXCLUDED.budget_health_contested,
          source_url = EXCLUDED.source_url,
          fetched_at = now()
        RETURNING (xmax = 0) AS inserted
        `,
        [
          r.cui, r.name, r.judet, pubDate, periodLabel, r.category,
          total, totalPrincipal, totalPenalty, totalContested,
          r.budgetState.principal, r.budgetState.penalty, r.budgetState.contested,
          r.budgetSocial.principal, r.budgetSocial.penalty, r.budgetSocial.contested,
          r.budgetUnemployment.principal, r.budgetUnemployment.penalty, r.budgetUnemployment.contested,
          r.budgetHealth.principal, r.budgetHealth.penalty, r.budgetHealth.contested,
          sourceUrl,
        ]
      );
      if (result.rows[0]?.inserted) inserted++; else updated++;
    }
    await client.query('COMMIT');
  } catch (e) {
    await client.query('ROLLBACK');
    throw e;
  } finally {
    client.release();
  }
  return { inserted, updated, skipped };
}

// ── Main: import data.gov.ro Q1-2016 snapshot ──────────────────────────────
async function importDataGov2016(): Promise<void> {
  console.log(`[mode] datagov2016 (snapshot ${DATAGOV_2016_LABEL}, pub_date=${DATAGOV_2016_PUB_DATE})`);

  let totalInserted = 0;
  let totalUpdated = 0;
  let totalParsed = 0;

  for (const src of DATAGOV_2016_SOURCES) {
    const rows = await fetchAndParseCsv(src.url, src.category);
    totalParsed += rows.length;

    if (argDryRun) {
      console.log(`[dry-run] ${src.category}: ${rows.length} rows would be upserted`);
      // Sample preview
      if (rows.length > 0) {
        const r = rows[0];
        console.log(`[dry-run] sample: cui=${r.cui} name="${r.name}" cat=${r.category} ` +
          `state.principal=${r.budgetState.principal}`);
      }
      continue;
    }

    console.log(`[upsert] ${src.category}: ${rows.length} rows...`);
    const t0 = Date.now();
    const stats = await upsertBatch(rows, DATAGOV_2016_PUB_DATE, DATAGOV_2016_LABEL, src.url);
    const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
    console.log(`[upsert] ${src.category}: ins=${stats.inserted} upd=${stats.updated} (${elapsed}s)`);
    totalInserted += stats.inserted;
    totalUpdated += stats.updated;
  }

  console.log(`[summary] parsed=${totalParsed} inserted=${totalInserted} updated=${totalUpdated}`);

  if (!argDryRun) {
    const verifyRes = await pool.query<{ cnt: string; total_ron: string }>(
      `SELECT COUNT(*)::text AS cnt, COALESCE(SUM(debt_total), 0)::text AS total_ron
       FROM anaf.datornici WHERE publication_date = $1`,
      [DATAGOV_2016_PUB_DATE]
    );
    const v = verifyRes.rows[0];
    console.log(`[verify] db rows for ${DATAGOV_2016_LABEL}: ${v.cnt} firms, ${v.total_ron} RON total debt`);
  }
}

// ── TODO: live scrape din anaf.ro/restante/ via captcha solver ─────────────
// Placeholder — necesită cont 2captcha/anti-captcha (~$1-3/1000 captcha-uri).
// Workflow:
//   1. GET /restante/index.xhtml → extract ViewState + cookies
//   2. GET /restante/kaptcha.jpg?pfdrid_c=true → image bytes
//   3. POST image la 2captcha → wait for solution string
//   4. POST /restante/index.xhtml cu form:inputc=<solution> + ViewState
//   5. Parse <update id="form:dataTable"> response → extract <tr> rows
//   6. Pagination: PrimeFaces dataTable_paginator → POST cu page param
// Probleme cunoscute:
//   - ANAF expune doar trimestrul curent (singura pagină live). Arhive
//     trimestriale istorice nu sunt accesibile public — ar fi nevoie de
//     archive.org snapshots sau colaborare cu listafirme.eu.
//
// async function scrapeAnafLive(quarterLabel: string): Promise<void> {
//   throw new Error('not implemented — needs captcha solver');
// }

async function main(): Promise<void> {
  console.log(`[start] source=${argSource} dry-run=${argDryRun}`);

  switch (argSource) {
    case 'datagov2016':
      await importDataGov2016();
      break;
    case 'live':
      throw new Error(
        'live scrape not implemented — anaf.ro/restante/ blocked by CAPTCHA. ' +
          'Add 2captcha/anti-captcha integration in scrapeAnafLive(). ' +
          'See header comments for workflow.'
      );
    default:
      throw new Error(`unknown --source=${argSource} (valid: datagov2016, live)`);
  }

  await pool.end();
  console.log('[done]');
}

main().catch((e) => {
  console.error('[fatal]', e);
  process.exit(1);
});
