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

// Einstieg über www.gothaer.de — der Login-Button leitet via /portal.htm auf Azure AD B2C
// (login.gothaer.de) weiter. Direkt auf login.gothaer.de zu springen scheitert, weil state/csrf
// session-gebunden erzeugt werden.
const ENTRY_URL = 'https://www.gothaer.de/meine-gothaer';

export class GothaerAdapter extends SourceAdapter {
  static key = 'gothaer';
  static label = 'Gothaer Versicherung';
  static group = 'Versicherungen';

  async dismissGothaerCmp() {
    const { page } = this;
    const btn = page.locator('#cmpbox a.cmpboxbtnyes').first();
    if (await btn.isVisible({ timeout: 2000 }).catch(() => false)) {
      await btn.click();
      log.info('[gothaer] CMP "Alle akzeptieren" geklickt');
      await page.waitForTimeout(400);
      return true;
    }
    return false;
  }

  async login() {
    const { page } = this;
    log.info('[gothaer] Öffne Meine-Gothaer-Einstieg');
    await page.goto(ENTRY_URL, { waitUntil: 'domcontentloaded' });
    await page.waitForTimeout(800);

    await this.dismissGothaerCmp();

    // Der <a> hat role="button" + aria-label "Jetzt bei Meine Gothaer einloggen oder registrieren".
    // Daher per getByRole('button', name=...) suchen; Fallback: sichtbarer href-Link mit Text "einloggen".
    const loginByRole = page.getByRole('button', { name: /einloggen/i }).first();
    const loginByHref = page.locator('a[href*="/meine-gothaer/portal.htm"]:visible').filter({ hasText: /einloggen/i }).first();

    let clicked = false;
    for (const [idx, candidate] of [loginByRole, loginByHref].entries()) {
      const visible = await candidate.isVisible({ timeout: 5000 }).catch(() => false);
      log.debug(`[gothaer] login-Kandidat ${idx} (${idx === 0 ? 'role=button' : 'href'}) sichtbar=${visible}`);
      if (visible) {
        log.info('[gothaer] Klicke "Jetzt einloggen"');
        await candidate.click();
        clicked = true;
        break;
      }
    }
    if (!clicked) log.info('[gothaer] Kein "Jetzt einloggen"-Button gefunden — prüfe URL ob schon eingeloggt');

    await page.waitForTimeout(1500);
    log.info(`[gothaer] URL nach Einstieg: ${page.url()}`);

    if (/\/meine-gothaer\/portal\.htm/.test(page.url())) {
      log.info('[gothaer] Session noch aktiv — bereits im Portal');
      return;
    }
    if (!/login\.gothaer\.de/.test(page.url())) {
      const shot = join(ensureDir(LOG_DIR), `gothaer-stuck-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      throw new Error(`[gothaer] Kein Portal und kein Login erreicht. URL: ${page.url()} — Screenshot: ${shot}`);
    }

    // Azure AD B2C mit Vue-Custom-UI: #v-username / #v-password sichtbar, der native
    // <input type="submit" class="login-form__submit"> ist CSS-versteckt — Submit per Enter /
    // form.requestSubmit() statt direkt klicken.
    const user = page.locator('#v-username').first();
    const pw = page.locator('#v-password').first();

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

    await user.fill(this.account.credentials.username);
    await pw.fill(this.account.credentials.password);
    await page.waitForTimeout(400);

    log.info('[gothaer] Submit (Enter im Passwort-Feld)');
    await pw.press('Enter');
    // Falls SPA Enter abfängt ohne zu submitten: form.requestSubmit() triggert die native Submit-Logik
    await page.waitForTimeout(1200);
    if (/login\.gothaer\.de/.test(page.url())) {
      const still = await user.isVisible({ timeout: 500 }).catch(() => false);
      if (still) {
        log.info('[gothaer] Enter hat nicht submitted — fallback form.requestSubmit()');
        await page.evaluate(() => document.querySelector('form.login-form')?.requestSubmit());
      }
    }

    log.info('[gothaer] Warte auf Post-Login-Redirect (bis zu 3 Min — evtl. MFA) ...');
    try {
      await page.waitForFunction(
        () => !/login\.gothaer\.de/.test(location.href),
        null,
        { timeout: 180_000 },
      );
    } catch (e) {
      const shot = join(ensureDir(LOG_DIR), `gothaer-no-redirect-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      log.warn(`[gothaer] Kein Redirect nach 3 Min. URL: ${page.url()} — Screenshot: ${shot}`);
      throw e;
    }
    log.info(`[gothaer] Login erfolgreich (URL: ${page.url()})`);
  }

  async ensureInboxListVisible() {
    const { page } = this;
    const items = page.locator('#inbox .webmailer__list .webmailer__inboxItem');

    // Ready = mindestens ein Item ist VISIBLE (nicht nur im DOM mit Text). Sonst klickt
    // Playwright-click in 30s-Timeout, weil Detail-View die Liste verdeckt.
    const isListReady = async () => {
      const cnt = await items.count().catch(() => 0);
      if (cnt === 0) return false;
      for (let i = 0; i < Math.min(cnt, 3); i++) {
        if (await items.nth(i).isVisible({ timeout: 150 }).catch(() => false)) return true;
      }
      return false;
    };

    if (await isListReady()) { log.debug('[gothaer] ensureInboxListVisible: bereits ready'); return; }

    log.info(`[gothaer]   → Rücknavigation (URL: ${page.url()})`);

    // Versuch 1: ESC
    log.debug('[gothaer] Versuch ESC');
    await page.keyboard.press('Escape').catch(() => {});
    await page.waitForTimeout(250);
    if (await isListReady()) { log.debug('[gothaer] ready nach ESC'); return; }

    // Versuch 2: Eingang-Tab klicken (mit force, falls overlay verdeckt)
    const inboxTab = page.locator('button.tabs__nav-item', { hasText: /^\s*Eingang/ }).first();
    if (await inboxTab.isVisible({ timeout: 600 }).catch(() => false)) {
      log.debug('[gothaer] Versuch Eingang-Tab Klick');
      await inboxTab.click({ force: true }).catch(() => {});
      await page.waitForTimeout(400);
      if (await isListReady()) { log.debug('[gothaer] ready nach Tab-Klick'); return; }
    }

    // Versuch 3: Hash hard-toggle dashboard→webMailer (Vue-Router ignoriert same-hash sets)
    log.debug('[gothaer] Versuch Hash-Toggle');
    await page.evaluate(() => { window.location.hash = '#/dashboard'; });
    await page.waitForTimeout(200);
    await page.evaluate(() => { window.location.hash = '#/webMailer'; });
    await page.waitForTimeout(600);
    if (await isListReady()) { log.debug('[gothaer] ready nach Hash-Toggle'); return; }

    log.warn('[gothaer]   → Liste konnte nicht freigemacht werden — Detail-View bleibt sichtbar?');
  }

  async loadContractAliases() {
    const { page } = this;
    log.info('[gothaer] Lade Verträge-Übersicht für Aliase ...');

    const tileBtn = page.locator('.overview-tile', { hasText: 'Meine Verträge' })
      .locator('button.section-card__link').first();
    if (!(await tileBtn.isVisible({ timeout: 5_000 }).catch(() => false))) {
      log.warn('[gothaer] Verträge-Kachel nicht sichtbar — überspringe Auto-Aliase');
      return new Map();
    }
    await tileBtn.click();

    const articles = page.locator('article.contract-overview');
    try {
      await articles.first().waitFor({ state: 'visible', timeout: 15_000 });
    } catch {
      log.warn('[gothaer] Vertrags-Karten nicht erschienen — überspringe Auto-Aliase');
      return new Map();
    }

    const cnt = await articles.count();
    log.info(`[gothaer] ${cnt} Verträge gefunden`);

    const map = new Map();
    for (let i = 0; i < cnt; i++) {
      const art = articles.nth(i);
      const typ = ((await art.locator('section[aria-labelledby="contract-label"] p').textContent().catch(() => '')) || '').trim();
      const nr = ((await art.locator('section[aria-labelledby="contract-number"] p').textContent().catch(() => '')) || '').trim();
      if (typ && nr) {
        const subfolder = `${typ} (${nr})`;
        map.set(nr, subfolder);
        log.debug(`[gothaer] Auto-Alias ${nr} → ${subfolder}`);
      }
    }

    // Zurück zum Dashboard, damit openPostbox() seinen normalen Weg gehen kann
    await page.evaluate(() => { window.location.hash = '#/dashboard'; });
    await page.waitForTimeout(500);

    return map;
  }

  async openPostbox() {
    const { page } = this;
    log.info(`[gothaer] openPostbox aufgerufen, URL: ${page.url()}`);

    // Schnellweg: wenn schon auf #/webMailer und Liste sichtbar, fertig
    const firstItem = page.locator('#inbox .webmailer__list .webmailer__inboxItem').first();
    if (/#\/webMailer/.test(page.url())) {
      if (await firstItem.isVisible({ timeout: 5_000 }).catch(() => false)) {
        log.info('[gothaer] Bereits in Postbox-Liste');
        return;
      }
    }

    // Sonst: auf Dashboard-Kacheln warten und Postfach-Kachel klicken
    log.info('[gothaer] Warte auf Dashboard-Kacheln (.overview-tile) ...');
    try {
      await page.locator('.overview-tile').first().waitFor({ state: 'visible', timeout: 45_000 });
      const tileCount = await page.locator('.overview-tile').count();
      log.info(`[gothaer] ${tileCount} Kacheln sichtbar`);
    } catch {
      // Vielleicht direkt in der Postbox gelandet ohne Dashboard-Zwischenstop
      if (await firstItem.isVisible({ timeout: 2_000 }).catch(() => false)) {
        log.info('[gothaer] Kein Dashboard, aber Postbox-Liste direkt sichtbar');
        return;
      }
      const shot = join(ensureDir(LOG_DIR), `gothaer-no-dashboard-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      throw new Error(`[gothaer] Dashboard-Kacheln nicht geladen. URL: ${page.url()} — Screenshot: ${shot}`);
    }

    // Kachel "Mein Postfach" → darin der Button mit Text "Zum Postfach"
    const postboxTile = page.locator('.overview-tile', { hasText: 'Mein Postfach' }).first();
    const openBtn = postboxTile.locator('button.section-card__link').first();

    try {
      await openBtn.waitFor({ state: 'visible', timeout: 10_000 });
      log.info('[gothaer] Klicke Kachel-Button "Zum Postfach"');
      await openBtn.click();
    } catch (e) {
      const shot = join(ensureDir(LOG_DIR), `gothaer-no-postbox-button-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      throw new Error(`[gothaer] "Zum Postfach"-Button nicht klickbar: ${e.message} — URL: ${page.url()} — Screenshot: ${shot}`);
    }

    // Warten auf Route und Inbox-Content
    try {
      await page.waitForURL(/#\/webMailer/, { timeout: 10_000 });
      log.info('[gothaer] Route #/webMailer erreicht');
    } catch {
      log.warn(`[gothaer] Route #/webMailer nicht erreicht nach Klick — URL: ${page.url()}`);
    }

    try {
      await firstItem.waitFor({ state: 'visible', timeout: 30_000 });
      const cnt = await page.locator('#inbox .webmailer__list .webmailer__inboxItem').count();
      log.info(`[gothaer] Inbox geladen, ${cnt} Einträge`);
    } catch {
      const shot = join(ensureDir(LOG_DIR), `gothaer-postbox-empty-${Date.now()}.png`);
      await page.screenshot({ path: shot, fullPage: true }).catch(() => {});
      log.warn(`[gothaer] Inbox-Liste nicht sichtbar — evtl. leer oder Loading-Fehler. URL: ${page.url()} — Screenshot: ${shot}`);
    }
    await page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {});
  }

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

    // Aliase aus Vertrags-Übersicht crawlen (Auto). Manuelle kontoAliases aus der Config
    // überschreiben Auto-Werte (User kann den Namen anpassen).
    this._aliases = new Map();
    try {
      const auto = await this.loadContractAliases();
      for (const [k, v] of auto) this._aliases.set(k, v);
    } catch (e) {
      log.warn(`[gothaer] Auto-Alias-Crawl fehlgeschlagen: ${e.message}`);
    }
    for (const [k, v] of Object.entries(this.account.kontoAliases || {})) {
      this._aliases.set(k, v);
    }

    await this.openPostbox();

    // Fehler-Modal abfangen (erscheint laut HTML, wenn Postfach nicht laden konnte).
    // waitFor statt isVisible — letzteres wartet NICHT (Timeout ist nur Locator-Resolution),
    // wir wuerden das Modal verpassen wenn es erst nach API-Roundtrip rendert.
    const errorDialog = page.locator('dialog.modal:has-text("Postfach konnte nicht geladen werden")').first();
    const dialogShown = await errorDialog.waitFor({ state: 'visible', timeout: 500 }).then(() => true).catch(() => false);
    if (dialogShown) {
      throw new Error('[gothaer] Postfach konnte nicht geladen werden (Dialog aktiv).');
    }

    // Tab "Eingang" ist Default; sicherheitshalber aktivieren
    const inboxTab = page.locator('button.tabs__nav-item', { hasText: /^\s*Eingang/ }).first();
    try {
      const selected = await inboxTab.getAttribute('aria-selected').catch(() => null);
      if (selected !== 'true') {
        await inboxTab.click();
        await page.waitForTimeout(500);
      }
    } catch { /* ignore */ }

    // Nur die Items im aktiven Inbox-Tabpanel. Die Skeleton-Loader sind in <div style="display:none;">
    // versteckt und teilen die Klasse .webmailer__mail, nicht .webmailer__inboxItem — hier also sauber.
    const items = page.locator('#inbox .webmailer__list .webmailer__inboxItem');
    const total = await items.count();
    log.info(`[gothaer] ${total} Nachrichten im Eingang`);

    for (let idx = 0; idx < total; idx++) {
      const row = items.nth(idx);
      await row.scrollIntoViewIfNeeded({ timeout: 2000 }).catch(() => {});
      await row.waitFor({ state: 'visible', timeout: 3000 }).catch(() => {});
      const metaCols = row.locator('.webmailer__inboxItemCol--meta');
      const subject = ((await metaCols.nth(0).innerText().catch(() => '')) || '').replace(/\s+/g, ' ').trim();
      const senderText = ((await row.locator('.webmailer__inboxItemCol--sender').first().innerText().catch(() => '')) || '').trim();
      const dateStr = ((await row.locator('.webmailer__inboxItemCol--date').first().innerText().catch(() => '')) || '').trim();
      const dateMatch = dateStr.match(/(\d{2})\.(\d{2})\.(\d{4})/);
      const yearFromDate = dateMatch ? Number(dateMatch[3]) : new Date().getFullYear();

      // Vertragsnummer aus Betreff (z.B. "39.428.367568" oder "95-482845-61")
      const nrDotted = subject.match(/\b(\d{2}\.\d{3}\.\d{6})\b/);
      const nrLv = subject.match(/\b(\d{2}-\d{6}-\d{2})\b/) || subject.match(/\b(\d{2}-\d{8})\b/);
      const vertragsNr = nrDotted?.[1] || nrLv?.[1] || null;

      log.info(`[gothaer] [${idx + 1}/${total}] ${dateStr} | ${senderText} | ${subject}`);

      // Detail-Ansicht öffnen
      await row.click();
      await page.waitForTimeout(600);

      const dlLinks = page.locator('.webmailer__mailContent--attachments a.webmailer__download');
      // Auf mindestens einen Link oder auf "keine Anhänge" warten — kurze Zeit
      await dlLinks.first().waitFor({ state: 'visible', timeout: 4000 }).catch(() => {});
      const dlCount = await dlLinks.count();

      if (dlCount === 0) {
        log.info(`[gothaer]   → ohne Anhang`);
      } else {
        for (let d = 0; d < dlCount; d++) {
          const link = dlLinks.nth(d);
          const href = await link.getAttribute('href').catch(() => null);
          const filenameRaw = ((await link.locator('.anchor__text').innerText().catch(() => '')) || '').trim();
          if (!href || !filenameRaw) {
            log.warn(`[gothaer]   → Anhang ohne href/filename (href=${href}, name=${filenameRaw})`);
            continue;
          }

          // Jahr aus Filename-Prefix (YYYYMMDD_...), Fallback: Datum-Spalte
          const fnDateMatch = filenameRaw.match(/^(\d{4})(\d{2})(\d{2})_(.+)$/);
          const year = fnDateMatch ? Number(fnDateMatch[1]) : yearFromDate;
          // Filename auf "JJJJ-MM-TT <Rest>" umbiegen — Datum vorn, Trenner Space statt Underscore.
          // Drei Quellen fuer das Datum, in Reihenfolge: Filename-Prefix > Datum-Spalte > nichts.
          let dateIso = '';
          let body = filenameRaw;
          if (fnDateMatch) {
            dateIso = `${fnDateMatch[1]}-${fnDateMatch[2]}-${fnDateMatch[3]}`;
            body = fnDateMatch[4];
          } else if (dateMatch) {
            dateIso = `${dateMatch[3]}-${dateMatch[2]}-${dateMatch[1]}`;
          }
          const filenameNormalized = dateIso ? `${dateIso} ${body}` : body;

          // Lookup: erst exakt, dann normalisiert (Punkte/Bindestriche raus) — so matchen
          // verschiedene Schreibweisen derselben Vertragsnummer (z.B. 95-48284561 vs. 95-482845-61).
          let subfolder = 'Sonstiges';
          if (vertragsNr) {
            if (this._aliases.has(vertragsNr)) {
              subfolder = this._aliases.get(vertragsNr);
            } else {
              const norm = vertragsNr.replace(/[.\-]/g, '');
              const hit = [...this._aliases.entries()].find(([k]) => k.replace(/[.\-]/g, '') === norm);
              subfolder = hit ? hit[1] : vertragsNr;
            }
          }

          // Pre-Skip — prueft beide Filename-Varianten:
          //  1) NEU "JJJJ-MM-TT <Rest>.pdf" (aktuelle Konvention)
          //  2) ALT "JJJJMMTT_<Rest>.pdf" (Server-Filename, wie er bei Gothaer angeboten wird)
          // Frueher nur Variante (1) → bei Bestandsfiles im Old-Format wurde alles
          // erneut gezogen. Jetzt wird auch das alte Layout erkannt → kein Re-Download
          // mehr, alte Files bleiben unter ihrem alten Namen liegen.
          if (this.cfg) {
            const safeSub = safeFilename(subfolder);
            let existing = fileExistsAtAnyLayout(this.cfg, this.account.folder, year, safeSub, filenameNormalized);
            if (!existing) {
              existing = fileExistsAtAnyLayout(this.cfg, this.account.folder, year, safeSub, filenameRaw);
            }
            if (existing) {
              log.info(`[${this.account.id}] skip (bereits vorhanden): ${existing}`);
              continue;
            }
          }

          const absUrl = new URL(href, page.url()).href;
          try {
            const resp = await this.context.request.get(absUrl);
            if (!resp.ok()) {
              log.warn(`[gothaer]   → HTTP ${resp.status()} bei "${filenameNormalized}"`);
              continue;
            }
            yield { filename: filenameNormalized, year, buffer: await resp.body(), subfolder };
          } catch (e) {
            log.warn(`[gothaer]   → Download fehlgeschlagen "${filenameNormalized}": ${e.message}`);
          }
        }
      }

      // Zurück zur Liste — beide Fälle abdecken (Detail-View oder Dashboard-Redirect)
      if (idx < total - 1) {
        await this.ensureInboxListVisible();
      }
    }
  }
}
