/* InstantDevis — Cloud Sync V1
 * Local-first: workspaceId + syncKey stored only in localStorage.
 * SHA-256 hash stored server-side. syncKey never logged or returned.
 * Conflict resolution: last-write-wins by updatedAt.
 * Signature Vault records are NEVER synced (read-only on server).
 */

const CLOUD_SYNC_WORKER = 'https://instantdevis-agents.sciencezvousyt.workers.dev';
const LS_SYNC_KEY       = 'instantdevis_sync_key';
const LS_SYNC_STATE     = 'instantdevis_cloud_sync_state';
const LS_WORKSPACE_ID   = 'instantdevis_workspace_id';
const LS_DEVICE_ID      = 'instantdevis_device_id';

// ─── Helpers ─────────────────────────────────────────────────────────────────

function _bytesToHex(bytes) {
  return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}

function getOrCreateSyncKey() {
  let k = localStorage.getItem(LS_SYNC_KEY);
  if (k && k.startsWith('sk_') && k.length >= 20) return k;
  const arr = new Uint8Array(32);
  crypto.getRandomValues(arr);
  k = 'sk_' + _bytesToHex(arr);
  localStorage.setItem(LS_SYNC_KEY, k);
  return k;
}

function getOrCreateWorkspaceIdSync() {
  let id = localStorage.getItem(LS_WORKSPACE_ID);
  if (id && id.startsWith('ws_')) return id;
  const arr = new Uint8Array(16);
  crypto.getRandomValues(arr);
  id = 'ws_' + _bytesToHex(arr);
  localStorage.setItem(LS_WORKSPACE_ID, id);
  return id;
}

function getOrCreateDeviceId() {
  let id = localStorage.getItem(LS_DEVICE_ID);
  if (id && id.length >= 8) return id;
  const arr = new Uint8Array(12);
  crypto.getRandomValues(arr);
  id = 'dev_' + _bytesToHex(arr);
  localStorage.setItem(LS_DEVICE_ID, id);
  return id;
}

function _getAuthSessionToken() {
  try {
    if (typeof window !== 'undefined' && window.AF_AUTH_SESSION && typeof window.AF_AUTH_SESSION.getToken === 'function') {
      const t = String(window.AF_AUTH_SESSION.getToken() || '').trim();
      if (t) return t;
    }
  } catch (_e) {}
  try {
    const raw = localStorage.getItem('instantdevis_auth_session');
    if (!raw) return '';
    const parsed = JSON.parse(raw);
    const t = String(parsed && parsed.sessionToken ? parsed.sessionToken : '').trim();
    return t || '';
  } catch (_e) {
    return '';
  }
}

const _CS_DEF = {
  enabled: false,
  status: 'local',
  lastSyncAt: '',
  lastPullAt: '',
  lastPushAt: '',
  lastError: '',
  updatedAt: '',
};

function getCloudSyncState() {
  try {
    const raw = localStorage.getItem(LS_SYNC_STATE);
    if (!raw) return { ..._CS_DEF };
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== 'object') return { ..._CS_DEF };
    // Merge with defaults so every field is always present.
    return { ..._CS_DEF, ...parsed, enabled: parsed.enabled === true };
  } catch (e) {
    return { ..._CS_DEF };
  }
}

function setCloudSyncState(patch) {
  try {
    const prev = getCloudSyncState();
    const next = { ...prev, ...patch, updatedAt: new Date().toISOString() };
    // Guarantee types.
    next.enabled = next.enabled === true;
    next.status = String(next.status || 'local');
    next.lastError = String(next.lastError == null ? '' : next.lastError);
    next.lastSyncAt = String(next.lastSyncAt == null ? '' : next.lastSyncAt);
    next.lastPullAt = String(next.lastPullAt == null ? '' : next.lastPullAt);
    next.lastPushAt = String(next.lastPushAt == null ? '' : next.lastPushAt);
    localStorage.setItem(LS_SYNC_STATE, JSON.stringify(next));
    return next;
  } catch (e) {
    return { ..._CS_DEF, ...patch };
  }
}

// ─── Build sync records from local store ─────────────────────────────────────

function buildSyncRecords() {
  const records = [];
  const workspaceId = getOrCreateWorkspaceIdSync();

  // Quotes
  try {
    const raw = localStorage.getItem('instantdevis_quotes_v1');
    if (raw) {
      const quotes = JSON.parse(raw);
      if (Array.isArray(quotes)) {
        for (const q of quotes) {
          if (!q || !q.id) continue;
          // Never sync signed/locked quotes — Signature Vault is the authority
          if (q.status === 'signed' || q.status === 'locked' || q.signedAt || q.locked) continue;
          records.push({
            type: 'quote',
            id: q.id,
            updatedAt: q.updatedAt || q.createdAt || new Date(0).toISOString(),
            deletedAt: null,
            payload: q,
          });
        }
      }
    }
  } catch (e) {}

  // Profile
  try {
    const raw = localStorage.getItem('instantdevis_profile_v1');
    if (raw) {
      const profile = JSON.parse(raw);
      if (profile && typeof profile === 'object') {
        // Never sync openAiKey
        const safe = { ...profile };
        delete safe.openAiKey;
        records.push({
          type: 'profile',
          id: workspaceId,
          updatedAt: safe.updatedAt || new Date(0).toISOString(),
          deletedAt: null,
          payload: safe,
        });
      }
    }
  } catch (e) {}

  // Catalog (AF_CATALOGUE if present)
  try {
    const raw = localStorage.getItem('instantdevis_catalogue');
    if (raw) {
      const items = JSON.parse(raw);
      if (Array.isArray(items)) {
        for (const item of items) {
          if (!item || !item.id) continue;
          records.push({
            type: 'catalog_item',
            id: item.id,
            updatedAt: item.updatedAt || new Date(0).toISOString(),
            deletedAt: null,
            payload: item,
          });
        }
      }
    }
  } catch (e) {}

  return records;
}

// ─── Apply pulled records to local store ─────────────────────────────────────

function applyPulledRecords(records) {
  if (!Array.isArray(records) || records.length === 0) return { applied: 0 };
  let applied = 0;

  const quoteUpdates = [];
  const profileUpdate = null;
  let latestProfile = null;
  const catalogUpdates = [];

  for (const rec of records) {
    if (!rec || !rec.payload) continue;
    if (rec.type === 'quote') {
      // Never overwrite signed/locked quotes
      if (rec.payload.status === 'signed' || rec.payload.status === 'locked' || rec.payload.signedAt) continue;
      quoteUpdates.push(rec);
    } else if (rec.type === 'profile') {
      if (!latestProfile || rec.updatedAt > latestProfile.updatedAt) {
        latestProfile = rec;
      }
    } else if (rec.type === 'catalog_item') {
      catalogUpdates.push(rec);
    }
  }

  // Apply quotes (last-write-wins by updatedAt)
  if (quoteUpdates.length > 0) {
    try {
      const raw = localStorage.getItem('instantdevis_quotes_v1');
      const existing = raw ? JSON.parse(raw) : [];
      const byId = {};
      for (const q of (Array.isArray(existing) ? existing : [])) {
        if (q && q.id) byId[q.id] = q;
      }
      for (const rec of quoteUpdates) {
        const local = byId[rec.id];
        const localAt = local ? (local.updatedAt || local.createdAt || '') : '';
        if (!local || rec.updatedAt > localAt) {
          byId[rec.id] = { ...rec.payload };
          applied++;
        }
      }
      localStorage.setItem('instantdevis_quotes_v1', JSON.stringify(Object.values(byId)));
    } catch (e) {}
  }

  // Apply profile
  if (latestProfile) {
    try {
      const raw = localStorage.getItem('instantdevis_profile_v1');
      const local = raw ? JSON.parse(raw) : {};
      const localAt = local ? (local.updatedAt || '') : '';
      if (!localAt || latestProfile.updatedAt > localAt) {
        const merged = { ...local, ...latestProfile.payload };
        delete merged.openAiKey;
        localStorage.setItem('instantdevis_profile_v1', JSON.stringify(merged));
        // Also update legacy key
        localStorage.setItem('instantdevis_profile', JSON.stringify(merged));
        applied++;
      }
    } catch (e) {}
  }

  // Apply catalog items
  if (catalogUpdates.length > 0) {
    try {
      const raw = localStorage.getItem('instantdevis_catalogue');
      const existing = raw ? JSON.parse(raw) : [];
      const byId = {};
      for (const item of (Array.isArray(existing) ? existing : [])) {
        if (item && item.id) byId[item.id] = item;
      }
      for (const rec of catalogUpdates) {
        const local = byId[rec.id];
        const localAt = local ? (local.updatedAt || '') : '';
        if (!local || rec.updatedAt > localAt) {
          byId[rec.id] = { ...rec.payload };
          applied++;
        }
      }
      localStorage.setItem('instantdevis_catalogue', JSON.stringify(Object.values(byId)));
    } catch (e) {}
  }

  return { applied };
}

// ─── Worker calls ─────────────────────────────────────────────────────────────

async function _syncPost(path, body) {
  const headers = { 'Content-Type': 'application/json' };
  const bearer = _getAuthSessionToken();
  if (bearer) headers.Authorization = `Bearer ${bearer}`;
  const res = await fetch(CLOUD_SYNC_WORKER + path, {
    method: 'POST',
    headers,
    body: JSON.stringify(body),
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw Object.assign(new Error(data.message || 'sync_error'), { data, status: res.status });
  return data;
}

// bootstrapCloudSync never throws — always returns { success: boolean, message?: string, ... }
async function bootstrapCloudSync() {
  try {
    const workspaceId = getOrCreateWorkspaceIdSync();
    const syncKey     = getOrCreateSyncKey();
    const deviceId    = getOrCreateDeviceId();
    let profile = null;
    try {
      const raw = localStorage.getItem('instantdevis_profile_v1');
      if (raw) {
        const p = JSON.parse(raw);
        if (p && typeof p === 'object') {
          profile = { ...p };
          delete profile.openAiKey;
        }
      }
    } catch (_e) {}
    const data = await _syncPost('/v1/sync/bootstrap', { workspaceId, syncKey, deviceId, profile });
    return { success: true, ...data };
  } catch (e) {
    const msg = (e && e.data && e.data.message) || (e && e.message) || 'bootstrap_error';
    return { success: false, message: msg };
  }
}

async function pushCloudSync() {
  const workspaceId = getOrCreateWorkspaceIdSync();
  const syncKey     = getOrCreateSyncKey();
  const deviceId    = getOrCreateDeviceId();
  const records     = buildSyncRecords();
  if (records.length === 0) return { success: true, pushed: 0, skippedOlder: 0 };
  return _syncPost('/v1/sync/push', { workspaceId, syncKey, deviceId, records });
}

async function pullCloudSync(since) {
  const workspaceId = getOrCreateWorkspaceIdSync();
  const syncKey     = getOrCreateSyncKey();
  const deviceId    = getOrCreateDeviceId();
  const body        = { workspaceId, syncKey, deviceId };
  if (since) body.since = since;
  const data = await _syncPost('/v1/sync/pull', body);
  if (data.records && data.records.length > 0) {
    applyPulledRecords(data.records);
  }
  return data;
}

async function getCloudSyncStatus() {
  const workspaceId = getOrCreateWorkspaceIdSync();
  const syncKey     = getOrCreateSyncKey();
  return _syncPost('/v1/sync/status', { workspaceId, syncKey });
}

// ─── syncNow: bootstrap → pull → push ────────────────────────────────────────
// Never runs unless the user has explicitly enabled cloud sync (enabled === true).

// syncNow never throws — always returns { ok: boolean, ... }
// Guard: no network call unless the user has explicitly enabled cloud sync.
async function syncNow(_opts) {
  try {
    const state = getCloudSyncState();
    if (!state || state.enabled !== true) {
      return { ok: false, error: 'cloud_sync_not_enabled' };
    }
    setCloudSyncState({ status: 'syncing', lastError: '' });
    const now = new Date().toISOString();
    const lastSyncAt = state.lastSyncAt || '';

    // bootstrap verifies the workspace key — never throws.
    const bootResult = await bootstrapCloudSync();
    if (!bootResult.success) {
      setCloudSyncState({ status: 'error', lastError: bootResult.message || 'bootstrap_error' });
      return { ok: false, error: bootResult.message || 'bootstrap_error' };
    }

    let pulled = 0;
    let pushed = 0;
    try {
      const pullResult = await pullCloudSync(lastSyncAt || null);
      pulled = pullResult.records ? pullResult.records.length : 0;
      setCloudSyncState({ lastPullAt: now });
    } catch (_e) {}

    try {
      const pushResult = await pushCloudSync();
      pushed = pushResult.pushed || 0;
      setCloudSyncState({ lastPushAt: now });
    } catch (_e) {}

    setCloudSyncState({ enabled: true, status: 'ready', lastSyncAt: now, lastError: '' });
    return { ok: true, pulled, pushed };
  } catch (e) {
    const msg = (e && e.message) || 'sync_error';
    setCloudSyncState({ status: 'error', lastError: msg });
    return { ok: false, error: msg };
  }
}

// ─── Exports (global, used by profile.jsx and app.jsx) ───────────────────────

window.CLOUD_SYNC = {
  getState:   getCloudSyncState,
  setState:   setCloudSyncState,
  syncNow,
  bootstrap:  bootstrapCloudSync,
  push:       pushCloudSync,
  pull:       pullCloudSync,
  status:     getCloudSyncStatus,
  getSyncKey: getOrCreateSyncKey,
  getDeviceId: getOrCreateDeviceId,
};
