import { appendFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { ensureDir, LOG_DIR } from './paths.mjs';
import { currentAccountId } from './run-context.mjs';

let debugEnabled = !!process.env.DEBUG;
let runFile = null;
let quietMode = false;

export function setQuiet(on) { quietMode = !!on; }

export function setDebug(on) {
  debugEnabled = !!on;
  if (debugEnabled && !runFile) {
    ensureDir(LOG_DIR);
    const d = new Date();
    const stamp = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}-${String(d.getHours()).padStart(2, '0')}${String(d.getMinutes()).padStart(2, '0')}${String(d.getSeconds()).padStart(2, '0')}`;
    runFile = join(LOG_DIR, `debug-${stamp}.log`);
    write('INFO', [`Debug-Modus aktiv — Run-Log: ${runFile}`]);
  }
}

export function isDebug() {
  return debugEnabled;
}

function ts() {
  return new Date().toISOString();
}

function todayFile() {
  ensureDir(LOG_DIR);
  const d = new Date();
  const name = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}.log`;
  return join(LOG_DIR, name);
}

// Logs >LOG_RETENTION_DAYS Tage loeschen. Logs enthalten Bank-Filenames und Account-IDs
// (= Klartext-Metadaten ueber Steuerdokumente) — unbegrenzt zu wachsen ist sowohl
// Disk-Verschwendung als auch unnoetige Persistenz personenbezogener Daten.
// Wird einmalig beim Modul-Init aufgerufen, best-effort — Errors werden ignoriert.
const LOG_RETENTION_DAYS = 30;
function pruneOldLogs() {
  try {
    const dir = ensureDir(LOG_DIR);
    const cutoffMs = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;
    let deleted = 0;
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
      if (!entry.isFile() || !entry.name.endsWith('.log')) continue;
      const full = join(dir, entry.name);
      try {
        const st = statSync(full);
        if (st.mtimeMs < cutoffMs) {
          unlinkSync(full);
          deleted++;
        }
      } catch { /* einzelne Locks/Permission-Probleme: ueberspringen */ }
    }
    if (deleted > 0) {
      // Direktes write umgehen, sonst Bootstrap-Loop. Direkt stdout.
      process.stdout.write(`${ts()} [INFO] [logger] ${deleted} alte Log-Files (>${LOG_RETENTION_DAYS}d) entsorgt\n`);
    }
  } catch { /* LOG_DIR nicht erreichbar: nicht-fatal */ }
}
// Beim Modul-Import einmal pruefen. Idempotent ueber Lebenszeit des Prozesses
// (jeder Adapter-Lauf startet einen frischen Prozess).
pruneOldLogs();

const listeners = new Set();
export function addLogListener(fn) { listeners.add(fn); return () => listeners.delete(fn); }

// Secret-Redaction: maskiert Bearer-Tokens, password=-Pairs, Cookie-Header und
// API-Keys-aussehende Hex/Base64-Strings. Hilft beim Versand von Debug-Logs an Support
// oder Steuerberater — auch wenn Adapter aus Versehen credentials loggen.
const SECRET_PATTERNS = [
  // Bearer / Basic / X-API-Key etc. (HTTP Auth Header)
  [/\b(Bearer|Basic|Token)\s+[A-Za-z0-9_\-./+=]+/gi, '$1 ***'],
  // password=xxx, pass=xxx, secret=xxx, apikey=xxx (Form-Encoded oder Logs)
  [/\b(password|passwort|pass|pwd|secret|apikey|api[_-]?key|consumerkey|appkey|appsecret)\s*[=:]\s*[^\s,;&"']+/gi, '$1=***'],
  // Cookie- / Set-Cookie-Header (kompletter Cookie-Inhalt)
  [/\b(Cookie|Set-Cookie|x-xsrf-token|sessionid)\s*:\s*[^\r\n]+/gi, '$1: ***'],
  // Rohe JWTs (eyJHEADER.PAYLOAD.SIGNATURE) — z.B. Atruvia jwtKundeListe/jwtDokumentListe
  // im Body. Greift wenn jemand ein Body-Object loggt.
  [/eyJ[A-Za-z0-9_\-]{8,}\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+/g, 'jwt:***'],
];

function redactSecrets(s) {
  let out = s;
  for (const [pat, rep] of SECRET_PATTERNS) out = out.replace(pat, rep);
  return out;
}

function write(level, parts) {
  // CRLF-Strip + Secret-Redaction: Adapter koennten user-content (Bank-Filenames,
  // Antwort-Texte) loggen. Newlines wuerden Log-Zeilen spalten (faked [LEVEL]-Lines),
  // Plain-Secrets kompromittieren Support-Logs. Beide einmal hier zentral filtern.
  const msg = redactSecrets(
    parts
      .map((p) => (typeof p === 'string' ? p : JSON.stringify(p)))
      .join(' ')
      .replace(/[\r\n]+/g, ' ')
  );
  const line = `${ts()} [${level}] ${msg}\n`;
  if (!quietMode) process.stdout.write(line);
  try { appendFileSync(todayFile(), line); } catch { /* Log-Fehler nicht eskalieren */ }
  if (runFile) {
    try { appendFileSync(runFile, line); } catch { /* ignore */ }
  }
  // accountId via AsyncLocalStorage — leer (null) fuer Logs ausserhalb eines Account-Runs.
  // Listener (z.B. RPC-Stream) kann damit pro Tab routen.
  const accountId = currentAccountId();
  for (const fn of listeners) {
    try { fn(level, msg, accountId); } catch { /* Listener-Fehler nicht eskalieren */ }
  }
}

export const log = {
  info: (...a) => write('INFO', a),
  warn: (...a) => write('WARN', a),
  error: (...a) => write('ERROR', a),
  debug: (...a) => { if (debugEnabled) write('DEBUG', a); },
};

// Bekannter Noise: Tracking-, Werbe-, Telemetrie-Endpoints, die auf vielen Bank-/Shop-Seiten
// regelmäßig abbrechen. Wir blenden sie im Debug-Log aus, damit echte Fehler sichtbar bleiben.
const NOISE_URL_PATTERNS = [
  /amazon-adsystem\.com/i,
  /unagi\.amazon\./i,
  /paets\.advertising\./i,
  /experiment\.routing\./i,
  /pbs\.yahoo\.com/i,
  /\/sash\//i,
  /api\.stores\..+\.amazon\./i,
  /tsrvce\.gothaer\./i,
  /eum\.gothaer\./i,
  /cdn\.consentmanager\./i,
  /delivery\.consentmanager\./i,
  /googletagmanager\.com/i,
  /google-analytics\.com/i,
  /bat\.bing\.com/i,
  /doubleclick\.net/i,
];

const NOISE_CONSOLE_PATTERNS = [
  /preloaded using link preload but not used/i,
  /Failed to load resource.*429/i,
  /Failed to load resource.*ERR_/i,
  /WebAuthn is not supported on sites with TLS/i,
  /pubKeyCredParams is missing.*default algorithm/i,
  /AbortFromContinueButtonClick/,
  /PerformanceTracker.*already exists/,
  /autocomplete attributes/i,
  /Unable to publish metrics/,
  /\[NoriLogger\]/,
  /DSPClientStrategy/,
];

const NOISE_PAGEERROR_PATTERNS = [
  /^err is not defined/i,
];

// Hängt Browser-Console-Output, Page-Errors und fehlgeschlagene Requests an den Logger.
// Nur aktiv im Debug-Modus. Bekannter Noise wird ausgefiltert.
export function attachPageDebug(page, prefix = 'browser') {
  if (!debugEnabled) return;
  page.on('console', (msg) => {
    const text = msg.text();
    if (NOISE_CONSOLE_PATTERNS.some((p) => p.test(text))) return;
    log.debug(`[${prefix}:console:${msg.type()}] ${text}`);
  });
  page.on('pageerror', (err) => {
    const msg = err.message || '';
    if (NOISE_PAGEERROR_PATTERNS.some((p) => p.test(msg))) return;
    log.debug(`[${prefix}:pageerror] ${msg}`);
  });
  page.on('requestfailed', (req) => {
    const url = req.url();
    if (NOISE_URL_PATTERNS.some((p) => p.test(url))) return;
    const f = req.failure();
    log.debug(`[${prefix}:requestfailed] ${req.method()} ${url} — ${f ? f.errorText : 'unknown'}`);
  });
  page.on('response', (resp) => {
    const status = resp.status();
    if (status < 400) return;
    const url = resp.url();
    if (NOISE_URL_PATTERNS.some((p) => p.test(url))) return;
    log.debug(`[${prefix}:response:${status}] ${resp.request().method()} ${url}`);
  });
}
