// ===== PLUGGABLE AUTH GATE (DC-049) =====
//
// On Caddy redirect to ?auth=required, this module queries
// GET /api/v1/auth/login/methods to discover which AuthProviders are enabled.
// If only one provider is enabled, jump straight to its challenge UI
// (TOTP-only installations today). If multiple providers exist, render a
// selector first so the user picks how to sign in.
//
// Today:
//   - totp:    challenge is 6-digit code via the existing TOTP overlay
//   - email:   challenge is email address → POST /api/v1/auth/login/email/initiate
//              → server emails magic link (or logs it to console in dev) →
//              waiting for verification (no auto-promotion; user reloads via
//              email link, which routes through /api/v1/auth/login/email/verify)
//
// This module wires into the existing totp-auth.js submitTotpCode() flow so
// the legacy TOTP happy-path is unchanged: when methods returns only
// `totp`, this module just shows the TOTP overlay and exits.

(function() {
  // ---- DOM refs (created lazily below; existing TOTP markup supplies them) ----
  let methodsCache = null;

  async function fetchMethods() {
    if (methodsCache) return methodsCache;
    try {
      const res = await fetch('/api/v1/auth/login/methods', { cache: 'no-store' });
      if (!res.ok) throw new Error(`methods HTTP ${res.status}`);
      const data = await res.json();
      methodsCache = Array.isArray(data.providers) ? data.providers : [];
      return methodsCache;
    } catch (e) {
      // Public endpoint — if it fails, fall through to legacy TOTP path.
      console.warn('[auth-gate] methods fetch failed; falling back to TOTP-only', e);
      return [];
    }
  }

  function showProviderSelector(providers) {
    const overlay = document.getElementById('totp-overlay');
    if (!overlay) return;
    const card = overlay.querySelector('.totp-card');
    if (!card) return;

    // Save the existing TOTP-challenge body so we can restore it on cancel.
    const originalBody = card.innerHTML;
    if (!card.dataset.originalBody) card.dataset.originalBody = originalBody;

    const providerButtons = providers.map(p => {
      const label = (p.config && (p.config.label || p.name)) || p.name;
      const btn = `<button class="provider-btn" data-provider="${p.name}"
                          style="margin: 6px 4px; padding: 10px 16px; background: var(--accent); color: white; border: 0; border-radius: 6px; cursor: pointer; font-size: 0.95rem;">
        Sign in with ${label}
      </button>`;
      return btn;
    }).join('\n');

    card.innerHTML = `
      <img class="totp-logo totp-logo-dark" src="/assets/dashcaddy-logo-dark.png" alt="DashCaddy" onerror="this.style.display='none'">
      <img class="totp-logo totp-logo-light" src="/assets/dashcaddy-logo-light.png" alt="DashCaddy" onerror="this.style.display='none'">
      <p class="subtitle">Choose how to sign in</p>
      <div id="auth-gate-providers" style="margin: 14px 0 10px;">${providerButtons}</div>
      <div id="auth-gate-message" style="margin-top: 8px; color: var(--muted); font-size: 0.8rem;"></div>
    `;

    overlay.classList.add('show');

    // Wire each button
    card.querySelectorAll('.provider-btn').forEach(btn => {
      btn.addEventListener('click', () => {
        const name = btn.dataset.provider;
        const provider = providers.find(p => p.name === name);
        renderProviderChallenge(provider);
      });
    });
  }

  function restoreOriginalBody() {
    const overlay = document.getElementById('totp-overlay');
    if (!overlay) return;
    const card = overlay.querySelector('.totp-card');
    if (!card || !card.dataset.originalBody) return;
    card.innerHTML = card.dataset.originalBody;
    // Re-bind the original digit input handlers by re-running the totp-auth
    // bootstrap. The simplest path: reload the page, which re-runs all IIFEs.
    window.location.reload();
  }

  function renderProviderChallenge(provider) {
    const overlay = document.getElementById('totp-overlay');
    if (!overlay) return;
    const card = overlay.querySelector('.totp-card');
    if (!card) return;

    if (provider.name === 'totp') {
      // Restore the original TOTP markup → existing totp-auth.js submitTotpCode
      // path handles the rest. (Reload is the cleanest path because
      // totp-auth.js wires its input handlers at top-level IIFE time.)
      window.location.reload();
      return;
    }

    if (provider.name === 'email') {
      card.innerHTML = `
        <img class="totp-logo totp-logo-dark" src="/assets/dashcaddy-logo-dark.png" alt="DashCaddy" onerror="this.style.display='none'">
        <img class="totp-logo totp-logo-light" src="/assets/dashcaddy-logo-light.png" alt="DashCaddy" onerror="this.style.display='none'">
        <p class="subtitle">Sign in with email</p>
        <p style="margin: 6px 0 14px; font-size: 0.85rem; color: var(--muted);">
          We'll email you a one-time sign-in link.
        </p>
        <input id="auth-gate-email-input" type="email" inputmode="email" autocomplete="email"
               placeholder="you@example.com"
               style="width: 100%; padding: 10px 12px; font-size: 1rem; background: var(--input-bg); color: var(--text); border: 1px solid var(--border); border-radius: 6px; box-sizing: border-box;">
        <button id="auth-gate-email-submit" class="provider-btn"
                style="margin-top: 14px; padding: 10px 16px; background: var(--accent); color: white; border: 0; border-radius: 6px; cursor: pointer; font-size: 0.95rem; width: 100%;">
          Send sign-in link
        </button>
        <div id="auth-gate-email-status" style="margin-top: 12px; font-size: 0.85rem; line-height: 1.4;"></div>
        <div style="margin-top: 16px;">
          <a href="#" id="auth-gate-back" style="color: var(--muted); font-size: 0.8rem;">← Back</a>
        </div>
      `;
      overlay.classList.add('show');
      const input = card.querySelector('#auth-gate-email-input');
      const submit = card.querySelector('#auth-gate-email-submit');
      const status = card.querySelector('#auth-gate-email-status');
      const back = card.querySelector('#auth-gate-back');

      submit.addEventListener('click', async () => {
        const email = (input.value || '').trim();
        if (!email || !email.includes('@')) {
          status.textContent = 'Enter a valid email address.';
          status.style.color = 'var(--error, #d33)';
          return;
        }
        submit.disabled = true;
        status.textContent = 'Sending…';
        status.style.color = '';
        try {
          const res = await fetch('/api/v1/auth/login/email/initiate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email }),
          });
          const data = await res.json();
          if (data.success) {
            const via = data.deliveredVia || 'email';
            const masked = data.maskedEmail || email;
            if (via === 'dev-console') {
              status.innerHTML = `
                Check the server logs for your one-time link.
                (Dev mode: no SMTP configured. In production this would email <strong>${masked}</strong>.)`;
            } else {
              status.innerHTML = `Sign-in link sent to <strong>${masked}</strong>.
                Check your inbox (and spam folder).`;
            }
            status.style.color = 'var(--success, #2a7)';
          } else {
            status.textContent = data.error || 'Could not send link.';
            status.style.color = 'var(--error, #d33)';
            submit.disabled = false;
          }
        } catch (e) {
          status.textContent = 'Connection error. Try again.';
          status.style.color = 'var(--error, #d33)';
          submit.disabled = false;
        }
      });

      input.addEventListener('keydown', e => {
        if (e.key === 'Enter') submit.click();
      });

      back.addEventListener('click', e => {
        e.preventDefault();
        showProviderSelector(providers);
      });
      return;
    }

    // Unknown provider → restore TOTP path as a last resort
    console.warn('[auth-gate] unknown provider', provider.name);
    window.location.reload();
  }

  async function show() {
    const overlay = document.getElementById('totp-overlay');
    if (!overlay) return; // TOTP-only build without our overlay changes

    const providers = await fetchMethods();
    if (providers.length === 0) {
      // Either the endpoint isn't reachable OR no provider reports enabled.
      // Fall back to the legacy TOTP overlay — existing totp-auth.js handles it.
      if (typeof window._showTotpOverlay === 'function') window._showTotpOverlay();
      return;
    }
    if (providers.length === 1 && providers[0].name === 'totp') {
      // Single TOTP provider → show the original TOTP overlay unchanged,
      // but with an "Or sign in with email" link below so the email path
      // is reachable as the recovery / phone-friendly alternative. Most
      // users still want their primary method (TOTP) front-and-center.
      showTotpWithEmailFallback(providers[0]);
      return;
    }
    showProviderSelector(providers);
  }

  function showTotpWithEmailFallback(totpProvider) {
    const emailEnabled = methodsCache && methodsCache.find(p => p.name === 'email');
    if (!emailEnabled) {
      // Truly single-provider path: legacy TOTP overlay, no alt link.
      if (typeof window._showTotpOverlay === 'function') window._showTotpOverlay();
      return;
    }
    const overlay = document.getElementById('totp-overlay');
    const card = overlay.querySelector('.totp-card');
    if (!card) return;

    // Save the original TOTP markup so we can restore on alt-link click off.
    if (!card.dataset.originalBody) card.dataset.originalBody = card.innerHTML;

    // Add a small "or" link at the bottom of the existing card WITHOUT
    // touching the TOTP input markup — keeps totp-auth.js's submitTotpCode
    // binding intact.
    let alt = card.querySelector('#auth-gate-email-alt');
    if (!alt) {
      const div = document.createElement('div');
      div.id = 'auth-gate-email-alt';
      div.style.cssText = 'margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); font-size: 0.85rem;';
      div.innerHTML = `<a href="#" id="auth-gate-email-alt-link"
        style="color: var(--accent); text-decoration: none;">
        Or sign in with email instead →
      </a>`;
      card.appendChild(div);
      div.querySelector('#auth-gate-email-alt-link').addEventListener('click', e => {
        e.preventDefault();
        renderProviderChallenge(emailEnabled);
      });
    }
    overlay.classList.add('show');
  }

  // ---- Trigger points ----
  // 1. SSO redirect from Caddy: ?auth=required
  //    We claim ownership here (set window.__dc_049_handled = true)
  //    BEFORE totp-auth.js's own ?auth=required check runs, so the legacy
  //    TOTP-only overlay doesn't flicker in for multi-provider installs.
  //    Single-provider TOTP-only installs work because we still delegate
  //    back to window._showTotpOverlay() in `show()` below.
  window.__dc_049_handled = true;

  function isAllowedReturnUrl(returnUrl) {
    try {
      const parsed = new URL(returnUrl, window.location.origin);
      if (!['http:', 'https:'].includes(parsed.protocol)) return false;
      if (parsed.origin === window.location.origin) return true;
      if (parsed.protocol !== 'https:') return false;

      // globals.js is concatenated before this module in core.js, so SITE is
      // available here. Permit exact hosts and subdomains under the configured
      // private TLD (for example plex.sami), while rejecting lookalikes such as
      // plex.sami.evil.example.
      const suffix = SITE.tld.startsWith('.') ? SITE.tld : `.${SITE.tld}`;
      return parsed.hostname === suffix.slice(1) || parsed.hostname.endsWith(suffix);
    } catch (_) {
      return false;
    }
  }

  function buildSsoHandoffTarget(returnUrl, token) {
    const parsed = new URL(returnUrl, window.location.origin);
    if (parsed.origin === window.location.origin) return parsed.toString();

    const suffix = SITE.tld.startsWith('.') ? SITE.tld : `.${SITE.tld}`;
    const isPrivateHost = parsed.hostname === suffix.slice(1) || parsed.hostname.endsWith(suffix);
    if (parsed.protocol !== 'https:' || !isPrivateHost || !token) return null;

    const returnPath = `${parsed.pathname}${parsed.search}${parsed.hash}`;
    parsed.pathname = '/dashcaddy-sso';
    parsed.search = '';
    parsed.hash = '';
    parsed.searchParams.set('token', token);
    parsed.searchParams.set('return', returnPath);
    return parsed.toString();
  }

  async function resumeExistingSession(returnUrl) {
    if (!returnUrl || !isAllowedReturnUrl(returnUrl)) return false;
    try {
      const parsedReturn = new URL(returnUrl, window.location.origin);
      const suffix = SITE.tld.startsWith('.') ? SITE.tld : `.${SITE.tld}`;
      const serviceId = parsedReturn.hostname.slice(0, -suffix.length);
      if (!/^[a-z0-9][a-z0-9-]*$/.test(serviceId)) return false;
      const res = await fetch(`/api/v1/auth/sso-handoff?serviceId=${encodeURIComponent(serviceId)}`, {
        credentials: 'include',
        cache: 'no-store',
      });
      if (!res.ok) return false;
      const data = await res.json();
      const target = data.success && buildSsoHandoffTarget(returnUrl, data.ssoToken);
      if (!target) return false;
      try { sessionStorage.removeItem('totp_redirect'); } catch (_) {}
      window.location.replace(target);
      return true;
    } catch (_) {
      return false;
    }
  }

  const urlParams = new URLSearchParams(window.location.search);
  if (urlParams.get('auth') === 'required') {
    // Preserve the gated service destination so submitTotpCode() can append
    // the one-time SSO handoff token and return the browser to that host.
    const returnUrl = urlParams.get('return');
    if (returnUrl && isAllowedReturnUrl(returnUrl)) {
      try { sessionStorage.setItem('totp_redirect', returnUrl); } catch (_) {}
    }
    // Clean URL — happens after we've captured the redirect
    window.history.replaceState({}, '', window.location.pathname);
    // Reuse the valid status.sami session first. Only show the TOTP/provider
    // challenge when that session is genuinely absent or expired.
    setTimeout(async () => {
      if (await resumeExistingSession(returnUrl)) return;
      await show();
    }, 0);
  }

  // Expose for hot-trigger from other modules (e.g. logout)
  window._showAuthGate = show;
  window._authGateMethods = fetchMethods;
})();
