/* CA Automator — client portal. Deliberately its own small app, not a mode
   of web/app.js: a client contact is a different credential space entirely
   (client_portal_user, not app_user — see server/db.py's "client portal"
   section), so this never shares a token, a bootstrap payload or a line of
   rendering code with the staff app. */
(function () {
  'use strict';

  const esc = s => String(s == null ? '' : s)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  const money = n => (n === null || n === undefined || n === '' || isNaN(n)) ? '–'
    : Number(n).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });

  const state = {
    token: localStorage.getItem('portal_token') || null,
    user: JSON.parse(localStorage.getItem('portal_user') || 'null'),
  };

  function setSession(token, user) {
    state.token = token; state.user = user;
    localStorage.setItem('portal_token', token);
    localStorage.setItem('portal_user', JSON.stringify(user));
  }
  function clearSession() {
    state.token = null; state.user = null;
    localStorage.removeItem('portal_token'); localStorage.removeItem('portal_user');
  }

  async function api(path, opts) {
    opts = opts || {};
    const headers = Object.assign({ 'Content-Type': 'application/json' }, opts.headers || {});
    if (state.token) headers.Authorization = 'Bearer ' + state.token;
    const res = await fetch(path, {
      method: opts.method || 'GET', headers,
      body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
    });
    if (res.status === 401 && path !== '/api/portal/auth/login') { clearSession(); location.hash = '#/login'; }
    const text = await res.text();
    let json; try { json = text ? JSON.parse(text) : null; } catch (e) { json = null; }
    if (!res.ok) throw new Error((json && json.error) || ('Request failed: ' + res.status));
    return json;
  }

  function downloadBlob(name, blob) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = name; document.body.appendChild(a); a.click();
    setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 500);
  }
  async function downloadAuthed(path, fallbackName) {
    const res = await fetch(path, { headers: { Authorization: 'Bearer ' + state.token } });
    if (!res.ok) { const j = await res.json().catch(() => null); throw new Error((j && j.error) || 'Download failed'); }
    const blob = await res.blob();
    const cd = res.headers.get('Content-Disposition') || '';
    const m = cd.match(/filename="?([^"]+)"?/);
    downloadBlob(m ? m[1] : fallbackName, blob);
  }

  const root = document.getElementById('root');

  function shellHtml(inner) {
    return '<div class="gate"><div class="gate-card">' + inner + '</div></div>';
  }

  const OAUTH_ERROR_MESSAGES = {
    not_configured: "Social sign-in isn't set up yet — use your email and password.",
    invalid_state: 'That sign-in attempt expired — please try again.',
    no_portal_account: "No portal account found for that email — ask your CA firm to invite you first, or sign in with your password.",
    no_verified_email: "That account doesn't have a verified email — please sign in with your password instead.",
    cancelled: 'Sign-in was cancelled.',
    failed: 'Social sign-in failed — please try again or use your password.',
  };
  const OAUTH_PROVIDER_LABELS = { google: 'Google' };

  function renderLogin(message, isError) {
    root.innerHTML = shellHtml(
      '<div class="gate-mark">CA</div><h1>Client Portal</h1>' +
      '<p class="gate-sub">Sign in to see your appointments, invoices and documents.</p>' +
      (message ? '<p class="portal-msg" style="color:' + (isError ? 'var(--crimson)' : 'var(--teal)') + '">' + esc(message) + '</p>' : '') +
      '<div id="pl-social"></div>' +
      '<label>Email<input id="pl-email" type="email" autocomplete="email"></label>' +
      '<label>Password<input id="pl-pass" type="password" autocomplete="current-password"></label>' +
      '<button class="btn btn-primary" id="pl-go" style="width:100%;margin-top:14px">Sign in</button>' +
      '<p class="portal-msg" id="pl-msg"></p>' +
      '<p style="margin-top:14px;font-size:13px"><a href="#/forgot">Forgot your password?</a></p>');
    document.getElementById('pl-go').addEventListener('click', async () => {
      const msg = document.getElementById('pl-msg'); msg.textContent = '';
      const email = document.getElementById('pl-email').value.trim();
      const password = document.getElementById('pl-pass').value;
      if (!email || !password) { msg.textContent = 'Email and password are required.'; return; }
      try {
        const res = await api('/api/portal/auth/login', { method: 'POST', body: { email, password } });
        setSession(res.token, res.user);
        location.hash = '#/';
        route();
      } catch (e) { msg.textContent = e.message; }
    });
    // Stays empty (no buttons, no layout gap) until a firm has real
    // credentials saved in Admin > Site Settings > Social Login — this is
    // a plain <a href> navigation, not a fetch, since OAuth means leaving
    // this page entirely for the provider's own sign-in screen.
    fetch('/api/portal/oauth/providers').then(r => r.json()).then(providers => {
      const holder = document.getElementById('pl-social');
      if (!holder || !providers.length) return;
      holder.innerHTML =
        providers.map(p => '<a class="btn" style="width:100%;margin-bottom:8px;text-align:center;display:block;box-sizing:border-box"' +
          ' href="/api/portal/oauth/' + encodeURIComponent(p.provider) + '/start">Sign in with ' +
          esc(OAUTH_PROVIDER_LABELS[p.provider] || p.label) + '</a>').join('') +
        '<div style="text-align:center;color:var(--ink-30);font-size:11.5px;margin:4px 0 14px">or sign in with email</div>';
    }).catch(() => {});
  }

  function renderForgot() {
    root.innerHTML = shellHtml(
      '<div class="gate-mark">CA</div><h1>Reset password</h1>' +
      '<p class="gate-sub">Enter the email you use to sign in — if it has portal access, we\'ll send a reset link.</p>' +
      '<label>Email<input id="pf-email" type="email" autocomplete="email"></label>' +
      '<button class="btn btn-primary" id="pf-go" style="width:100%;margin-top:14px">Send reset link</button>' +
      '<p class="portal-msg" id="pf-msg"></p>' +
      '<p style="margin-top:14px;font-size:13px"><a href="#/login">Back to sign in</a></p>');
    document.getElementById('pf-go').addEventListener('click', async () => {
      const msg = document.getElementById('pf-msg'); msg.textContent = '';
      const email = document.getElementById('pf-email').value.trim();
      try {
        const res = await api('/api/portal/auth/forgot', { method: 'POST', body: { email } });
        msg.style.color = 'var(--teal)'; msg.textContent = res.message;
      } catch (e) { msg.textContent = e.message; }
    });
  }

  function renderSetPassword(mode, token) {
    const isInvite = mode === 'invite';
    root.innerHTML = shellHtml(
      '<div class="gate-mark">CA</div><h1>' + (isInvite ? 'Set your password' : 'Choose a new password') + '</h1>' +
      '<p class="gate-sub">' + (isInvite ? 'Set a password to activate your client portal access.' : 'At least 8 characters.') + '</p>' +
      '<label>New password<input id="ps-pass" type="password" autocomplete="new-password"></label>' +
      '<button class="btn btn-primary" id="ps-go" style="width:100%;margin-top:14px">' + (isInvite ? 'Activate account' : 'Set password') + '</button>' +
      '<p class="portal-msg" id="ps-msg"></p>');
    document.getElementById('ps-go').addEventListener('click', async () => {
      const msg = document.getElementById('ps-msg'); msg.textContent = '';
      const password = document.getElementById('ps-pass').value;
      if (password.length < 8) { msg.textContent = 'Use at least 8 characters.'; return; }
      try {
        const path = isInvite ? '/api/portal/invite/accept' : '/api/portal/auth/reset';
        const res = await api(path, { method: 'POST', body: { token, password } });
        setSession(res.token, res.user);
        location.hash = '#/';
        route();
      } catch (e) { msg.textContent = e.message; }
    });
  }

  const WEEKDAY = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  const MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  const fmtDate = iso => { if (!iso) return '—'; const d = new Date(iso + 'T00:00:00'); return WEEKDAY[d.getDay()].slice(0, 3) + ' ' + d.getDate() + ' ' + MONTH[d.getMonth()] + ' ' + d.getFullYear(); };

  async function renderDashboard() {
    const paymentResult = new URLSearchParams(location.search).get('payment');
    const paymentBanner = paymentResult
      ? '<div class="card" style="border-color:' + (paymentResult === 'success' ? 'var(--teal)' : 'var(--crimson)') + '">' +
        (paymentResult === 'success'
          ? '<b style="color:var(--teal)">Payment received</b> — thank you, this invoice is now marked paid.'
          : '<b style="color:var(--crimson)">Payment not completed</b> — nothing was charged. You can try again from the invoice below.') +
        '</div>'
      : '';
    root.innerHTML =
      '<nav class="portal-nav"><img class="brand-mark" src="/logo-mark.png" alt="CA Automator"> <b id="pd-firm">Client Portal</b>' +
      '<span class="who">' + esc(state.user.name) + ' · ' + esc(state.user.client_name) + '</span>' +
      '<button class="btn btn-small" id="pd-signout">Sign out</button></nav>' +
      '<div class="portal-main">' +
      '<div class="page-head"><div><h2>Welcome, ' + esc(state.user.name) + '</h2>' +
      '<p>Everything ' + esc(state.user.client_name) + ' has on file with us.</p></div></div>' +
      paymentBanner +
      '<div class="card" id="pd-profile"><div class="empty">Loading…</div></div>' +
      '<div class="card" id="pd-appointments"><div class="empty">Loading…</div></div>' +
      '<div class="card" id="pd-invoices"><div class="empty">Loading…</div></div>' +
      '<div class="card" id="pd-documents"><div class="empty">Loading…</div></div>' +
      '<div class="card" id="pd-request">' +
      '<h3>Request a service</h3>' +
      '<div class="field"><label>What do you need?</label><textarea id="pd-req-msg" rows="3" style="width:100%;padding:8px;border:1px solid var(--rule);border-radius:var(--r);font:inherit"></textarea></div>' +
      '<button class="btn btn-primary" id="pd-req-go" style="margin-top:8px">Send request</button>' +
      '<p class="portal-msg" id="pd-req-result"></p>' +
      '</div></div>';

    document.getElementById('pd-signout').addEventListener('click', async () => {
      try { await api('/api/portal/auth/logout', { method: 'POST' }); } catch (e) {}
      clearSession(); location.hash = '#/login'; route();
    });
    document.getElementById('pd-req-go').addEventListener('click', async () => {
      const el = document.getElementById('pd-req-msg');
      const result = document.getElementById('pd-req-result');
      result.textContent = '';
      if (!el.value.trim()) { result.textContent = 'Describe what you need first.'; return; }
      try {
        await api('/api/portal/service-requests', { method: 'POST', body: { message: el.value.trim() } });
        result.style.color = 'var(--teal)'; result.textContent = 'Sent — we\'ll be in touch shortly.';
        el.value = '';
      } catch (e) { result.textContent = e.message; }
    });

    try {
      const boot = await api('/api/portal/bootstrap');
      document.getElementById('pd-firm').textContent = (boot.firm && boot.firm.name) || 'Client Portal';
      const c = boot.client;
      document.getElementById('pd-profile').innerHTML = '<h3>Profile</h3>' +
        '<div class="grid c3">' +
        '<div><div style="font-size:11px;color:var(--ink-60)">Client code</div><b>' + esc(c.code) + '</b></div>' +
        '<div><div style="font-size:11px;color:var(--ink-60)">Entity type</div><b>' + esc(c.entity_type || '—') + '</b></div>' +
        '<div><div style="font-size:11px;color:var(--ink-60)">PAN</div><b>' + esc(c.pan || '—') + '</b></div>' +
        '</div>';
    } catch (e) { document.getElementById('pd-profile').innerHTML = '<div class="empty">' + esc(e.message) + '</div>'; }

    api('/api/portal/appointments').then(rows => {
      const holder = document.getElementById('pd-appointments');
      holder.innerHTML = '<h3>Appointments</h3>' + (rows.length
        ? '<div class="table-wrap"><table><thead><tr><th>Date</th><th>Time</th><th>Service</th><th>Status</th></tr></thead><tbody>' +
          rows.map(r => '<tr><td>' + esc(fmtDate(r.date)) + '</td><td>' + esc(r.start_time) + '–' + esc(r.end_time) + '</td>' +
            '<td>' + esc(r.service || '—') + '</td><td><span class="tag">' + esc(r.status) + '</span></td></tr>').join('') +
          '</tbody></table></div>'
        : '<div class="empty">No appointments yet.</div>');
    }).catch(e => { document.getElementById('pd-appointments').innerHTML = '<div class="empty">' + esc(e.message) + '</div>'; });

    api('/api/portal/invoices').then(rows => {
      const holder = document.getElementById('pd-invoices');
      holder.innerHTML = '<h3>Invoices</h3>' + (rows.length
        ? '<div class="table-wrap"><table><thead><tr><th>Invoice</th><th>Date</th><th>Status</th><th class="num">Total</th><th class="num">Paid</th><th></th></tr></thead><tbody>' +
          rows.map(r => '<tr><td>' + esc(r.invoice_no) + '</td><td>' + esc(r.invoice_date) + '</td>' +
            '<td><span class="tag' + (r.status === 'paid' ? ' tag-green' : r.status === 'sent' ? ' tag-amber' : '') + '">' + esc(r.status) + '</span></td>' +
            '<td class="num">' + money(r.total) + '</td><td class="num">' + money(r.paid_amount) + '</td>' +
            '<td>' + (r.status !== 'paid'
              ? '<select data-pay-gateway="' + r.id + '"><option value="khalti">Khalti</option><option value="connectips">ConnectIPS</option></select> ' +
                '<button class="btn btn-small btn-primary" data-pay-invoice="' + r.id + '">Pay now</button>'
              : '') + '</td></tr>').join('') +
          '</tbody></table></div>'
        : '<div class="empty">No invoices yet.</div>');

      holder.querySelectorAll('[data-pay-invoice]').forEach(btn => btn.addEventListener('click', async () => {
        const iid = btn.dataset.payInvoice;
        const gateway = holder.querySelector('[data-pay-gateway="' + iid + '"]').value;
        btn.disabled = true; btn.textContent = 'Redirecting…';
        try {
          const res = await api('/api/portal/invoices/' + iid + '/pay', { method: 'POST', body: { gateway } });
          if (res.gateway === 'khalti') {
            location.href = res.payment_url;
          } else {
            const form = document.createElement('form');
            form.method = 'POST'; form.action = res.action_url;
            Object.keys(res.fields).forEach(k => {
              const input = document.createElement('input');
              input.type = 'hidden'; input.name = k; input.value = res.fields[k];
              form.appendChild(input);
            });
            document.body.appendChild(form);
            form.submit();
          }
        } catch (e) {
          alert(e.message);
          btn.disabled = false; btn.textContent = 'Pay now';
        }
      }));
    }).catch(e => { document.getElementById('pd-invoices').innerHTML = '<div class="empty">' + esc(e.message) + '</div>'; });

    api('/api/portal/documents').then(rows => {
      const holder = document.getElementById('pd-documents');
      holder.innerHTML = '<h3>Documents</h3>' + (rows.length
        ? '<div class="table-wrap"><table><thead><tr><th>File</th><th>Category</th><th>Uploaded</th><th></th></tr></thead><tbody>' +
          rows.map(r => '<tr><td>' + esc(r.filename) + '</td><td><span class="tag">' + esc(r.category || 'other') + '</span></td>' +
            '<td>' + esc((r.uploaded_at || '').slice(0, 10)) + '</td>' +
            '<td><button class="btn btn-small" data-dl-doc="' + r.id + '" data-doc-name="' + esc(r.filename) + '">Download</button></td></tr>').join('') +
          '</tbody></table></div>'
        : '<div class="empty">Nothing shared with you yet.</div>');
      holder.querySelectorAll('[data-dl-doc]').forEach(btn => btn.addEventListener('click', async () => {
        try { await downloadAuthed('/api/portal/documents/' + btn.dataset.dlDoc + '/download', btn.dataset.docName); }
        catch (e) { alert(e.message); }
      }));
    }).catch(e => { document.getElementById('pd-documents').innerHTML = '<div class="empty">' + esc(e.message) + '</div>'; });
  }

  // The OAuth callback (server/app.py's portal_oauth_callback) lands back
  // here as a plain browser navigation with ?oauth_token=... on success or
  // ?oauth_error=<code> on failure — picked up once, then scrubbed from the
  // URL so a page refresh doesn't try to "consume" it a second time.
  function consumeOAuthReturn() {
    const qs = new URLSearchParams(location.search);
    const token = qs.get('oauth_token');
    const error = qs.get('oauth_error');
    if (!token && !error) return null;
    history.replaceState(null, '', location.pathname + location.hash);
    if (token) {
      // The server issued the session; this page never sees the client's
      // Google credentials at any point, only the resulting portal token.
      fetch('/api/portal/oauth/providers').catch(() => {});
      return { token };
    }
    return { error: OAUTH_ERROR_MESSAGES[error] || OAUTH_ERROR_MESSAGES.failed };
  }

  function route() {
    const oauthResult = consumeOAuthReturn();
    if (oauthResult && oauthResult.token) {
      // We only get the token back, not the user object — fetch it once so
      // the header/dashboard has a name to show, same shape as a password login.
      api('/api/portal/me', { headers: { Authorization: 'Bearer ' + oauthResult.token } })
        .catch(() => null)
        .then(user => {
          setSession(oauthResult.token, user || {});
          location.hash = '#/';
          route();
        });
      return;
    }

    const hash = location.hash || '#/login';
    const inviteMatch = hash.match(/^#\/invite\/(.+)$/);
    const resetMatch = hash.match(/^#\/reset\/(.+)$/);
    if (inviteMatch) { renderSetPassword('invite', inviteMatch[1]); return; }
    if (resetMatch) { renderSetPassword('reset', resetMatch[1]); return; }
    if (hash === '#/forgot') { renderForgot(); return; }
    if (!state.token) { renderLogin(oauthResult && oauthResult.error, true); return; }
    if (hash === '#/login') { location.hash = '#/'; return; }
    renderDashboard();
  }

  window.addEventListener('hashchange', route);
  document.addEventListener('DOMContentLoaded', route);
})();
