// gdrive.jsx — Sao lưu tệp đính kèm (Supabase Storage) lên Google Drive của user đang đăng nhập.
// Dùng Google Identity Services (token client, client-side) — cùng OAuth client với Gmail, thêm scope drive.file.
// window.GDrive: getToken/ensurePath/findFile/uploadBlob/backupAll. Component <DriveBackupCard/> (thẻ trong Cài đặt).
//
// YÊU CẦU: thêm scope 'https://www.googleapis.com/auth/drive.file' vào OAuth consent screen (Google Cloud Console).
// drive.file = app CHỈ thấy/sửa tệp do CHÍNH app tạo → an toàn, đủ cho backup (tạo bản sao trong thư mục "MyAPP Backup").
// Tên thư mục CỐ ĐỊNH (không theo branding.name): đổi tên → cây thư mục mới → tải lại toàn bộ (mất dedup).
// Chỉ SAO CHÉP: không bao giờ xóa. Sao lưu các tệp mà tài khoản đang đăng nhập có quyền đọc (theo RLS).
(function(){
  const CLIENT_ID = (window.SWO_CONFIG && window.SWO_CONFIG.GOOGLE_OAUTH_CLIENT_ID) || '';
  const SCOPE = 'https://www.googleapis.com/auth/drive.file';
  const BUCKET = 'attachments';
  const ROOT = ['MyAPP Backup', 'attachments'];     // cây thư mục gốc trên Drive
  let tokenClient = null, token = null, tokenExp = 0;
  const TOK_KEY = 'swo-portal-driveTok', GRANT_KEY = 'swo-portal-driveGranted';

  function configured(){ return !!CLIENT_ID; }
  function gisReady(){ return !!(window.google && google.accounts && google.accounts.oauth2); }
  (function restore(){ try { const s = JSON.parse(sessionStorage.getItem(TOK_KEY) || 'null'); if (s && s.t && s.e) { token = s.t; tokenExp = s.e; } } catch (_){} })();
  function granted(){ try { return localStorage.getItem(GRANT_KEY) === '1'; } catch (_){ return false; } }

  function ensureClient(){
    if (tokenClient || !CLIENT_ID || !gisReady()) return tokenClient;
    tokenClient = google.accounts.oauth2.initTokenClient({ client_id: CLIENT_ID, scope: SCOPE, callback: () => {} });
    return tokenClient;
  }
  function getToken(){
    return new Promise((resolve, reject) => {
      if (!CLIENT_ID) { reject(new Error('Chưa cấu hình GOOGLE_OAUTH_CLIENT_ID')); return; }
      if (!gisReady()) { reject(new Error('Google Identity Services chưa tải xong — thử lại sau giây lát')); return; }
      ensureClient();
      if (token && Date.now() < tokenExp - 60000) { resolve(token); return; }
      tokenClient.callback = (resp) => {
        if (resp && resp.error) { reject(new Error(resp.error_description || resp.error)); return; }
        token = resp.access_token; tokenExp = Date.now() + ((resp.expires_in || 3600) * 1000);
        try { sessionStorage.setItem(TOK_KEY, JSON.stringify({ t:token, e:tokenExp })); } catch (_){}
        try { localStorage.setItem(GRANT_KEY, '1'); } catch (_){}
        resolve(token);
      };
      try { tokenClient.requestAccessToken({ prompt: granted() ? '' : 'consent' }); } catch (e) { reject(e); }
    });
  }

  // ---- Drive REST ----
  async function driveApi(url, opts){
    opts = opts || {};
    const headers = Object.assign({ 'Authorization': 'Bearer ' + token }, opts.headers || {});
    const res = await fetch(url, Object.assign({}, opts, { headers }));
    if (!res.ok){
      let m = 'Drive API lỗi ' + res.status;
      try { const e = await res.json(); if (e && e.error && e.error.message) m = e.error.message; } catch (_){}
      throw new Error(m);
    }
    return res.json();
  }
  const qesc = (s) => (s || '').replace(/['\\]/g, '\\$&');
  async function findChild(name, parentId, folderOnly){
    let q = "name = '" + qesc(name) + "' and trashed = false and '" + (parentId || 'root') + "' in parents";
    if (folderOnly) q += " and mimeType = 'application/vnd.google-apps.folder'";
    const url = 'https://www.googleapis.com/drive/v3/files?spaces=drive&pageSize=5&fields=files(id,name)&q=' + encodeURIComponent(q);
    const data = await driveApi(url);
    return (data.files && data.files[0]) ? data.files[0].id : null;
  }
  async function createFolder(name, parentId){
    const data = await driveApi('https://www.googleapis.com/drive/v3/files?fields=id', {
      method:'POST', headers:{ 'Content-Type':'application/json' },
      body: JSON.stringify({ name, mimeType:'application/vnd.google-apps.folder', parents: parentId ? [parentId] : [] }),
    });
    return data.id;
  }
  // ensurePath(parts, cache): tạo/tra cây thư mục, trả id thư mục lá. cache dùng lại trong 1 lần backup.
  async function ensurePath(parts, cache){
    let parentId = null, key = '';
    for (const name of parts){
      key += '/' + name;
      if (cache[key]) { parentId = cache[key]; continue; }
      let id = await findChild(name, parentId, true);
      if (!id) id = await createFolder(name, parentId);
      cache[key] = id; parentId = id;
    }
    return parentId;
  }
  async function findFile(name, parentId){ return findChild(name, parentId, false); }
  // Upload 2 bước (resumable): B1 POST metadata JSON → nhận URL phiên; B2 PUT nội dung thô.
  // Bền hơn multipart tự dựng (từng bị Google trả 400) + lỗi ở B1 luôn có message JSON đọc được.
  async function uploadBlob(name, mime, blob, parentId){
    const ct = mime || 'application/octet-stream';
    const init = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&fields=id', {
      method:'POST',
      headers:{ 'Authorization':'Bearer ' + token, 'Content-Type':'application/json; charset=UTF-8', 'X-Upload-Content-Type':ct },
      body: JSON.stringify({ name, parents: parentId ? [parentId] : [] }),
    });
    if (!init.ok){
      let m = 'Drive API lỗi ' + init.status + ' (khởi tạo upload)';
      try { const e = await init.json(); if (e && e.error && e.error.message) m = e.error.message; } catch (_){}
      throw new Error(m);
    }
    const loc = init.headers.get('Location') || init.headers.get('location');
    if (!loc) throw new Error('Không nhận được URL phiên tải lên (Location)');
    const up = await fetch(loc, { method:'PUT', headers:{ 'Content-Type':ct }, body: blob });
    if (!up.ok){
      let m = 'Drive API lỗi ' + up.status + ' (tải nội dung)';
      try { const e = await up.json(); if (e && e.error && e.error.message) m = e.error.message; } catch (_){}
      throw new Error(m);
    }
    return up.json();
  }

  // ---- liệt kê bucket (folder-aware, phân trang) ----
  async function listFolder(prefix){
    const out = { files: [], folders: [] };
    const limit = 100;
    for (let offset = 0; ; offset += limit){
      const { data, error } = await sb.storage.from(BUCKET).list(prefix, { limit, offset, sortBy:{ column:'name', order:'asc' } });
      if (error) throw new Error(error.message || 'list lỗi');
      const rows = data || [];
      for (const o of rows){
        if (o.id || o.metadata) out.files.push((prefix ? prefix + '/' : '') + o.name);
        else out.folders.push((prefix ? prefix + '/' : '') + o.name);
      }
      if (rows.length < limit) break;
    }
    return out;
  }
  async function walkBucket(prefix, acc){
    const { files, folders } = await listFolder(prefix);
    acc.push(...files);
    for (const d of folders) await walkBucket(d, acc);
    return acc;
  }

  // backupAll(onProgress) → { total, uploaded, skipped, errors, errMsg }
  async function backupAll(onProgress){
    if (!window.sb || !window.SB_READY) throw new Error('Supabase chưa sẵn sàng');
    const rep = (o) => { if (onProgress) onProgress(o); };
    await getToken();                                   // xin quyền (lần đầu hiện màn đồng ý)
    rep({ phase: 'list', done: 0, total: 0, uploaded: 0, skipped: 0, errors: 0 });
    const files = await walkBucket('', []);
    const cache = {};
    let uploaded = 0, skipped = 0, errors = 0, done = 0, errMsg = '';
    const total = files.length;
    rep({ phase: null, done, total, uploaded, skipped, errors });
    for (const path of files){
      try {
        const parts = path.split('/');
        const name = parts.pop();
        const folderId = await ensurePath(ROOT.concat(parts), cache);
        if (await findFile(name, folderId)) { skipped++; }
        else {
          const { data, error } = await sb.storage.from(BUCKET).download(path);
          if (error || !data) throw new Error((error && error.message) || 'tải xuống lỗi');
          await uploadBlob(name, data.type || 'application/octet-stream', data, folderId);
          uploaded++;
        }
      } catch (e){
        errors++;
        if (!errMsg) errMsg = (e && e.message) || String(e);   // giữ lỗi ĐẦU TIÊN để hiện cho user
        console.warn('[GDrive backup] lỗi tệp', path, e);       // log đủ từng tệp để chẩn đoán
      }
      done++;
      rep({ phase: null, done, total, uploaded, skipped, errors, errMsg });
    }
    return { total, uploaded, skipped, errors, errMsg };
  }

  window.GDrive = { configured, gisReady, getToken, ensurePath, findFile, uploadBlob, backupAll };
})();

// Thẻ "Sao lưu lên Google Drive" trong Cài đặt (chỉ admin).
function DriveBackupCard(){
  const p = usePortal();
  const { lang } = useLang();
  const toast = useToast();
  const [busy, setBusy] = React.useState(false);
  const [prog, setProg] = React.useState(null);
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  if (p.role !== 'admin') return null;
  const ok = !!(window.GDrive && GDrive.configured());
  const pct = prog && prog.total ? Math.round((prog.done / prog.total) * 100) : 0;
  const run = async () => {
    if (busy) return;
    setBusy(true);
    setProg({ phase: 'list', done: 0, total: 0, uploaded: 0, skipped: 0, errors: 0 });
    try {
      const res = await GDrive.backupAll(setProg);
      setProg(Object.assign({ phase: 'done' }, res, { done: res.total }));
      toast(L('Sao lưu xong: ', 'Backed up: ') + res.uploaded + L(' tải lên · ', ' up · ') + res.skipped + L(' bỏ qua', ' skipped') + (res.errors ? (' · ' + res.errors + L(' lỗi', ' errors')) : ''), res.errors ? 'warning' : 'success');
    } catch (e){
      toast((e && e.message) || L('Sao lưu thất bại', 'Backup failed'), 'error');
      setProg(null);
    } finally { setBusy(false); }
  };
  return (
    <div className="card">
      <div className="card-head"><h3>{L('Sao lưu lên Google Drive', 'Backup to Google Drive')}</h3></div>
      <div className="card-body col g12">
        <div className="subtle" style={{ fontSize:'var(--fs-base)', lineHeight:1.5 }}>
          {L('Sao chép toàn bộ tệp đính kèm (bạn có quyền truy cập) lên Google Drive của bạn — thư mục "MyAPP Backup". Chỉ tạo bản sao, không xóa gì. Lần sau chỉ tải tệp mới.',
             'Copy all attachments you can access to your Google Drive — folder "MyAPP Backup". Copy only, never deletes. Next runs upload only new files.')}
        </div>
        {!ok && <div style={{ color:'var(--warning)', fontSize:'var(--fs-base)' }}><i className="bi bi-exclamation-triangle" style={{ marginRight:6 }}></i>{L('Chưa cấu hình đăng nhập Google (GOOGLE_OAUTH_CLIENT_ID) — không thể sao lưu.', 'Google sign-in not configured — cannot back up.')}</div>}
        {prog && <div className="col g6">
          <div className="flex items-center justify-between">
            <span className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{prog.phase === 'list' ? L('Đang liệt kê tệp…', 'Listing files…') : prog.phase === 'done' ? L('Hoàn tất', 'Done') : (prog.done + '/' + prog.total)}</span>
            <span className="mono subtle" style={{ fontSize:'var(--fs-sm)' }}>{pct}%</span>
          </div>
          <div style={{ height:6, background:'var(--bg-sunken)', borderRadius:99, overflow:'hidden' }}><div style={{ height:'100%', width:pct + '%', background:'var(--primary)', transition:'width .2s' }}></div></div>
          <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Tải lên', 'Up')}: {prog.uploaded || 0} · {L('Bỏ qua', 'Skip')}: {prog.skipped || 0}{prog.errors ? (' · ' + L('Lỗi', 'Err') + ': ' + prog.errors) : ''}</div>
          {!!prog.errMsg && <div style={{ fontSize:'var(--fs-sm)', color:'var(--error)', lineHeight:1.5 }}>
            <i className="bi bi-exclamation-circle" style={{ marginRight:4 }}></i>{prog.errMsg}
            {/Drive API|has not been used|it is disabled|accessNotConfigured/i.test(prog.errMsg) && <div className="subtle" style={{ marginTop:2 }}>
              {L('→ Cần BẬT "Google Drive API" trong Google Cloud Console (APIs & Services → Library) cho đúng project OAuth, đợi ~1 phút rồi sao lưu lại.',
                 '→ Enable "Google Drive API" in Google Cloud Console (APIs & Services → Library) for the OAuth project, wait ~1 min, retry.')}
            </div>}
          </div>}
        </div>}
        <div><button className="btn btn-primary" disabled={busy || !ok} onClick={run}>{busy ? <><i className="bi bi-arrow-repeat spin"></i>{L('Đang sao lưu…', 'Backing up…')}</> : <><i className="bi bi-cloud-arrow-up"></i>{L('Sao lưu ngay', 'Back up now')}</>}</button></div>
      </div>
    </div>
  );
}
Object.assign(window, { DriveBackupCard });
