import { SourceAdapter } from './base.mjs';
import { log } from '../logger.mjs';
import { join } from 'node:path';
import { ensureDir, LOG_DIR, fileExistsAtAnyLayout } from '../paths.mjs';

// CAS-SSO (Apereo) der NetCologne — Login auf sso.netcologne.de, Redirect zu meinekundenwelt.netcologne.de.
const LOGIN_URL    = 'https://sso.netcologne.de/cas/login?service=https://meinekundenwelt.netcologne.de/&mandant=nc';
const PORTAL_HOST  = 'meinekundenwelt.netcologne.de';
const INVOICES_URL = 'https://meinekundenwelt.netcologne.de/rechnungen';
const API_BASE     = 'https://meinekundenwelt.netcologne.de/api/api/core/invoice';

// Welche Dokument-Typen pro Karte verfuegbar sind. flag = Account-Option, default true nur fuer Rechnung-PDF.
// id-Pfad gibt an, wo die Hex-ID im List-API-Response steht.
// sub = Unterordner, ext = Datei-Extension, label = nur fuer Logs.
const DOC_TYPES = [
  { flag: 'fetchInvoicePdf', idPath: ['invoice', 'pdf'], sub: 'Rechnungen',                ext: 'pdf', label: 'Rechnung-PDF' },
  { flag: 'fetchInvoiceCsv', idPath: ['invoice', 'csv'], sub: 'Rechnungen',                ext: 'csv', label: 'Rechnung-CSV' },
  { flag: 'fetchEvnPdf',     idPath: ['egn',     'pdf'], sub: 'Einzelverbindungsnachweis', ext: 'pdf', label: 'EVN-PDF'      },
  { flag: 'fetchEvnCsv',     idPath: ['egn',     'csv'], sub: 'Einzelverbindungsnachweis', ext: 'csv', label: 'EVN-CSV'      },
];

const MONTH_NAMES_DE = [
  'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
  'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember',
];

export class NetcologneAdapter extends SourceAdapter {
  static key = 'netcologne';
  static label = 'NetCologne';
  static group = 'Telekom';

  async login() {
    const { page } = this;
    await page.goto(LOGIN_URL, { waitUntil: 'commit' });
    await page.waitForTimeout(1500);
    log.info(`[netcologne] URL nach goto: ${page.url()}`);

    if (new URL(page.url()).hostname === PORTAL_HOST) {
      log.info('[netcologne] Session noch aktiv (Direkt-Redirect zum Portal)');
      return;
    }

    if (await this.dismissConsent()) log.info('[netcologne] Consent-Banner bestaetigt');

    const userField = page.locator('input#username').first();
    const pwField   = page.locator('input#password').first();
    const submit    = page.locator('input[name="submit"][type="submit"]').first();

    try {
      await userField.waitFor({ state: 'visible', timeout: 180_000 });
    } catch {
      const shot = join(ensureDir(LOG_DIR), `netcologne-no-form-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      throw new Error(`[netcologne] Login-Form nicht sichtbar. URL: ${page.url()} — Screenshot: ${shot}`);
    }

    await userField.fill(this.account.credentials.username);
    await pwField.fill(this.account.credentials.password);
    await page.waitForTimeout(400);

    log.info('[netcologne] Druecke Anmelden');
    await submit.click();

    log.info('[netcologne] Warte auf Redirect zu meinekundenwelt.netcologne.de ...');
    try {
      await page.waitForFunction(
        (host) => location.hostname === host,
        PORTAL_HOST,
        { timeout: 180_000 },
      );
    } catch (e) {
      const shot = join(ensureDir(LOG_DIR), `netcologne-no-redirect-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      log.warn(`[netcologne] Kein Redirect nach 3 Min. URL: ${page.url()} — Screenshot: ${shot}`);
      throw e;
    }
    log.info(`[netcologne] Login erfolgreich (URL: ${page.url()})`);
  }

  /** JWT vom Browser einsammeln — entweder aus dem ersten /api/api/-Request oder aus localStorage als Fallback. */
  async _captureBearerToken() {
    const { page } = this;
    let token = null;
    const onRequest = (req) => {
      if (token) return;
      if (!req.url().includes('/api/api/')) return;
      const auth = req.headers()['authorization'];
      if (auth?.toLowerCase().startsWith('bearer ')) token = auth.substring(7);
    };
    page.on('request', onRequest);
    try {
      await page.goto(INVOICES_URL, { waitUntil: 'domcontentloaded' });
      // Auf den ersten API-Call warten, damit der Listener feuert
      await page.waitForResponse((r) => r.url().includes('/api/api/'), { timeout: 30_000 }).catch(() => {});
      await page.waitForTimeout(800);
    } finally {
      page.off('request', onRequest);
    }
    if (token) return token;

    // Fallback: localStorage durchsuchen
    log.info('[netcologne] JWT nicht aus Request gefangen — versuche localStorage');
    return await page.evaluate(() => {
      const isJwt = (v) => typeof v === 'string' && v.startsWith('eyJ') && v.split('.').length === 3;
      for (let i = 0; i < localStorage.length; i++) {
        const k = localStorage.key(i);
        const v = localStorage.getItem(k);
        if (isJwt(v)) return v;
        try {
          const obj = JSON.parse(v);
          if (isJwt(obj?.access_token)) return obj.access_token;
          if (isJwt(obj?.token))         return obj.token;
        } catch { /* nicht JSON */ }
      }
      return null;
    });
  }

  async *iterateStatements() {
    const { context } = this;

    // Welche Doc-Typen will der User? Default: nur Rechnung-PDF.
    const wantedFlags = new Set(DOC_TYPES.filter((t) => {
      const v = this.account[t.flag];
      if (v === undefined || v === null || v === '') return t.flag === 'fetchInvoicePdf';
      return v === true || v === 'true';
    }).map((t) => t.flag));
    if (wantedFlags.size === 0) {
      log.warn('[netcologne] Keine Doc-Typen ausgewaehlt — nichts zu holen');
      return;
    }
    log.info(`[netcologne] Aktive Typen: ${[...wantedFlags].join(', ')}`);

    const bearerToken = await this._captureBearerToken();
    if (!bearerToken) {
      const shot = join(ensureDir(LOG_DIR), `netcologne-no-jwt-${Date.now()}.png`);
      await this.page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      throw new Error(`[netcologne] JWT Bearer-Token konnte nicht ermittelt werden. Screenshot: ${shot}`);
    }
    log.info(`[netcologne] JWT erfasst (${bearerToken.length} chars)`);

    const apiHeaders = {
      'Authorization': `Bearer ${bearerToken}`,
      'Accept': 'application/json, text/plain, */*',
    };

    // Verfuegbare Jahres-Spanne abfragen
    const rangeResp = await context.request.get(`${API_BASE}/meta/range`, { headers: apiHeaders });
    if (!rangeResp.ok()) throw new Error(`[netcologne] /meta/range HTTP ${rangeResp.status()}`);
    const range = await rangeResp.json();
    const fromY = Number(range.fromYear);
    const toY   = Number(range.toYear);
    const years = [];
    for (let y = fromY; y <= toY; y++) years.push(y);
    log.info(`[netcologne] Jahre verfuegbar: ${fromY}–${toY} (${years.length} Stueck)`);

    // Neueste zuerst → Pre-Skip ist am effizientesten
    for (const year of years.slice().reverse()) {
      const listResp = await context.request.get(`${API_BASE}/meta/list?year=${year}`, { headers: apiHeaders });
      if (!listResp.ok()) {
        log.warn(`[netcologne] /meta/list?year=${year} HTTP ${listResp.status()}`);
        continue;
      }
      const months = await listResp.json();
      log.info(`[netcologne] -> Jahr ${year}: ${months.length} Monate`);

      for (const monthEntry of months) {
        const dateStr = monthEntry.date || '';      // "2026-03-01"
        const yyyy = Number(dateStr.substring(0, 4));
        const mm   = dateStr.substring(5, 7);
        const monthName = MONTH_NAMES_DE[Number(mm) - 1] || mm;

        // Pro Karten-Eintrag (meist 1× landline, koennte mehrere Kategorien geben):
        // Erst alle gewuenschten Typen pruefen, schon vorhandene bündelig skippen, nur fehlende holen.
        for (const invoice of monthEntry.invoices || []) {
          const todos = [];
          let already = 0;
          for (const t of DOC_TYPES) {
            if (!wantedFlags.has(t.flag)) continue;
            const id = invoice[t.idPath[0]]?.[t.idPath[1]];
            if (!id) continue;
            // Pattern "JJJJ-MM-TT <Monatsname> <Jahr>.<ext>" — Datum vorn, Source und Doc-Typ
            // stecken im Folder (NetCologne/<Jahr>/Rechnungen|Einzelverbindungsnachweis/).
            const filename = `${dateStr} ${monthName} ${yyyy}.${t.ext}`;
            if (this.cfg) {
              const existing = fileExistsAtAnyLayout(this.cfg, this.account.folder, yyyy, t.sub, filename);
              if (existing) {
                already++;
                continue;
              }
            }
            todos.push({ id, filename, sub: t.sub, label: t.label });
          }
          if (todos.length === 0) {
            if (already > 0) log.info(`[${this.account.id}] skip ${monthName} ${yyyy}: alle ${already} Doc-Typen bereits vorhanden`);
            continue;
          }
          if (already > 0) {
            log.info(`[${this.account.id}] ${monthName} ${yyyy}: ${already} bereits vorhanden, ${todos.length} werden geholt`);
          }

          for (const d of todos) {
            try {
              const fileResp = await context.request.get(`${API_BASE}/file/${d.id}`, { headers: apiHeaders });
              if (!fileResp.ok()) {
                log.warn(`[netcologne] HTTP ${fileResp.status()} fuer "${d.filename}" (id=${d.id})`);
                continue;
              }
              const buffer = await fileResp.body();
              if (!buffer || buffer.length === 0) {
                log.warn(`[netcologne] Leere Response fuer "${d.filename}" (id=${d.id})`);
                continue;
              }
              log.debug(`[netcologne] ${d.filename}: ${buffer.length} bytes`);
              yield { filename: d.filename, year: yyyy, subfolder: d.sub, buffer };
            } catch (e) {
              log.warn(`[netcologne] Request-Fehler bei "${d.filename}": ${e.message}`);
            }
            await new Promise((r) => setTimeout(r, 200)); // sanfte Pause
          }
        }
      }
    }
  }
}
