import axios from 'axios';
import { chromium } from 'playwright';

const BASE_URL = 'https://www.mytischtennis.de';

class MyTischtennisClient {
  constructor() {
    this.baseURL = BASE_URL;
    this.client = axios.create({
      baseURL: this.baseURL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': '*/*'
      },
      maxRedirects: 0, // Don't follow redirects automatically
      validateStatus: (status) => status >= 200 && status < 400 // Accept 3xx as success
    });
  }

  /**
   * Get login page to extract XSRF token and CAPTCHA token
   * @returns {Promise<Object>} Object with xsrfToken, captchaToken, and captchaClicked flag
   */
  async getLoginPage() {
    try {
      const response = await this.client.get('/login?next=%2F');
      const html = typeof response.data === 'string' ? response.data : String(response.data || '');

      const extractFirst = (patterns) => {
        for (const pattern of patterns) {
          const match = html.match(pattern);
          if (match && (match[1] || match[2] || match[3])) {
            return match[1] || match[2] || match[3];
          }
        }
        return null;
      };

      // Parse form action and input fields for frontend login-form endpoint
      const formMatch = html.match(/<form[^>]*action=(?:"([^"]+)"|'([^']+)')[^>]*>([\s\S]*?)<\/form>/i);
      const loginAction = formMatch ? (formMatch[1] || formMatch[2] || '/login') : '/login';
      const formHtml = formMatch ? formMatch[3] : html;
      const fields = [];

      const inputRegex = /<input\b([\s\S]*?)>/gi;
      let inputMatch = null;
      while ((inputMatch = inputRegex.exec(formHtml)) !== null) {
        const rawAttributes = inputMatch[1] || '';
        const attributes = {};

        // Parses key="value", key='value', key=value and boolean attributes.
        const attributeRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
        let attributeMatch = null;
        while ((attributeMatch = attributeRegex.exec(rawAttributes)) !== null) {
          const key = attributeMatch[1];
          const value = attributeMatch[2] ?? attributeMatch[3] ?? attributeMatch[4] ?? true;
          attributes[key] = value;
        }

        fields.push({
          name: typeof attributes.name === 'string' ? attributes.name : null,
          id: typeof attributes.id === 'string' ? attributes.id : null,
          type: typeof attributes.type === 'string' ? attributes.type : 'text',
          placeholder: typeof attributes.placeholder === 'string' ? attributes.placeholder : null,
          autocomplete: typeof attributes.autocomplete === 'string' ? attributes.autocomplete : null,
          minlength: typeof attributes.minlength === 'string' ? attributes.minlength : null,
          required: attributes.required === true || attributes.required === 'required',
          value: typeof attributes.value === 'string' ? attributes.value : null
        });
      }

      // Fallback: if page is JS-rendered and no input tags are server-rendered, provide usable defaults.
      const hasEmailField = fields.some((f) => f?.name === 'email' || f?.type === 'email');
      const hasPasswordField = fields.some((f) => f?.name === 'password' || f?.type === 'password');
      if (!hasEmailField) {
        fields.push({
          name: 'email',
          id: null,
          type: 'email',
          placeholder: null,
          autocomplete: 'email',
          minlength: null,
          required: true,
          value: null
        });
      }
      if (!hasPasswordField) {
        fields.push({
          name: 'password',
          id: null,
          type: 'password',
          placeholder: null,
          autocomplete: 'current-password',
          minlength: null,
          required: true,
          value: null
        });
      }
      
      // Extract XSRF token from hidden input
      const xsrfToken = extractFirst([
        /<input[^>]*name=(?:"xsrf"|'xsrf')[^>]*value=(?:"([^"]+)"|'([^']+)')/i,
        /(?:^|[,{])\s*"xsrf"\s*:\s*"([^"]+)"/i
      ]);
      
      // Extract CAPTCHA token from hidden input (if present)
      const captchaToken = extractFirst([
        /<input[^>]*name=(?:"captcha"|'captcha')[^>]*value=(?:"([^"]+)"|'([^']+)')/i,
        /(?:^|[,{])\s*"captcha"\s*:\s*"([^"]+)"/i
      ]);
      
      // Check if captcha_clicked is true or false
      const captchaClickedRaw = extractFirst([
        /<input[^>]*name=(?:"captcha_clicked"|'captcha_clicked')[^>]*value=(?:"([^"]+)"|'([^']+)')/i,
        /(?:^|[,{])\s*"captcha_clicked"\s*:\s*"([^"]+)"/i
      ]);
      const captchaClicked = String(captchaClickedRaw || '').toLowerCase() === 'true';
      
      // Check if CAPTCHA is required (look for private-captcha element or captcha input)
      const requiresCaptcha = html.includes('private-captcha')
        || html.includes('name="captcha"')
        || html.includes("name='captcha'")
        || /captcha/i.test(html);

      // Extract CAPTCHA metadata used by frontend
      const captchaSiteKey = extractFirst([
        /data-sitekey=(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i,
        /(?:^|[,{])\s*"sitekey"\s*:\s*"([^"]+)"/i,
        /(?:^|[,{])\s*"captchaSiteKey"\s*:\s*"([^"]+)"/i
      ]);
      const captchaPuzzleEndpoint = extractFirst([
        /data-puzzle-endpoint=(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i,
        /(?:^|[,{])\s*"puzzle_endpoint"\s*:\s*"([^"]+)"/i,
        /(?:^|[,{])\s*"captchaPuzzleEndpoint"\s*:\s*"([^"]+)"/i
      ]);
      
      console.log('[myTischtennisClient.getLoginPage]', {
        hasXsrfToken: !!xsrfToken,
        hasCaptchaToken: !!captchaToken,
        captchaClicked,
        requiresCaptcha,
        fieldsCount: fields.length,
        hasCaptchaSiteKey: !!captchaSiteKey,
        hasCaptchaPuzzleEndpoint: !!captchaPuzzleEndpoint
      });
      
      return {
        success: true,
        loginAction,
        fields,
        xsrfToken,
        captchaToken,
        captchaClicked,
        requiresCaptcha,
        captchaSiteKey,
        captchaPuzzleEndpoint
      };
    } catch (error) {
      console.error('Error fetching login page:', error.message);
      return {
        success: false,
        error: error.message
      };
    }
  }

  /**
   * Login to myTischtennis API
   * @param {string} email - myTischtennis email (not username!)
   * @param {string} password - myTischtennis password
   * @param {string} captchaToken - Optional CAPTCHA token if required
   * @param {string} xsrfToken - Optional XSRF token (will be fetched if not provided)
   * @returns {Promise<Object>} Login response with token and session data
   */
  async login(email, password, captchaToken = null, xsrfToken = null) {
    try {
      let loginPage = null;
      let captchaClicked = false;
      
      // If XSRF token not provided, fetch login page to get it
      if (!xsrfToken) {
        loginPage = await this.getLoginPage();
        if (!loginPage.success) {
          return {
            success: false,
            error: 'Konnte Login-Seite nicht abrufen: ' + loginPage.error
          };
        }
        xsrfToken = loginPage.xsrfToken;
        
        // If CAPTCHA token not provided but found in HTML, use it
        if (!captchaToken && loginPage.captchaToken) {
          captchaToken = loginPage.captchaToken;
          captchaClicked = loginPage.captchaClicked;
          console.log('[myTischtennisClient.login] CAPTCHA-Token aus HTML extrahiert, captcha_clicked:', captchaClicked);
        }
        
        // If CAPTCHA is required but no token found yet, wait and try to get it again
        // Das CAPTCHA-System löst das Puzzle im Hintergrund via JavaScript, daher kann es einen Moment dauern
        // Wir müssen mehrmals versuchen, da das Token erst generiert wird, nachdem das JavaScript gelaufen ist
        if (loginPage.requiresCaptcha && !captchaToken) {
          console.log('[myTischtennisClient.login] CAPTCHA erforderlich, aber noch kein Token gefunden. Warte und versuche erneut...');
          
          // Versuche bis zu 5 Mal, das CAPTCHA-Token zu erhalten
          let maxRetries = 5;
          let retryCount = 0;
          let foundToken = false;
          
          while (retryCount < maxRetries && !foundToken) {
            // Warte 2-4 Sekunden zwischen den Versuchen
            const waitMs = Math.floor(Math.random() * 2000) + 2000; // 2000-4000ms
            console.log(`[myTischtennisClient.login] Versuch ${retryCount + 1}/${maxRetries}: Warte ${waitMs}ms...`);
            await new Promise(resolve => setTimeout(resolve, waitMs));
            
            // Versuche erneut, die Login-Seite abzurufen, um das gelöste CAPTCHA-Token zu erhalten
            const retryLoginPage = await this.getLoginPage();
            if (retryLoginPage.success && retryLoginPage.captchaToken) {
              captchaToken = retryLoginPage.captchaToken;
              captchaClicked = retryLoginPage.captchaClicked;
              xsrfToken = retryLoginPage.xsrfToken || xsrfToken; // Aktualisiere XSRF-Token falls nötig
              foundToken = true;
              console.log(`[myTischtennisClient.login] CAPTCHA-Token nach ${retryCount + 1} Versuchen gefunden, captcha_clicked:`, captchaClicked);
            } else {
              retryCount++;
            }
          }
          
          if (!foundToken) {
            // Wenn nach allen Versuchen kein Token gefunden wurde, Fehler zurückgeben
            console.log('[myTischtennisClient.login] CAPTCHA-Token konnte nach mehreren Versuchen nicht gefunden werden');
            return {
              success: false,
              error: 'CAPTCHA erforderlich. Bitte lösen Sie das CAPTCHA auf der MyTischtennis-Website.',
              requiresCaptcha: true
            };
          }
        }
        
        // Zufällige Verzögerung von 2-5 Sekunden zwischen Laden des Forms und Absenden
        // Simuliert menschliches Verhalten und gibt dem CAPTCHA-System Zeit
        const delayMs = Math.floor(Math.random() * 3000) + 2000; // 2000-5000ms
        console.log(`[myTischtennisClient] Warte ${delayMs}ms vor Login-Request (simuliert menschliches Verhalten)`);
        await new Promise(resolve => setTimeout(resolve, delayMs));
      }

      // Create form data
      const formData = new URLSearchParams();
      formData.append('email', email);
      formData.append('password', password);
      formData.append('intent', 'login');
      
      if (xsrfToken) {
        formData.append('xsrf', xsrfToken);
      }
      
      if (captchaToken) {
        formData.append('captcha', captchaToken);
        formData.append('captcha_clicked', captchaClicked ? 'true' : 'false');
      }

      const response = await this.client.post(
        '/login?next=%2F&_data=routes%2F_auth%2B%2Flogin',
        formData.toString()
      );

      // Extract the cookie from response headers
      const setCookie = response.headers['set-cookie'];
      if (!setCookie || !Array.isArray(setCookie)) {
        return {
          success: false,
          error: 'Keine Session-Cookie erhalten'
        };
      }

      // Find the sb-10-auth-token cookie
      const authCookie = setCookie.find(cookie => cookie.startsWith('sb-10-auth-token='));
      if (!authCookie) {
        return {
          success: false,
          error: 'Kein Auth-Token in Response gefunden'
        };
      }

      // Extract and decode the token
      const tokenMatch = authCookie.match(/sb-10-auth-token=base64-([^;]+)/);
      if (!tokenMatch) {
        return {
          success: false,
          error: 'Token-Format ungültig'
        };
      }

      const base64Token = tokenMatch[1];
      let tokenData;
      try {
        const decodedToken = Buffer.from(base64Token, 'base64').toString('utf-8');
        tokenData = JSON.parse(decodedToken);
      } catch (decodeError) {
        console.error('Error decoding token:', decodeError);
        return {
          success: false,
          error: 'Token konnte nicht dekodiert werden'
        };
      }

      return {
        success: true,
        accessToken: tokenData.access_token,
        refreshToken: tokenData.refresh_token,
        expiresAt: tokenData.expires_at,
        expiresIn: tokenData.expires_in,
        user: tokenData.user,
        cookie: authCookie.split(';')[0] // Just the cookie value without attributes
      };
    } catch (error) {
      const statusCode = error.response?.status || 500;
      const responseData = error.response?.data;
      
      // Check if response contains CAPTCHA error
      let errorMessage = error.response?.data?.message || error.message || 'Login fehlgeschlagen';
      let requiresCaptcha = false;
      
      // Check for CAPTCHA-related errors in response
      if (typeof responseData === 'string') {
        if (responseData.includes('Captcha') || responseData.includes('CAPTCHA') || 
            responseData.includes('captcha') || responseData.includes('Captcha-Bestätigung')) {
          requiresCaptcha = true;
          errorMessage = 'CAPTCHA erforderlich. Bitte lösen Sie das CAPTCHA auf der MyTischtennis-Website.';
        }
      } else if (responseData && typeof responseData === 'object') {
        // Check for CAPTCHA errors in JSON response or HTML
        const dataString = JSON.stringify(responseData);
        if (dataString.includes('Captcha') || dataString.includes('CAPTCHA') || 
            dataString.includes('captcha') || dataString.includes('Captcha-Bestätigung')) {
          requiresCaptcha = true;
          errorMessage = 'CAPTCHA erforderlich. Bitte lösen Sie das CAPTCHA auf der MyTischtennis-Website.';
        }
      }
      
      console.error('MyTischtennis login error:', errorMessage, `(Status: ${statusCode})`, requiresCaptcha ? '(CAPTCHA erforderlich)' : '');
      return {
        success: false,
        error: errorMessage,
        status: statusCode,
        requiresCaptcha
      };
    }
  }

  /**
   * Browser-based fallback login for CAPTCHA flows.
   * @param {string} email
   * @param {string} password
   * @param {Object} [options]
   * @param {Object} [options.savedStorageState] - Playwright storage state from a previous session.
   *   If provided and the stored auth cookie is still valid, returns immediately without a new login.
   * @returns {Promise<Object>} Login response with token, session data, and `storageState` for persistence.
   */
  async loginWithBrowserAutomation(email, password, options = {}) {
    const { savedStorageState } = options;
    let browser = null;
    let context = null;

    // --- Fast path: restore a saved Playwright session ---
    if (savedStorageState) {
      try {
        browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
        context = await browser.newContext({ storageState: savedStorageState });
        const cookies = await context.cookies('https://www.mytischtennis.de');
        const authCookie = cookies.find((c) => c.name === 'sb-10-auth-token' || /^sb-\d+-auth-token$/.test(c.name));
        if (authCookie?.value) {
          const tokenMatch = String(authCookie.value).match(/^base64-(.+)$/);
          if (tokenMatch) {
            const tokenData = JSON.parse(Buffer.from(tokenMatch[1], 'base64').toString('utf-8'));
            const nowSec = Math.floor(Date.now() / 1000);
            // Accept if not expired (with 5-minute safety buffer)
            if (tokenData.expires_at && tokenData.expires_at > nowSec + 300) {
              console.log('[myTischtennisClient.playwright] Restored session from saved state (no CAPTCHA needed)');
              const storageState = await context.storageState();
              await context.close();
              await browser.close();
              browser = null; context = null;
              return {
                success: true,
                accessToken: tokenData.access_token,
                refreshToken: tokenData.refresh_token,
                expiresAt: tokenData.expires_at,
                expiresIn: tokenData.expires_in,
                user: tokenData.user,
                cookie: `sb-10-auth-token=${authCookie.value}`,
                storageState,
                restoredFromCache: true
              };
            }
          }
        }
        // Cookie absent or expired → close and fall through to full login
        console.log('[myTischtennisClient.playwright] Saved session expired or invalid, starting full login');
        await context.close();
        await browser.close();
        browser = null; context = null;
      } catch (restoreErr) {
        console.warn('[myTischtennisClient.playwright] Session restore failed, starting full login:', restoreErr.message);
        try { if (context) await context.close(); } catch (_e) { /* ignore */ }
        try { if (browser) await browser.close(); } catch (_e) { /* ignore */ }
        browser = null; context = null;
      }
    }

    try {
      console.log('[myTischtennisClient.playwright] Start browser login flow');
      browser = await chromium.launch({
        headless: true,
        args: ['--no-sandbox', '--disable-dev-shm-usage']
      });
      context = await browser.newContext();
      const page = await context.newPage();

      // Helper: click the CMP/consent "Akzeptieren" button if visible.
      // Tries multiple selectors to cover different CMP implementations.
      const acceptConsentDialog = async (waitMs = 0) => {
        if (waitMs > 0) await page.waitForTimeout(waitMs);
        const consentSelectors = [
          '#onetrust-accept-btn-handler',
          'button:has-text("Alle akzeptieren")',
          'button:has-text("Akzeptieren")',
          'button:has-text("Einverstanden")',
          'button:has-text("Zustimmen")',
          '[data-testid="accept-button"]',
          '.cmp-accept-all',
          '.accept-all-btn'
        ];
        for (const selector of consentSelectors) {
          try {
            const button = page.locator(selector).first();
            if (await button.count()) {
              await button.click({ timeout: 2500 });
              console.log('[myTischtennisClient.playwright] Consent dialog accepted via:', selector);
              await page.waitForTimeout(800);
              return true;
            }
          } catch (_e) {
            // try next selector
          }
        }
        return false;
      };

      // Visit the homepage first so the browser receives and stores the correct CMP
      // consent cookies (the TCF v2 format cannot be guessed and set manually).
      // After accepting consent here, the login page will not show the banner again.
      try {
        await page.goto(this.baseURL, { waitUntil: 'domcontentloaded', timeout: 30000 });
        const acceptedOnHome = await acceptConsentDialog(0);
        if (!acceptedOnHome) await acceptConsentDialog(2500);
        console.log('[myTischtennisClient.playwright] Homepage visited, consent handled');
      } catch (_homeErr) {
        console.log('[myTischtennisClient.playwright] Homepage pre-visit failed (continuing):', _homeErr.message);
      }

      await page.goto(`${this.baseURL}/login?next=%2F`, { waitUntil: 'domcontentloaded', timeout: 45000 });
      console.log('[myTischtennisClient.playwright] Login page loaded');

      // Second consent attempt in case it re-appears on the login page.
      const consentOnLogin = await acceptConsentDialog(0);
      if (!consentOnLogin) await acceptConsentDialog(1500);

      // Fill credentials
      await page.locator('input[name="email"]').first().fill(email, { timeout: 10000 });
      await page.locator('input[name="password"]').first().fill(password, { timeout: 10000 });
      console.log('[myTischtennisClient.playwright] Credentials filled');

      // Try to interact with private-captcha if present (it may render with delay).
      try {
        await page.waitForSelector('private-captcha', { timeout: 8000 });
      } catch (_e) {
        // ignore: captcha host might not be present in all flows
      }
      const captchaHost = page.locator('private-captcha').first();
      const hasCaptchaHost = (await captchaHost.count()) > 0;
      let captchaReadyDetected = !hasCaptchaHost;
      if (hasCaptchaHost) {
        try {
          await page.waitForTimeout(1200);
          const captchaVisualStateBefore = await page.evaluate(() => {
            const host = document.querySelector('private-captcha');
            const checkbox = host?.shadowRoot?.querySelector('#pc-checkbox');
            return {
              hostClass: host?.className || null,
              hostDataState: host?.getAttribute?.('data-state') || null,
              checkboxClass: checkbox?.className || null,
              checkboxChecked: !!checkbox?.checked,
              checkboxAriaChecked: checkbox?.getAttribute?.('aria-checked') || null
            };
          });
          const interaction = await page.evaluate(() => {
            const host = document.querySelector('private-captcha');
            const checkbox = host?.shadowRoot?.querySelector('#pc-checkbox');
            if (!checkbox) {
              return { clicked: false, reason: 'checkbox-missing' };
            }
            checkbox.click();
            checkbox.dispatchEvent(new Event('input', { bubbles: true }));
            checkbox.dispatchEvent(new Event('change', { bubbles: true }));
            return {
              clicked: true,
              viaShadowRoot: true,
              className: checkbox.className || null,
              checked: !!checkbox.checked
            };
          });
          console.log('[myTischtennisClient.playwright] evaluate interaction result:', interaction);

          // Wait until hidden captcha fields are populated by site scripts.
          try {
            await page.waitForFunction(() => {
              const captchaField = document.querySelector('input[name="captcha"]');
              const clickedField = document.querySelector('input[name="captcha_clicked"]');
              const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : '');
              const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : '');
              return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1');
            }, { timeout: 20000 });
            const captchaState = await page.evaluate(() => {
              const captchaField = document.querySelector('input[name="captcha"]');
              const clickedField = document.querySelector('input[name="captcha_clicked"]');
              return {
                captchaLen: captchaField?.value?.length || 0,
                captchaClicked: clickedField?.value || null
              };
            });
            console.log('[myTischtennisClient.playwright] Captcha value ready:', captchaState);
            captchaReadyDetected = true;
          } catch (_waitErr) {
            // Keep going; some flows still succeed without explicit hidden field update.
            console.warn('[myTischtennisClient.playwright] Captcha value not ready in time');
          }

          // Optional diagnostic only: visual state change should never block submit.
          try {
            await page.waitForFunction((beforeState) => {
              const host = document.querySelector('private-captcha');
              const checkbox = host?.shadowRoot?.querySelector('#pc-checkbox');
              if (!host || !checkbox) return false;

              const current = {
                hostClass: host.className || '',
                hostDataState: host.getAttribute?.('data-state') || '',
                checkboxClass: checkbox.className || '',
                checkboxChecked: !!checkbox.checked,
                checkboxAriaChecked: checkbox.getAttribute?.('aria-checked') || ''
              };

              const visualChanged =
                current.hostClass !== (beforeState?.hostClass || '')
                || current.hostDataState !== (beforeState?.hostDataState || '')
                || current.checkboxClass !== (beforeState?.checkboxClass || '')
                || current.checkboxChecked !== !!beforeState?.checkboxChecked
                || current.checkboxAriaChecked !== (beforeState?.checkboxAriaChecked || '');

              return visualChanged;
            }, captchaVisualStateBefore, { timeout: 1500 });
            console.log('[myTischtennisClient.playwright] Captcha visual state changed');
          } catch (_visualWaitErr) {
            // no-op: widget often keeps "ready" class despite solved token
          }
        } catch (captchaError) {
          console.warn('[myTischtennisClient.playwright] Captcha interaction warning:', captchaError?.message || captchaError);
        }
      }

      // Ensure captcha_clicked field is set if available.
      await page.evaluate(() => {
        const clickedField = document.querySelector('input[name="captcha_clicked"]');
        if (clickedField && !clickedField.value) {
          clickedField.value = 'true';
        }
      });

      // Before submit, ensure CAPTCHA fields are actually ready if captcha widget exists.
      if (hasCaptchaHost) {
        const isCaptchaReadyNow = await page.evaluate(() => {
          const captchaField = document.querySelector('input[name="captcha"]');
          const clickedField = document.querySelector('input[name="captcha_clicked"]');
          const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : '');
          const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : '');
          return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1');
        });
        captchaReadyDetected = captchaReadyDetected || isCaptchaReadyNow;

        if (!isCaptchaReadyNow) {
          try {
            await page.waitForFunction(() => {
              const captchaField = document.querySelector('input[name="captcha"]');
              const clickedField = document.querySelector('input[name="captcha_clicked"]');
              const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : '');
              const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : '');
              return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1');
            }, { timeout: 12000 });
            captchaReadyDetected = true;
          } catch (_captchaNotReadyErr) {
            return {
              success: false,
              error: 'Playwright-Login fehlgeschlagen: CAPTCHA wurde im Browser nicht als gelöst erkannt'
            };
          }
        }
      }

      // Human-like pause only after captcha was actually solved (2-6s).
      if (captchaReadyDetected) {
        const postCaptchaDelayMs = 2000 + Math.floor(Math.random() * 4001);
        await page.waitForTimeout(postCaptchaDelayMs);
        console.log('[myTischtennisClient.playwright] Waited after solved captcha:', postCaptchaDelayMs);
      }

      // Ensure login intent is present and click the explicit login submit button.
      await page.evaluate(() => {
        const form = document.querySelector('form[action*="/login"]');
        if (!form) return;
        let intentField = form.querySelector('input[name="intent"]');
        if (!intentField) {
          intentField = document.createElement('input');
          intentField.setAttribute('type', 'hidden');
          intentField.setAttribute('name', 'intent');
          form.appendChild(intentField);
        }
        intentField.setAttribute('value', 'login');
      });

      // Submit form
      const loginSubmitButton = page.locator('button[type="submit"][name="intent"][value="login"]').first();
      const genericSubmitButton = page.locator('button[type="submit"], input[type="submit"]').first();
      if (await loginSubmitButton.count()) {
        await loginSubmitButton.click({ timeout: 15000, noWaitAfter: true });
      } else if (await genericSubmitButton.count()) {
        await genericSubmitButton.click({ timeout: 15000, noWaitAfter: true });
      } else {
        await page.keyboard.press('Enter');
      }
      console.log('[myTischtennisClient.playwright] Submit clicked');

      // Wait for auth cookie after submit (polling avoids timing races).
      let authCookieObj = null;
      let detectedSubmitError = null;
      const pollIntervalMs = 500;
      const maxAttempts = 40; // ~20s max wait after submit
      for (let attempt = 0; attempt < maxAttempts; attempt++) {
        const cookies = await context.cookies();
        authCookieObj = cookies.find((c) => c.name === 'sb-10-auth-token')
          || cookies.find((c) => /^sb-\d+-auth-token$/.test(c.name))
          || cookies.find((c) => c.name.includes('auth-token'));
        if (authCookieObj?.value) {
          console.log('[myTischtennisClient.playwright] Auth cookie detected:', authCookieObj.name);
          break;
        }

        // Periodically: dismiss consent banner (may reappear after submit redirect)
        // and probe page text to fail fast on known error strings.
        if (attempt % 4 === 0) {
          try { await acceptConsentDialog(0); } catch (_e) { /* ignore */ }
          try {
            const textContent = await page.locator('body').innerText({ timeout: 600 });
            if (textContent?.includes('Captcha-Bestätigung fehlgeschlagen')) {
              detectedSubmitError = 'Captcha-Bestätigung fehlgeschlagen';
              break;
            }
            if (textContent?.includes('Captcha-Bestätigung ist erforderlich')) {
              detectedSubmitError = 'Captcha-Bestätigung ist erforderlich';
              break;
            }
            if (textContent?.includes('Ungültige E-Mail oder Passwort')) {
              detectedSubmitError = 'Ungültige E-Mail oder Passwort';
              break;
            }
          } catch (_readBodyErr) {
            // ignore text read errors during polling
          }
        }

        await page.waitForTimeout(pollIntervalMs);
      }
      if (!authCookieObj || !authCookieObj.value) {
        let errorText = null;
        let failureDiagnostics = null;
        try {
          const textContent = await page.locator('body').innerText({ timeout: 1000 });
          if (textContent?.includes('Captcha-Bestätigung fehlgeschlagen')) {
            errorText = 'Captcha-Bestätigung fehlgeschlagen';
          }
          if (!errorText && textContent?.includes('Passwort')) {
            errorText = 'Login vermutlich fehlgeschlagen (Passwort oder CAPTCHA)';
          }

          const currentUrl = page.url();
          const allCookies = await context.cookies();
          const cookieNames = allCookies.map((c) => c.name);
          failureDiagnostics = {
            url: currentUrl,
            cookieNames,
            bodyPreview: String(textContent || '').slice(0, 320)
          };
        } catch (_e) {
          // ignore text read errors
        }
        if (!errorText && detectedSubmitError) {
          errorText = detectedSubmitError;
        }
        if (failureDiagnostics) {
          console.warn('[myTischtennisClient.playwright] Login failure diagnostics:', failureDiagnostics);
        }
        return {
          success: false,
          error: errorText
            ? `Playwright-Login fehlgeschlagen: ${errorText}`
            : 'Playwright-Login fehlgeschlagen: Kein sb-10-auth-token Cookie gefunden'
        };
      }

      // Cookie value is expected as "base64-<tokenData>"
      const tokenMatch = String(authCookieObj.value).match(/^base64-(.+)$/);
      if (!tokenMatch) {
        return {
          success: false,
          error: 'Playwright-Login fehlgeschlagen: Token-Format ungültig'
        };
      }

      let tokenData;
      try {
        tokenData = JSON.parse(Buffer.from(tokenMatch[1], 'base64').toString('utf-8'));
      } catch (decodeError) {
        return {
          success: false,
          error: `Playwright-Login fehlgeschlagen: Token konnte nicht dekodiert werden (${decodeError.message})`
        };
      }

      const cookie = `sb-10-auth-token=${authCookieObj.value}`;

      // Persist the full browser storage state so future calls can skip the CAPTCHA flow.
      let storageState = null;
      try { storageState = await context.storageState(); } catch (_e) { /* ignore */ }

      console.log('[myTischtennisClient.playwright] Browser login successful');
      return {
        success: true,
        accessToken: tokenData.access_token,
        refreshToken: tokenData.refresh_token,
        expiresAt: tokenData.expires_at,
        expiresIn: tokenData.expires_in,
        user: tokenData.user,
        cookie,
        storageState
      };
    } catch (error) {
      const rawMessage = String(error?.message || error || 'Playwright-Login fehlgeschlagen');
      const isMissingBrowserExecutable = /Executable doesn't exist|download new browsers|playwright install/i.test(rawMessage);
      const normalizedError = isMissingBrowserExecutable
        ? 'Playwright-Browser ist auf dem Server nicht installiert. Bitte "npx playwright install chromium" ausführen.'
        : rawMessage;
      console.error('[myTischtennisClient.playwright] Browser login failed:', normalizedError);
      return {
        success: false,
        error: normalizedError,
        requiresSetup: isMissingBrowserExecutable,
        status: isMissingBrowserExecutable ? 503 : 400
      };
    } finally {
      if (context) {
        try {
          await context.close();
        } catch (contextCloseError) {
          console.warn('[myTischtennisClient.playwright] Context close warning:', contextCloseError?.message || contextCloseError);
        }
      }
      if (browser) {
        try {
          await browser.close();
        } catch (browserCloseError) {
          console.warn('[myTischtennisClient.playwright] Browser close warning:', browserCloseError?.message || browserCloseError);
        }
        console.log('[myTischtennisClient.playwright] Browser closed');
      }
    }
  }

  /**
   * Verify login credentials
   * @param {string} email - myTischtennis email
   * @param {string} password - myTischtennis password
   * @returns {Promise<boolean>} True if credentials are valid
   */
  async verifyCredentials(email, password) {
    const result = await this.login(email, password);
    return result.success;
  }

  /**
   * Make an authenticated request
   * @param {string} endpoint - API endpoint
   * @param {string} cookie - Authentication cookie (sb-10-auth-token)
   * @param {Object} options - Additional axios options
   * @returns {Promise<Object>} API response
   */
  async authenticatedRequest(endpoint, cookie, options = {}) {
    try {
      const response = await this.client.request({
        url: endpoint,
        ...options,
        headers: {
          ...options.headers,
          'Cookie': cookie,
          'Accept': '*/*',
          'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
          'Referer': 'https://www.mytischtennis.de/',
          'sec-fetch-dest': 'empty',
          'sec-fetch-mode': 'cors',
          'sec-fetch-site': 'same-origin'
        }
      });
      return {
        success: true,
        data: response.data
      };
    } catch (error) {
      console.error('MyTischtennis API error:', error.message);
      return {
        success: false,
        error: error.response?.data?.message || 'API-Anfrage fehlgeschlagen',
        status: error.response?.status || 500
      };
    }
  }

  /**
   * Get user profile and club information
   * @param {string} cookie - Authentication cookie (sb-10-auth-token)
   * @returns {Promise<Object>} User profile with club info
   */
  async getUserProfile(cookie) {
    
    const result = await this.authenticatedRequest('/?_data=root', cookie, {
      method: 'GET'
    });
    
    
    if (result.success) {
      
      return {
        success: true,
        clubId: result.data?.userProfile?.club?.clubnr || null,
        clubName: result.data?.userProfile?.club?.name || null,
        fedNickname: result.data?.userProfile?.organization?.short || null,
        ttr: result.data?.userProfile?.ttr || null,
        qttr: result.data?.userProfile?.qttr || null,
        userProfile: result.data?.userProfile || null
      };
    }
    
    console.error('[getUserProfile] - Failed:', result.error);
    return result;
  }

  /**
   * Get club rankings (andro-Rangliste)
   * @param {string} cookie - Authentication cookie
   * @param {string} clubId - Club number (e.g., "43030")
   * @param {string} fedNickname - Federation nickname (e.g., "HeTTV")
   * @returns {Promise<Object>} Rankings with player entries (all pages)
   */
  async getClubRankings(cookie, clubId, fedNickname, currentRanking = 'yes', options = {}) {
    const { includeHistoryPlayerIds = false } = options;
    const allEntries = [];
    let currentPage = 0;
    let hasMorePages = true;
    
    
    while (hasMorePages) {
      const endpoint = `/rankings/andro-rangliste?all-players=on&clubnr=${clubId}&fednickname=${fedNickname}&current-ranking=${currentRanking}&results-per-page=100&page=${currentPage}&_data=routes%2F%24`;
      const result = await this.authenticatedRequest(endpoint, cookie, {
        method: 'GET'
      });
      
      if (!result.success) {
        console.error(`[getClubRankings] - Failed to fetch page ${currentPage}:`, result.error);
        return result;
      }
      
      // Find the dynamic key that contains entries
      const blockLoaderData = result.data?.pageContent?.blockLoaderData;
      if (!blockLoaderData) {
        console.error('[getClubRankings] - No blockLoaderData found');
        return {
          success: false,
          error: 'Keine blockLoaderData gefunden'
        };
      }
      
      // Finde den Schlüssel, der entries enthält
      let entries = null;
      let rankingData = null;
      
      for (const key in blockLoaderData) {
        if (blockLoaderData[key]?.entries) {
          entries = blockLoaderData[key].entries;
          rankingData = blockLoaderData[key];
          break;
        }
      }
      
      if (!entries) {
        console.error('[getClubRankings] - No entries found in blockLoaderData');
        return {
          success: false,
          error: 'Keine entries in blockLoaderData gefunden'
        };
      }

      let historyPlayerIdsByName = null;
      if (includeHistoryPlayerIds) {
        const htmlEndpoint = `/rankings/andro-rangliste?clubnr=${clubId}&fednickname=${fedNickname}&all-players=on&continent=all&country=all&current-ranking=${currentRanking}&results-per-page=100&page=${currentPage + 1}`;
        const htmlResult = await this.authenticatedRequest(htmlEndpoint, cookie, {
          method: 'GET',
          headers: {
            Accept: 'text/html,application/xhtml+xml'
          }
        });
        historyPlayerIdsByName = htmlResult.success
          ? this.extractHistoryPlayerIdsFromAndroRankingHtml(htmlResult.data)
          : new Map();
      }

      const enrichedEntries = entries.map((entry) => {
        const nameKey = this._buildRankingNameKey(entry?.firstname, entry?.lastname);
        const historyPlayerId = historyPlayerIdsByName?.get(nameKey) || null;
        return {
          ...entry,
          historyPlayerId,
          myTischtennisHistoryPlayerId: historyPlayerId
        };
      });
      
      
      // Füge Entries hinzu
      allEntries.push(...enrichedEntries);
      
      // Prüfe ob es weitere Seiten gibt
      // Wenn die aktuelle Seite weniger Einträge hat als das Limit, sind wir am Ende
      // Oder wenn wir alle erwarteten Einträge haben
      if (enrichedEntries.length === 0) {
        hasMorePages = false;
      } else if (rankingData.numberOfPages && currentPage >= rankingData.numberOfPages - 1) {
        hasMorePages = false;
      } else if (allEntries.length >= rankingData.resultLength) {
        hasMorePages = false;
      } else {
        currentPage++;
      }
    }
    
    
    return {
      success: true,
      entries: allEntries,
      metadata: {
        totalEntries: allEntries.length,
        pagesFetched: currentPage + 1
      }
    };
  }

  extractHistoryPlayerIdsFromAndroRankingHtml(html) {
    const result = new Map();
    const source = typeof html === 'string' ? html : String(html || '');
    const anchorPattern = /href="\/community\/external-profile\?player-id=(P[A-Z0-9]+)"[^>]*>([^<]+)<\/a>/gi;

    let match = null;
    while ((match = anchorPattern.exec(source)) !== null) {
      const playerId = match[1];
      const fullName = this._decodeHtmlEntities(match[2] || '');
      const key = this._buildRankingFullNameKey(fullName);
      if (key && playerId && !result.has(key)) {
        result.set(key, playerId);
      }
    }

    return result;
  }

  _buildRankingNameKey(firstname, lastname) {
    return this._buildRankingFullNameKey(`${firstname || ''} ${lastname || ''}`);
  }

  _buildRankingFullNameKey(name) {
    return String(name || '')
      .normalize('NFKC')
      .replace(/\s+/g, ' ')
      .trim()
      .toLowerCase();
  }

  _decodeHtmlEntities(value) {
    return String(value || '')
      .replace(/&amp;/g, '&')
      .replace(/&quot;/g, '"')
      .replace(/&#39;/g, "'")
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>');
  }
}

export default new MyTischtennisClient();
