// Saloniq — Firebase client
// Initializes Firebase + exposes async helpers for the rest of the app.
//
// All security is enforced server-side via firestore.rules — the helpers here
// are convenience wrappers, not security boundaries.

(function initFirebase() {
  if (firebase.apps.length === 0) {
    firebase.initializeApp(window.SALONIQ_FIREBASE_CONFIG);
  }
})();

const fbAuth = firebase.auth();
const fbDb = firebase.firestore();

// Persist sessions across browser restarts (default is LOCAL but be explicit)
fbAuth.setPersistence(firebase.auth.Auth.Persistence.LOCAL).catch(() => {});

// Enable Firestore IndexedDB offline cache — survives reloads + computer crashes.
// Writes are queued locally if offline and replayed when network returns.
try {
  fbDb.enablePersistence({ synchronizeTabs: true }).catch(err => {
    if (err.code !== 'failed-precondition' && err.code !== 'unimplemented') {
      console.warn('Firestore persistence:', err.code, err.message);
    }
  });
} catch (e) { /* called twice or unsupported — safe to ignore */ }

// ─── Helpers ───────────────────────────────────────────────────────────────
const isSuperAdminEmail = (email) =>
  !!email && (window.SALONIQ_SUPER_ADMIN_EMAILS || []).includes(email.toLowerCase());

// Subscribe to auth-state changes; resolves user + their /users/{uid} profile.
function onAuthChange(callback) {
  return fbAuth.onAuthStateChanged(async (user) => {
    if (!user) {
      callback({ user: null, profile: null, isSuperAdmin: false });
      return;
    }
    const superAdmin = isSuperAdminEmail(user.email);
    let profile = null;
    if (!superAdmin) {
      try {
        const doc = await fbDb.collection('users').doc(user.uid).get();
        profile = doc.exists ? { id: doc.id, ...doc.data() } : null;
      } catch (e) {
        console.warn('Failed to load user profile', e);
      }
    }
    callback({ user, profile, isSuperAdmin: superAdmin });
  });
}

// ─── Auth ──────────────────────────────────────────────────────────────────
async function signIn(email, password, { remember = true } = {}) {
  // LOCAL = persists across browser restarts; SESSION = only this tab
  const persistence = remember
    ? firebase.auth.Auth.Persistence.LOCAL
    : firebase.auth.Auth.Persistence.SESSION;
  await fbAuth.setPersistence(persistence);
  return fbAuth.signInWithEmailAndPassword(email.trim().toLowerCase(), password);
}

async function signOut() {
  return fbAuth.signOut();
}

// Send password-reset email. Firebase generates a one-time link to its hosted
// reset page (or your custom continueUrl). Returns silently — we don't reveal
// whether the email exists, to avoid leaking which accounts are valid.
async function sendPasswordReset(email) {
  const cleanEmail = (email || '').trim().toLowerCase();
  if (!cleanEmail.includes('@')) throw new Error('Ugyldig email');
  return fbAuth.sendPasswordResetEmail(cleanEmail, {
    url: window.location.origin + '/?reset=done',
    handleCodeInApp: false,
  });
}

// ─── Salon directory ───────────────────────────────────────────────────────
async function listSalons() {
  const snap = await fbDb.collection('salons').orderBy('createdAt', 'desc').get();
  return snap.docs.map(d => ({ id: d.id, ...d.data() }));
}

async function getSalonBySubdomain(subdomain) {
  const snap = await fbDb.collection('salons')
    .where('subdomain', '==', subdomain.toLowerCase())
    .limit(1).get();
  if (snap.empty) return null;
  const doc = snap.docs[0];
  return { id: doc.id, ...doc.data() };
}

async function getSalon(salonId) {
  const doc = await fbDb.collection('salons').doc(salonId).get();
  return doc.exists ? { id: doc.id, ...doc.data() } : null;
}

// ─── Super-admin operations ────────────────────────────────────────────────
// Creates: 1) Firebase Auth user (in a SECONDARY auth instance so we don't
// kick out the super-admin), 2) /users/{uid} profile, 3) /salons/{salonId} doc.
async function createSalonWithOwner({ salonName, subdomain, color, industry, ownerEmail, ownerPassword, ownerName }) {
  const cleanSubdomain = subdomain.toLowerCase().replace(/[^a-z0-9-]/g, '');
  const cleanEmail = ownerEmail.trim().toLowerCase();
  if (!cleanSubdomain) throw new Error('Ugyldigt subdomæne');
  if (!cleanEmail.includes('@')) throw new Error('Ugyldig email');
  if (!ownerPassword || ownerPassword.length < 6) throw new Error('Adgangskode skal være mindst 6 tegn');

  // Check subdomain isn't taken
  const existing = await getSalonBySubdomain(cleanSubdomain);
  if (existing) throw new Error('Subdomænet er allerede i brug');

  // Use a secondary auth instance so super-admin doesn't get signed out
  const secondaryAppName = '__secondary_' + Date.now();
  const secondaryApp = firebase.initializeApp(window.SALONIQ_FIREBASE_CONFIG, secondaryAppName);
  let cred;
  try {
    cred = await secondaryApp.auth().createUserWithEmailAndPassword(cleanEmail, ownerPassword);
  } finally {
    await secondaryApp.delete();
  }

  const uid = cred.user.uid;
  const salonId = cleanSubdomain;
  const now = firebase.firestore.FieldValue.serverTimestamp();

  // Create /salons/{salonId} + /users/{uid} in a batch
  const batch = fbDb.batch();
  batch.set(fbDb.collection('salons').doc(salonId), {
    name: salonName,
    subdomain: cleanSubdomain,
    color: color || '#3F4A3A',
    industry: industry || 'frisor',
    active: true,
    createdAt: now,
    createdBy: fbAuth.currentUser?.uid || 'super-admin',
  });
  batch.set(fbDb.collection('users').doc(uid), {
    email: cleanEmail,
    salonId,
    role: 'owner',
    name: ownerName || cleanEmail,
    createdAt: now,
  });
  await batch.commit();

  return { salonId, uid };
}

async function setSalonActive(salonId, active) {
  await fbDb.collection('salons').doc(salonId).update({ active });
}

async function updateSalon(salonId, patch) {
  // SMTP credentials live in a separate private subcollection so the salon
  // doc itself can stay publicly-readable for subdomain lookup without
  // leaking the password.
  const { smtp, ...publicPatch } = patch || {};
  if (Object.keys(publicPatch).length > 0) {
    await fbDb.collection('salons').doc(salonId).update(publicPatch);
  }
  if (smtp !== undefined) {
    const smtpRef = fbDb.collection('salons').doc(salonId).collection('private').doc('smtp');
    if (smtp === null) {
      await smtpRef.delete().catch(() => {});
    } else {
      await smtpRef.set(smtp, { merge: false });
    }
  }
}

// Reset salon's adminPin (clear it). Owner is prompted to set a new PIN
// on next login via PinSetupModal. Operates on the private state doc.
async function resetSalonPin(salonId) {
  const ref = fbDb.collection('salons').doc(salonId).collection('private').doc('state');
  const doc = await ref.get();
  if (!doc.exists) return;
  const data = doc.data();
  const settings = { ...(data.appSettings || {}), adminPin: '' };
  await ref.update({ appSettings: settings, updatedAt: firebase.firestore.FieldValue.serverTimestamp() });
}

async function deleteSalon(salonId) {
  // Delete /salons/{salonId} doc — full cascade requires a Cloud Function or manual cleanup.
  // For now we mark as inactive; super-admin can hard-delete the auth user from Firebase Console.
  await fbDb.collection('salons').doc(salonId).delete();
}

async function listSalonUsers(salonId) {
  const snap = await fbDb.collection('users').where('salonId', '==', salonId).get();
  return snap.docs.map(d => ({ id: d.id, ...d.data() }));
}

async function addSalonUser({ salonId, email, password, name, role }) {
  const cleanEmail = email.trim().toLowerCase();
  if (!cleanEmail.includes('@')) throw new Error('Ugyldig email');
  if (!password || password.length < 6) throw new Error('Adgangskode skal være mindst 6 tegn');

  const secondaryAppName = '__secondary_' + Date.now();
  const secondaryApp = firebase.initializeApp(window.SALONIQ_FIREBASE_CONFIG, secondaryAppName);
  let cred;
  try {
    cred = await secondaryApp.auth().createUserWithEmailAndPassword(cleanEmail, password);
  } finally {
    await secondaryApp.delete();
  }

  await fbDb.collection('users').doc(cred.user.uid).set({
    email: cleanEmail,
    salonId,
    role: role || 'staff',
    name: name || cleanEmail,
    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
  });
  return cred.user.uid;
}

async function removeSalonUser(uid) {
  // Removes Firestore profile only; the auth user must be deleted from Firebase
  // Console (or via a Cloud Function) for full cleanup.
  await fbDb.collection('users').doc(uid).delete();
}

// Map common Firebase auth-error codes to Danish user-friendly messages
function authErrorMessage(err) {
  const code = err?.code || '';
  if (code === 'auth/wrong-password' || code === 'auth/invalid-credential' || code === 'auth/user-not-found' || code === 'auth/invalid-login-credentials')
    return 'Forkert email eller adgangskode.';
  if (code === 'auth/too-many-requests')
    return 'For mange forsøg. Vent et øjeblik og prøv igen.';
  if (code === 'auth/email-already-in-use')
    return 'Denne email er allerede brugt af en anden konto.';
  if (code === 'auth/network-request-failed')
    return 'Ingen forbindelse til serveren. Tjek dit netværk og prøv igen.';
  if (code === 'auth/weak-password')
    return 'Adgangskoden er for svag. Brug mindst 6 tegn.';
  if (code === 'auth/invalid-email')
    return 'Ugyldig email-adresse.';
  return err?.message || 'Der skete en fejl. Prøv igen.';
}

// ─── Image helper ─────────────────────────────────────────────────────────────
// Resize and re-encode an uploaded image to a small JPEG dataURL so it fits
// inside a Firestore document. Without this, full-resolution iPhone photos
// (~4MB base64) would push the salon doc over Firestore's 1MB hard cap and
// fail every subsequent save silently — the cause of "data doesn't persist"
// reports.
window.SaloniqResizeImage = function (file, maxDim = 400, quality = 0.78) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => reject(new Error('Kunne ikke læse fil'));
    reader.onload = (ev) => {
      const img = new Image();
      img.onerror = () => reject(new Error('Ugyldigt billedformat'));
      img.onload = () => {
        const ratio = Math.min(maxDim / img.width, maxDim / img.height, 1);
        const w = Math.round(img.width * ratio);
        const h = Math.round(img.height * ratio);
        const canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, w, h); // flatten transparency
        ctx.drawImage(img, 0, 0, w, h);
        // SVG goes through as-is (canvas can't faithfully rasterize them anyway).
        if (file.type === 'image/svg+xml') resolve(ev.target.result);
        else resolve(canvas.toDataURL('image/jpeg', quality));
      };
      img.src = ev.target.result;
    };
    reader.readAsDataURL(file);
  });
};

// ─── Public cancel tokens ─────────────────────────────────────────────────
// One doc per booking, stored at /cancelTokens/{token}. Exposes ONLY the
// fields the customer needs to recognise their own booking — no phone,
// email, address or note. Token is a random URL-safe id ('aflys-' prefix).
function generateCancelToken() {
  const bytes = new Uint8Array(8);
  if (window.crypto?.getRandomValues) {
    window.crypto.getRandomValues(bytes);
  } else {
    for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
  }
  return 'aflys-' + Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
}

async function writeCancelToken(token, payload) {
  const safe = {
    salonId: String(payload.salonId || ''),
    bookingId: String(payload.bookingId || ''),
    customerName: String(payload.customerName || '').slice(0, 80),
    serviceTitle: String(payload.serviceTitle || '').slice(0, 120),
    staffName: String(payload.staffName || '').slice(0, 80),
    dateISO: String(payload.dateISO || ''),
    startMin: Number(payload.startMin) || 0,
    durationMin: Number(payload.durationMin) || 0,
    priceKr: Number(payload.priceKr) || 0,
    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
  };
  return fbDb.collection('cancelTokens').doc(token).set(safe);
}

async function getCancelToken(token) {
  const doc = await fbDb.collection('cancelTokens').doc(token).get();
  return doc.exists ? { id: doc.id, ...doc.data() } : null;
}

async function deleteCancelToken(token) {
  return fbDb.collection('cancelTokens').doc(token).delete().catch(() => {});
}

// ─── Public cancel/reschedule requests ────────────────────────────────────
// The customer-facing /aflys-XXXX page submits a doc here. Anyone (anonymous)
// can create — Firestore rules constrain shape and rate. Only the salon's
// own users can read/update/delete.
async function submitCancelRequest({ salonId, slug, bookingId, customerName, customerPhone, action, reason, message }) {
  // Firestore rules require all 10 keys to be present (even when empty),
  // so we explicitly include every field — never let any of them be undefined.
  const doc = {
    salonId: String(salonId || ''),
    slug: String(slug || ''),
    bookingId: String(bookingId || ''),
    customerName: String(customerName || '').trim().slice(0, 80),
    customerPhone: String(customerPhone || '').trim().slice(0, 30),
    action: action === 'reschedule' ? 'reschedule' : 'cancel',
    reason: String(reason || '').trim().slice(0, 80),
    message: String(message || '').trim().slice(0, 500),
    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
    handled: false,
  };
  return fbDb.collection('cancelRequests').add(doc);
}

// Subscribe to unhandled cancel requests for a salon. Used by App to
// surface incoming requests as bell-notifications in real time.
function subscribeCancelRequests(salonId, callback) {
  return fbDb.collection('cancelRequests')
    .where('salonId', '==', salonId)
    .where('handled', '==', false)
    .onSnapshot(snap => {
      const list = [];
      snap.forEach(d => list.push({ id: d.id, ...d.data() }));
      callback(list);
    }, err => {
      console.warn('cancelRequests subscription failed:', err?.code, err?.message);
    });
}

async function markCancelRequestHandled(requestId) {
  return fbDb.collection('cancelRequests').doc(requestId).update({ handled: true });
}

// Realtime-listener på indkomne online-bookinger fra det offentlige link.
function subscribeBookingNotifications(salonId, callback) {
  return fbDb.collection('bookingNotifications')
    .where('salonId', '==', salonId)
    .where('handled', '==', false)
    .onSnapshot(snap => {
      const list = [];
      snap.forEach(d => list.push({ id: d.id, ...d.data() }));
      callback(list);
    }, err => {
      console.warn('bookingNotifications subscription failed:', err?.code, err?.message);
    });
}
async function markBookingNotificationHandled(notifId) {
  return fbDb.collection('bookingNotifications').doc(notifId).update({ handled: true });
}

// ─── Mail queue ───────────────────────────────────────────────────────────
// Enqueue an email to be sent by the processMailQueue Cloud Function.
// Returns the queue-doc id; status updates land back on the same doc.
async function enqueueEmail({ salonId, to, type, data }) {
  if (!salonId || !to || !type) throw new Error('enqueueEmail: salonId, to, type required');
  const ref = await fbDb.collection('mailQueue').add({
    salonId: String(salonId),
    to: String(to),
    type: String(type),
    data: data || {},
    status: 'pending',
    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
  });
  return ref.id;
}

window.SaloniqFirebase = {
  auth: fbAuth, db: fbDb,
  isSuperAdminEmail, onAuthChange,
  signIn, signOut, sendPasswordReset,
  listSalons, getSalonBySubdomain, getSalon,
  createSalonWithOwner, setSalonActive, deleteSalon, updateSalon, resetSalonPin,
  listSalonUsers, addSalonUser, removeSalonUser,
  submitCancelRequest, subscribeCancelRequests, markCancelRequestHandled,
  subscribeBookingNotifications, markBookingNotificationHandled,
  generateCancelToken, writeCancelToken, getCancelToken, deleteCancelToken,
  enqueueEmail,
  authErrorMessage,
};

// ─── Per-salon data store ─────────────────────────────────────────────────────
// Backed by Firestore — works across devices, survives crashes via IndexedDB
// offline cache. Single doc per salon at /salons/{id}/private/state.
//
// One-time migration: if Firestore has no doc yet but localStorage does (from
// the previous localStorage-only build), upload the localStorage data and clear it.
function _privateRef(salonId) {
  return fbDb.collection('salons').doc(salonId).collection('private').doc('state');
}

async function _migrateLegacyLocal(salonId) {
  try {
    const raw = localStorage.getItem('saloniq_data_' + salonId);
    if (!raw) return null;
    const data = JSON.parse(raw);
    await _privateRef(salonId).set({
      ...data,
      _migratedFromLocalStorage: true,
      updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
    });
    localStorage.removeItem('saloniq_data_' + salonId);
    return data;
  } catch {
    return null;
  }
}

window.SaloniqSalonData = {
  // Load salon's full state. Returns Promise<data | null>.
  async load(salonId) {
    if (!salonId) return null;
    try {
      const doc = await _privateRef(salonId).get();
      if (doc.exists) return doc.data();
      // First-time load — try migrating from localStorage if present
      return await _migrateLegacyLocal(salonId);
    } catch (e) {
      console.error('Failed to load salon data', e);
      return null;
    }
  },

  // Save full state. Firestore overwrites the doc; offline writes queue.
  async save(salonId, data) {
    if (!salonId) return;
    try {
      // Firestore can't store undefined; sanitize via JSON round-trip
      const clean = JSON.parse(JSON.stringify(data || {}));
      clean.updatedAt = firebase.firestore.FieldValue.serverTimestamp();
      await _privateRef(salonId).set(clean);
    } catch (e) {
      console.error('Failed to save salon data', e);
      // Surface to the user so they know something went wrong instead of
      // assuming changes are saved. Common case: doc-too-large from raw photos.
      const msg = e?.message?.includes('exceeds the maximum')
        ? 'Kunne ikke gemme — billede eller anden data fylder for meget. Prøv et mindre billede.'
        : 'Kunne ikke gemme ændringer (' + (e?.code || 'fejl') + '). Tjek netværket og prøv igen.';
      window.dispatchEvent(new CustomEvent('saloniq-save-error', { detail: msg }));
    }
  },

  // Real-time subscription — fires whenever another device updates the doc.
  // Returns unsubscribe function.
  subscribe(salonId, callback) {
    if (!salonId) return () => {};
    return _privateRef(salonId).onSnapshot(
      doc => { if (doc.exists) callback(doc.data()); },
      err => console.warn('Salon data subscription error:', err),
    );
  },
};
