// Google Drive client da extensão.
//
// Auth: chrome.identity.getAuthToken — usa o OAuth2 client_id declarado no manifest.
// IMPORTANTE pra deploy: criar o OAuth Client no Google Cloud Console como
// "Chrome Extension", autorizar o ID da extensão publicada e colar o client_id
// no manifest.json campo "oauth2.client_id".
//
// Pasta raiz: "ScaleReels Downloader" criada na raiz do Drive do user.
// Subpastas por plataforma/autor: "tiktok_@autor" e "instagram_@autor".

import { downloadInOffscreen } from './ffmpeg-bridge.js';

const DEFAULT_ROOT_FOLDER_NAME = 'ScaleReels Downloader';
const DRIVE_API = 'https://www.googleapis.com/drive/v3';
const DRIVE_UPLOAD = 'https://www.googleapis.com/upload/drive/v3';

function getAuthToken(interactive = false) {
  return new Promise((resolve, reject) => {
    chrome.identity.getAuthToken({ interactive }, (token) => {
      if (chrome.runtime.lastError || !token) {
        reject(new Error(chrome.runtime.lastError?.message || 'Sem token Drive'));
      } else {
        resolve(token);
      }
    });
  });
}

export async function ensureDriveAuth({ interactive = false } = {}) {
  try {
    return await getAuthToken(interactive);
  } catch (err) {
    if (!interactive) {
      // Tenta de novo com prompt
      return getAuthToken(true);
    }
    throw err;
  }
}

export function revokeDriveAuth() {
  return new Promise((resolve) => {
    chrome.identity.getAuthToken({ interactive: false }, (token) => {
      if (!token) { resolve(); return; }
      chrome.identity.removeCachedAuthToken({ token }, () => {
        // Também revoga no Google pra forçar re-prompt
        fetch(`https://oauth2.googleapis.com/revoke?token=${token}`, { method: 'POST' })
          .finally(() => resolve());
      });
    });
  });
}

async function driveFetch(path, { method = 'GET', body = null, token, headers = {} } = {}) {
  const url = path.startsWith('http') ? path : (DRIVE_API + path);
  const r = await fetch(url, {
    method,
    headers: { Authorization: `Bearer ${token}`, ...headers },
    body
  });
  if (!r.ok) {
    let detail = '';
    try { detail = JSON.stringify(await r.json()); } catch (_) { detail = await r.text().catch(() => ''); }
    throw new Error(`Drive ${method} ${path} HTTP ${r.status}: ${detail.slice(0, 200)}`);
  }
  return r.json();
}

// Encontra (ou cria) uma pasta com nome+parent. Retorna o id.
async function ensureFolder(token, name, parentId = null) {
  const escaped = String(name).replace(/'/g, "\\'");
  let q = `name = '${escaped}' and mimeType = 'application/vnd.google-apps.folder' and trashed = false`;
  if (parentId) q += ` and '${parentId}' in parents`;
  else q += ` and 'root' in parents`;

  const list = await driveFetch(`/files?q=${encodeURIComponent(q)}&fields=files(id,name)`, { token });
  if (list.files?.length) return list.files[0].id;

  const meta = {
    name,
    mimeType: 'application/vnd.google-apps.folder',
    parents: parentId ? [parentId] : ['root']
  };
  const created = await driveFetch('/files?fields=id,name', {
    method: 'POST',
    token,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(meta)
  });
  return created.id;
}

// Estrutura de pastas:
//   ScaleReels Downloader/
//     tiktok_@autor/
//       2026-05-18_id123.mp4
//     instagram_@autor/
//       ...
//
// Retorna o folderId destino pra essa combinação platform+author.
export async function ensureTargetFolder(token, { platform, author, rootFolderName = DEFAULT_ROOT_FOLDER_NAME }) {
  const root = await ensureFolder(token, rootFolderName || DEFAULT_ROOT_FOLDER_NAME);
  const safeAuthor = String(author || 'unknown').replace(/[\/\\?%*:|"<>]/g, '_').slice(0, 60);
  const sub = `${platform}_@${safeAuthor}`;
  return ensureFolder(token, sub, root);
}

// Lista arquivos na pasta destino. Usado pra dedup (skip se nome já existe).
export async function listFolderFilenames(token, folderId) {
  const out = new Set();
  let pageToken = null;
  do {
    const params = new URLSearchParams({
      q: `'${folderId}' in parents and trashed = false`,
      fields: 'files(id,name),nextPageToken',
      pageSize: '1000'
    });
    if (pageToken) params.set('pageToken', pageToken);
    const page = await driveFetch(`/files?${params}`, { token });
    for (const f of (page.files || [])) out.add(f.name);
    pageToken = page.nextPageToken || null;
  } while (pageToken);
  return out;
}

// Upload multipart de um Blob pro folderId.
// Retorna { id, name, size } do arquivo criado.
export async function uploadFile(token, { blob, filename, folderId, mimeType = 'video/mp4', onProgress = null }) {
  const meta = {
    name: filename,
    parents: [folderId]
  };

  // Pra arquivos > ~5MB é mais robusto usar Resumable Upload (que dá pra acompanhar
  // progresso via XHR). Multipart simples já basta pro MVP — Chrome lida bem
  // com Blobs de até centenas de MB em fetch.
  const boundary = '-------is-ext-' + Math.random().toString(36).slice(2);
  const delim = `\r\n--${boundary}\r\n`;
  const closeDelim = `\r\n--${boundary}--`;

  const metaPart = delim
    + 'Content-Type: application/json; charset=UTF-8\r\n\r\n'
    + JSON.stringify(meta);

  const filePart = delim
    + `Content-Type: ${mimeType}\r\n\r\n`;

  // Constrói body como Blob composto
  const bodyParts = [
    new Blob([metaPart], { type: 'text/plain' }),
    new Blob([filePart], { type: 'text/plain' }),
    blob,
    new Blob([closeDelim], { type: 'text/plain' })
  ];
  const body = new Blob(bodyParts, { type: `multipart/related; boundary=${boundary}` });

  const url = `${DRIVE_UPLOAD}/files?uploadType=multipart&fields=id,name,size`;

  // fetch não tem onprogress nativo — pra MVP, sem progresso fino. Worker do SW
  // atualiza status quando começa e quando termina.
  const r = await fetch(url, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}`, 'Content-Type': `multipart/related; boundary=${boundary}` },
    body
  });
  if (!r.ok) {
    let detail = '';
    try { detail = JSON.stringify(await r.json()); } catch (_) { detail = await r.text().catch(() => ''); }
    throw new Error(`Drive upload HTTP ${r.status}: ${detail.slice(0, 200)}`);
  }
  return r.json();
}

// Baixa um arquivo público da CDN (TikTok/Instagram) como Blob.
//
// Roda dentro do page-world da aba TikTok/IG via chrome.tabs.sendMessage + bridge postMessage.
// Vantagens:
//   - Browser manda Referer correto (CDN exige)
//   - credentials:'include' envia os cookies de sessão do usuário (CDN tb exige!)
//   - Origem da página passa pelo CORS do CDN
//   - URL assinada tá ligada à sessão; precisa dos cookies pra validar.
export async function fetchAsBlob(url, { signal = null } = {}) {
  return fetchViaTabPage(url);
}

async function fetchViaTabPage(url) {
  const host = new URL(url).hostname;
  let tabPattern, label;
  if (/(^|\.)(tiktok|tiktokcdn|tiktokv|tiktokcdn-us)\./i.test(host)) {
    tabPattern = 'https://*.tiktok.com/*';
    label = 'TikTok';
  } else if (/(^|\.)(instagram|cdninstagram|fbcdn)\./i.test(host)) {
    tabPattern = 'https://*.instagram.com/*';
    label = 'Instagram';
  } else {
    throw new Error(`host desconhecido ${host}`);
  }
  const tabs = await chrome.tabs.query({ url: tabPattern });
  if (!tabs.length) {
    throw new Error(`Nenhuma aba do ${label} aberta — abra um perfil pra extensão poder baixar`);
  }
  const isVideoUrl = /\.mp4(\?|$)/i.test(url);
  // Tenta em cada aba — se uma navegou ou foi descarregada, vai pra próxima.
  let lastErr = null;
  for (const tab of tabs) {
    try {
      const reply = await chrome.tabs.sendMessage(tab.id, {
        type: 'IS_EXT_FETCH_URL',
        url
      });
      if (!reply?.ok) {
        lastErr = reply?.error || 'reply sem dados';
        continue;
      }
      // Decode base64 → Uint8Array → Blob.
      // (chrome.tabs.sendMessage JSON-ifica ArrayBuffer, então o page-world manda b64.)
      const bin = atob(reply.b64 || '');
      const u8 = new Uint8Array(bin.length);
      for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);

      // Validação mínima: rejeita só se for obviamente lixo (< 100 bytes).
      // Removidas checagens estritas de ftyp e tamanho 1KB+ que estavam
      // bloqueando downloads legítimos do IG (alguns shapes/streaming).
      if (u8.length < 100) {
        lastErr = `blob muito pequeno (${u8.length}b)`;
        continue;
      }
      return new Blob([u8], { type: reply.mime || 'video/mp4' });
    } catch (e) {
      lastErr = e?.message || String(e);
      // continua tentando próximas abas
    }
  }
  // Mensagem mais útil pro user: explica o caso mais comum (URL expirada)
  // O IG assina URLs de Reels que valem ~5-10 min. Se passou disso, vem 403.
  const expired = /trunc|tiny|no-ftyp|HTTP 403|HTTP 410|download falhou/i.test(lastErr || '');
  const hint = expired
    ? ' (URL do reel expirou — recarregue o perfil e tente baixar de novo)'
    : '';
  throw new Error(`Falha ao baixar do ${label}${hint}. Detalhe: ${lastErr}`);
}

// Download local — usa a API chrome.downloads pra salvar em Downloads/.
// Chrome só permite subpasta dentro da pasta Downloads padrão (segurança),
// não dá pra escolher pasta arbitrária.
//
// MV3 SW tem bug com blob:// URLs criadas no próprio SW pra usar com
// chrome.downloads (URL fica inacessível pelo download manager). Por isso:
//   - Se sourceUrl fornecida → usa ela direto (rápido, Chrome baixa da CDN)
//   - Se blob fornecido → usa offscreen pra criar a blob URL no contexto DOM
//
// Retorna { id, filename }.
export async function downloadLocal({ blob = null, sourceUrl = null, filename, subfolder = null }) {
  let path = filename;
  if (subfolder) {
    const safe = String(subfolder).replace(/[\\<>:"|?*]/g, '_').replace(/^\/+|\/+$/g, '');
    if (safe) path = `${safe}/${filename}`;
  }

  // Caminho A: URL direta da CDN — Chrome faz tudo, mais robusto
  if (sourceUrl && !blob) {
    const id = await chrome.downloads.download({
      url: sourceUrl,
      filename: path,
      saveAs: false,
      conflictAction: 'uniquify'
    });
    return { id, filename: path };
  }

  // Caminho B: blob processado — precisa criar a URL no offscreen (DOM)
  // pra que o chrome.downloads enxergue. Tentamos primeiro no SW (funciona em
  // versões recentes do Chrome) e caímos pro offscreen se falhar.
  if (blob) {
    try {
      const url = URL.createObjectURL(blob);
      const id = await chrome.downloads.download({
        url,
        filename: path,
        saveAs: false,
        conflictAction: 'uniquify'
      });
      setTimeout(() => URL.revokeObjectURL(url), 60000);
      return { id, filename: path };
    } catch (err) {
      console.warn('[Drive] downloadLocal via SW falhou, tentando offscreen:', err.message);
      return downloadInOffscreen({ blob, filename: path });
    }
  }

  throw new Error('downloadLocal: precisa de blob OU sourceUrl');
}

export function safeFilename({ platform, source_id, author, kind, ext }) {
  // Nome genérico video_<hash>.mp4 — sem expor plataforma/usuário/source ID.
  // Hash determinístico de 32 chars derivado do source_id (mesma fonte → mesmo
  // nome, evita duplicatas). Quando não tem source_id, gera random.
  const e = ext || (kind === 'image' ? '.jpg' : '.mp4');
  const seed = String(source_id || (platform + ':' + author + ':' + Date.now() + ':' + Math.random()));
  const hash = djb2Hex(seed) + djb2Hex(seed + ':salt');  // 32 chars
  return `video_${hash}${e}`;
}

// Hash djb2 → 16 chars hex (síncrono, sem WebCrypto — basta pra naming determinístico)
function djb2Hex(str) {
  let h = 5381;
  for (let i = 0; i < str.length; i++) {
    h = ((h * 33) ^ str.charCodeAt(i)) >>> 0;
  }
  // Mistura mais um pouco pra ter 16 chars
  let h2 = h;
  for (let i = 0; i < 8; i++) {
    h2 = ((h2 * 33) ^ ((h >> (i * 4)) & 0xff)) >>> 0;
  }
  return (h.toString(16).padStart(8, '0') + h2.toString(16).padStart(8, '0')).slice(0, 16);
}
