// =============================================================================
// Storage abstraction layer
// =============================================================================
// Wraps both localStorage (for anonymous users) and Supabase (for logged-in
// users) behind a single async interface. The rest of the app should never
// touch localStorage or Supabase directly — it goes through `window.Storage`.
//
// Design principles:
// - localStorage stays the fast path (instant reads/writes, no network).
// - Supabase is the durability layer (sync across devices, history, sharing).
// - When logged in, writes go to BOTH (write-through cache pattern).
// - When offline, writes still succeed locally and queue for later sync.
// - Reads prefer localStorage, fall back to Supabase only when needed.
//
// This file defines:
//   - window.Storage — the public API the app uses
//   - window.supabaseClient — lazily initialized Supabase client (null if not configured)
// =============================================================================

const LS_KEY_STATE = "pm_atelier_v1";
const LS_KEY_PENDING_SYNC = "pm_pending_sync";
const LS_KEY_USER_HINT = "pm_user_hint"; // remembers last logged-in user across reloads

// ─────────────────────────────────────────────────────────────────────────────
// Supabase client initialization
// ─────────────────────────────────────────────────────────────────────────────

function initSupabaseClient() {
  if (!window.isSupabaseConfigured || !window.isSupabaseConfigured()) {
    return null;
  }
  if (!window.supabase || !window.supabase.createClient) {
    console.warn("Supabase SDK not loaded — running in localStorage-only mode.");
    return null;
  }
  const { url, anonKey } = window.APP_CONFIG.supabase;
  try {
    return window.supabase.createClient(url, anonKey, {
      auth: {
        persistSession: true,
        autoRefreshToken: true,
        detectSessionInUrl: true  // handles magic-link redirects
      }
    });
  } catch (e) {
    console.error("Failed to init Supabase client:", e);
    return null;
  }
}

window.supabaseClient = initSupabaseClient();

// ─────────────────────────────────────────────────────────────────────────────
// localStorage helpers (the always-available fast path)
// ─────────────────────────────────────────────────────────────────────────────

function lsRead(key, fallback) {
  try {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) : fallback;
  } catch (e) {
    return fallback;
  }
}

function lsWrite(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch (e) {
    console.warn("localStorage write failed:", e);
    return false;
  }
}

function lsDelete(key) {
  try { localStorage.removeItem(key); } catch (e) {}
}

// ─────────────────────────────────────────────────────────────────────────────
// Pending-sync queue — for writes made while offline or before login
// ─────────────────────────────────────────────────────────────────────────────

function queuePendingSync(operation) {
  const queue = lsRead(LS_KEY_PENDING_SYNC, []);
  queue.push({ ...operation, queuedAt: Date.now() });
  lsWrite(LS_KEY_PENDING_SYNC, queue);
}

async function drainPendingSync() {
  const queue = lsRead(LS_KEY_PENDING_SYNC, []);
  if (!queue.length || !window.supabaseClient) return;

  const { data: { user } } = await window.supabaseClient.auth.getUser();
  if (!user) return;

  const remaining = [];
  for (const op of queue) {
    try {
      await applyOperation(op, user.id);
    } catch (e) {
      console.warn("Sync op failed, will retry:", op, e);
      remaining.push(op);
    }
  }
  lsWrite(LS_KEY_PENDING_SYNC, remaining);
}

async function applyOperation(op, userId) {
  const sb = window.supabaseClient;
  if (op.type === "user_state.upsert") {
    return sb.from("user_state").upsert({
      user_id: userId,
      state: op.payload
    });
  }
  if (op.type === "quiz_attempts.insert") {
    return sb.from("quiz_attempts").insert({
      user_id: userId,
      ...op.payload
    });
  }
  if (op.type === "sim_runs.insert") {
    return sb.from("sim_runs").insert({
      user_id: userId,
      ...op.payload
    });
  }
  if (op.type === "projects.upsert") {
    return sb.from("projects").upsert({
      user_id: userId,
      ...op.payload
    });
  }
  throw new Error("Unknown sync op type: " + op.type);
}

// ─────────────────────────────────────────────────────────────────────────────
// Public API: window.Storage
// ─────────────────────────────────────────────────────────────────────────────

window.Storage = {
  // ─── State (the big localStorage blob) ──────────────────────────────────

  /**
   * Read the user's full app state.
   * Returns localStorage immediately. If user is logged in, also fetches
   * remote state in the background and resolves to whichever is newer.
   */
  async loadState(defaultState) {
    const localState = lsRead(LS_KEY_STATE, null);

    // Anonymous user — localStorage is the source of truth
    if (!window.supabaseClient) {
      return { ...defaultState, ...(localState || {}) };
    }

    const { data: { user } } = await window.supabaseClient.auth.getUser();
    if (!user) {
      return { ...defaultState, ...(localState || {}) };
    }

    // Logged-in user — fetch remote state
    try {
      const { data, error } = await window.supabaseClient
        .from("user_state")
        .select("state, updated_at")
        .eq("user_id", user.id)
        .maybeSingle();

      if (error) throw error;

      const remoteState = data?.state || null;
      const remoteUpdated = data?.updated_at ? new Date(data.updated_at).getTime() : 0;
      const localUpdated = localState?._meta?.updatedAt || 0;

      // Pick whichever is newer
      const winner = remoteUpdated >= localUpdated ? remoteState : localState;
      const merged = { ...defaultState, ...(winner || {}) };

      // Refresh local cache with the winner
      lsWrite(LS_KEY_STATE, merged);
      return merged;
    } catch (e) {
      console.warn("loadState remote fetch failed, using local:", e);
      return { ...defaultState, ...(localState || {}) };
    }
  },

  /**
   * Save the user's full app state.
   * Writes to localStorage immediately. If logged in, syncs to Supabase.
   * If offline or sync fails, queues for later.
   */
  async saveState(state) {
    const stamped = {
      ...state,
      _meta: { updatedAt: Date.now() }
    };
    lsWrite(LS_KEY_STATE, stamped);

    if (!window.supabaseClient) return;

    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return;

      const { error } = await window.supabaseClient
        .from("user_state")
        .upsert({ user_id: user.id, state: stamped });

      if (error) throw error;
    } catch (e) {
      console.warn("saveState remote failed, queuing:", e);
      queuePendingSync({ type: "user_state.upsert", payload: stamped });
    }
  },

  /**
   * Wipe everything — local AND remote.
   */
  async resetState() {
    lsDelete(LS_KEY_STATE);
    lsDelete(LS_KEY_PENDING_SYNC);

    if (!window.supabaseClient) return;
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return;
      await window.supabaseClient
        .from("user_state")
        .delete()
        .eq("user_id", user.id);
    } catch (e) {
      console.warn("resetState remote failed:", e);
    }
  },

  // ─── Quiz attempts ──────────────────────────────────────────────────────

  /**
   * Record a finished quiz attempt. Always immutable.
   * Local copy isn't needed (the score lives in state already); this is
   * just for analytics/history on the server.
   */
  async recordQuizAttempt({ module, questionSet, answers, score, startedAt, finishedAt }) {
    if (!window.supabaseClient) return;
    const payload = {
      module,
      question_set: questionSet,
      answers,
      score,
      started_at: startedAt || new Date().toISOString(),
      finished_at: finishedAt || new Date().toISOString()
    };
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return;
      const { error } = await window.supabaseClient
        .from("quiz_attempts")
        .insert({ user_id: user.id, ...payload });
      if (error) throw error;
    } catch (e) {
      console.warn("recordQuizAttempt failed, queuing:", e);
      queuePendingSync({ type: "quiz_attempts.insert", payload });
    }
  },

  // ─── Simulation runs ────────────────────────────────────────────────────

  async recordSimRun({ scenarioId, finalState, history, finalScore, aiReport, startedAt, finishedAt }) {
    if (!window.supabaseClient) return;
    const payload = {
      scenario_id: scenarioId,
      final_state: finalState,
      history,
      final_score: finalScore,
      ai_report: aiReport,
      started_at: startedAt || new Date().toISOString(),
      finished_at: finishedAt || new Date().toISOString()
    };
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return;
      const { error } = await window.supabaseClient
        .from("sim_runs")
        .insert({ user_id: user.id, ...payload });
      if (error) throw error;
    } catch (e) {
      console.warn("recordSimRun failed, queuing:", e);
      queuePendingSync({ type: "sim_runs.insert", payload });
    }
  },

  // ─── M2 Projects ────────────────────────────────────────────────────────
  // Phase 0: not used yet (M2 data still in main state blob).
  // Phase 1+: each M2 project is its own row.

  /**
   * List all projects for the current user.
   * Returns metadata only (no `data` field) — call loadProject() for full content.
   */
  async listProjects({ includeArchived = false } = {}) {
    if (!window.supabaseClient) return [];
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return [];
      let q = window.supabaseClient
        .from("projects")
        .select("id, title, status, created_at, updated_at")
        .eq("user_id", user.id)
        .order("updated_at", { ascending: false });
      if (!includeArchived) q = q.neq("status", "archived");
      const { data, error } = await q;
      if (error) throw error;
      return data || [];
    } catch (e) {
      console.warn("listProjects failed:", e);
      return [];
    }
  },

  /** Load a single project by id, including its full data blob. */
  async loadProject(id) {
    if (!window.supabaseClient) return null;
    try {
      const { data, error } = await window.supabaseClient
        .from("projects")
        .select("*")
        .eq("id", id)
        .single();
      if (error) throw error;
      return data;
    } catch (e) {
      console.warn("loadProject failed:", e);
      return null;
    }
  },

  /** Create a new project. Returns the inserted row. */
  async createProject({ title, data = {} }) {
    if (!window.supabaseClient) return null;
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return null;
      const { data: saved, error } = await window.supabaseClient
        .from("projects")
        .insert({ user_id: user.id, title: title || "Proiect fără titlu", data })
        .select()
        .single();
      if (error) throw error;
      return saved;
    } catch (e) {
      console.warn("createProject failed:", e);
      return null;
    }
  },

  /** Update an existing project's data and/or title. */
  async updateProject(id, { title, data, status }) {
    if (!window.supabaseClient || !id) return null;
    try {
      const patch = {};
      if (title !== undefined) patch.title = title;
      if (data !== undefined) patch.data = data;
      if (status !== undefined) patch.status = status;
      const { data: saved, error } = await window.supabaseClient
        .from("projects")
        .update(patch)
        .eq("id", id)
        .select()
        .single();
      if (error) throw error;
      return saved;
    } catch (e) {
      console.warn("updateProject failed:", e);
      return null;
    }
  },

  /** Duplicate a project (creates a copy with " (copie)" suffix on title). */
  async duplicateProject(id) {
    if (!window.supabaseClient || !id) return null;
    try {
      const original = await this.loadProject(id);
      if (!original) return null;
      return this.createProject({
        title: `${original.title} (copie)`,
        data: original.data
      });
    } catch (e) {
      console.warn("duplicateProject failed:", e);
      return null;
    }
  },

  /** Permanently delete a project. */
  async deleteProject(id) {
    if (!window.supabaseClient || !id) return false;
    try {
      const { error } = await window.supabaseClient
        .from("projects")
        .delete()
        .eq("id", id);
      if (error) throw error;
      return true;
    } catch (e) {
      console.warn("deleteProject failed:", e);
      return false;
    }
  },

  // ─── Activity history (reads) ──────────────────────────────────────────
  // These power the "Istoricul meu" page. Anonymous users get empty arrays.

  /** Returns the most recent quiz attempts for the current user. */
  async listQuizAttempts({ limit = 50, module = null } = {}) {
    if (!window.supabaseClient) return [];
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return [];
      let q = window.supabaseClient
        .from("quiz_attempts")
        .select("id, module, score, started_at, finished_at, question_set, answers")
        .eq("user_id", user.id)
        .order("finished_at", { ascending: false })
        .limit(limit);
      if (module) q = q.eq("module", module);
      const { data, error } = await q;
      if (error) throw error;
      return data || [];
    } catch (e) {
      console.warn("listQuizAttempts failed:", e);
      return [];
    }
  },

  /** Returns the most recent simulator runs for the current user. */
  async listSimRuns({ limit = 50 } = {}) {
    if (!window.supabaseClient) return [];
    try {
      const { data: { user } } = await window.supabaseClient.auth.getUser();
      if (!user) return [];
      const { data, error } = await window.supabaseClient
        .from("sim_runs")
        .select("id, scenario_id, final_score, final_state, history, ai_report, started_at, finished_at")
        .eq("user_id", user.id)
        .order("finished_at", { ascending: false })
        .limit(limit);
      if (error) throw error;
      return data || [];
    } catch (e) {
      console.warn("listSimRuns failed:", e);
      return [];
    }
  },

  /** Aggregate stats for the user, computed from quiz_attempts + sim_runs. */
  async getActivityStats() {
    const [quizzes, sims] = await Promise.all([
      this.listQuizAttempts({ limit: 500 }),
      this.listSimRuns({ limit: 500 })
    ]);

    const m1Quizzes = quizzes.filter(q => q.module === "m1");
    const m4Exams = quizzes.filter(q => q.module === "m4_exam");

    const avg = (arr, key) =>
      arr.length ? Math.round(arr.reduce((s, x) => s + (x[key] || 0), 0) / arr.length) : null;
    const best = (arr, key) =>
      arr.length ? Math.max(...arr.map(x => x[key] || 0)) : null;

    return {
      totalQuizAttempts: quizzes.length,
      totalSimRuns: sims.length,
      m1QuizCount: m1Quizzes.length,
      m1QuizBest: best(m1Quizzes, "score"),
      m1QuizAvg: avg(m1Quizzes, "score"),
      m4ExamCount: m4Exams.length,
      m4ExamBest: best(m4Exams, "score"),
      m4ExamAvg: avg(m4Exams, "score"),
      simBest: best(sims, "final_score"),
      simAvg: avg(sims, "final_score"),
      firstActivity: [...quizzes, ...sims]
        .map(x => x.started_at || x.finished_at)
        .filter(Boolean)
        .sort()[0] || null
    };
  },

  // ─── GDPR — Data Portability (Article 20) ──────────────────────────────

  /**
   * Fetches all data the platform holds about the current user and returns
   * it as a JSON blob. Triggers a browser download.
   */
  async exportMyData() {
    let serverData = null;
    if (window.supabaseClient) {
      try {
        const { data: { user } } = await window.supabaseClient.auth.getUser();
        if (user) {
          const { data, error } = await window.supabaseClient.rpc("export_my_data");
          if (error) throw error;
          serverData = data;
        }
      } catch (e) {
        console.warn("Server export failed, using local-only:", e);
      }
    }

    const localData = {
      app_version: window.APP_CONFIG?.app?.version || "unknown",
      exported_at_local: new Date().toISOString(),
      local_state: lsRead(LS_KEY_STATE, null),
      pending_sync: lsRead(LS_KEY_PENDING_SYNC, [])
    };

    const fullExport = {
      ...(serverData || {}),
      _local: localData
    };

    // Trigger download
    const blob = new Blob([JSON.stringify(fullExport, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    const today = new Date().toISOString().slice(0, 10);
    a.download = `pm-hub-export-${today}.json`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);

    return fullExport;
  },

  // ─── GDPR — Right to Erasure (Article 17) ──────────────────────────────

  /**
   * Permanently deletes the user's account and all associated data.
   * After this:
   *   - All Supabase rows are deleted (cascade from auth.users)
   *   - localStorage is wiped
   *   - User is signed out
   */
  async deleteMyAccount() {
    // 1. Server delete (if logged in)
    if (window.supabaseClient) {
      try {
        const { data: { user } } = await window.supabaseClient.auth.getUser();
        if (user) {
          const { error } = await window.supabaseClient.rpc("delete_my_account");
          if (error) throw error;
          await window.supabaseClient.auth.signOut();
        }
      } catch (e) {
        console.error("Server delete failed:", e);
        throw e;
      }
    }

    // 2. Wipe local
    lsDelete(LS_KEY_STATE);
    lsDelete(LS_KEY_PENDING_SYNC);
    lsDelete(LS_KEY_USER_HINT);
    try { sessionStorage.clear(); } catch (e) {}
  },

  // ─── Sync utilities ─────────────────────────────────────────────────────

  async syncPending() {
    return drainPendingSync();
  },

  getPendingCount() {
    return lsRead(LS_KEY_PENDING_SYNC, []).length;
  }
};

// Try to drain pending sync queue whenever we come back online
window.addEventListener("online", () => {
  drainPendingSync().catch(() => {});
});
