'use strict';

/**
 * Durable Stripe fulfillment state.
 *
 * The bridge and the API share this file through the host data mount. A
 * generated license is persisted before email delivery so a webhook retry can
 * resend the same key instead of minting a second valid key.
 */

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const platformPaths = require('../../platform-paths');

const DELIVERY_LEASE_MS = 5 * 60 * 1000;

function createFulfillmentStore(options = {}) {
  const filePath = options.filePath
    || process.env.STRIPE_FULFILLMENT_FILE
    || path.join(platformPaths.dataDir, 'stripe-fulfillments.json');
  let queue = Promise.resolve();

  function emptyState() {
    return { version: 1, byEventId: {}, bySessionId: {} };
  }

  function readState() {
    try {
      if (!fs.existsSync(filePath)) return emptyState();
      const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
      if (!parsed || typeof parsed !== 'object'
        || !parsed.byEventId || typeof parsed.byEventId !== 'object'
        || !parsed.bySessionId || typeof parsed.bySessionId !== 'object') {
        throw new Error('fulfillment state has an invalid shape');
      }
      return parsed;
    } catch (error) {
      if (error && error.code === 'ENOENT') return emptyState();
      throw new Error(`Stripe fulfillment state unavailable: ${error.message}`);
    }
  }

  function writeState(state) {
    const dir = path.dirname(filePath);
    fs.mkdirSync(dir, { recursive: true });
    const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}.${crypto.randomBytes(4).toString('hex')}`;
    fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
    try {
      fs.renameSync(tmp, filePath);
    } catch (error) {
      try { fs.unlinkSync(tmp); } catch (_) { /* best effort */ }
      throw error;
    }
    try { fs.chmodSync(filePath, 0o600); } catch (_) { /* best effort */ }
  }

  function mutate(mutator) {
    const run = queue.then(async () => {
      const state = readState();
      const result = await mutator(state);
      if (result && result.changed) writeState(state);
      return result;
    });
    queue = run.catch(() => {});
    return run;
  }

  function readBySession(sessionId) {
    if (!sessionId) return null;
    const record = readState().bySessionId[sessionId];
    return record ? { ...record } : null;
  }

  function readByEvent(eventId) {
    if (!eventId) return null;
    const record = readState().byEventId[eventId];
    return record ? { ...record } : null;
  }

  async function claim({ eventId, sessionId, productId, durationDays, email }) {
    if (!eventId || !sessionId) throw new Error('eventId and sessionId are required');
    return mutate((state) => {
      const existing = state.bySessionId[sessionId] || state.byEventId[eventId];
      const now = Date.now();
      if (existing) {
        if (existing.status === 'generating' && existing.leaseUntil > now && existing.claimToken !== eventId) {
          return { changed: false, claimed: false, busy: true, record: { ...existing } };
        }
        if (existing.status === 'generating' && existing.leaseUntil <= now) {
          existing.claimToken = eventId;
          existing.leaseUntil = now + DELIVERY_LEASE_MS;
          state.byEventId[eventId] = existing;
          return { changed: true, claimed: true, busy: false, record: { ...existing } };
        }
        return { changed: false, claimed: false, busy: false, record: { ...existing } };
      }
      const record = {
        eventId, sessionId, productId, durationDays, email,
        status: 'generating', claimToken: eventId, leaseUntil: now + DELIVERY_LEASE_MS,
        createdAt: new Date(now).toISOString(), updatedAt: new Date(now).toISOString(),
      };
      state.byEventId[eventId] = record;
      state.bySessionId[sessionId] = record;
      return { changed: true, claimed: true, busy: false, record: { ...record } };
    });
  }

  async function saveLicense({ eventId, sessionId, code, codeId }) {
    return mutate((state) => {
      const record = state.bySessionId[sessionId] || state.byEventId[eventId];
      if (!record || record.claimToken !== eventId) return { changed: false, saved: false, record: record ? { ...record } : null };
      record.code = code;
      record.codeId = codeId;
      record.status = 'pending_email';
      record.leaseUntil = 0;
      record.updatedAt = new Date().toISOString();
      state.byEventId[record.eventId] = record;
      state.byEventId[eventId] = record;
      state.bySessionId[record.sessionId] = record;
      return { changed: true, saved: true, record: { ...record } };
    });
  }

  async function claimDelivery({ sessionId, ownerToken }) {
    return mutate((state) => {
      const record = state.bySessionId[sessionId];
      if (!record || !record.code) return { changed: false, claimed: false, record: record ? { ...record } : null };
      const now = Date.now();
      if (record.status === 'delivered') return { changed: false, claimed: false, record: { ...record } };
      if (record.status === 'delivering' && record.leaseUntil > now && record.leaseOwner !== ownerToken) {
        return { changed: false, claimed: false, busy: true, record: { ...record } };
      }
      record.status = 'delivering';
      record.leaseOwner = ownerToken;
      record.leaseUntil = now + DELIVERY_LEASE_MS;
      record.updatedAt = new Date(now).toISOString();
      state.byEventId[record.eventId] = record;
      state.bySessionId[sessionId] = record;
      return { changed: true, claimed: true, busy: false, record: { ...record } };
    });
  }

  async function markDelivered({ sessionId, ownerToken, deliveredVia }) {
    return mutate((state) => {
      const record = state.bySessionId[sessionId];
      if (!record || record.leaseOwner !== ownerToken) return { changed: false, saved: false };
      record.status = 'delivered';
      record.deliveredVia = deliveredVia;
      record.deliveredAt = new Date().toISOString();
      record.lastError = null;
      record.leaseUntil = 0;
      record.leaseOwner = null;
      record.updatedAt = new Date().toISOString();
      state.byEventId[record.eventId] = record;
      state.bySessionId[sessionId] = record;
      return { changed: true, saved: true, record: { ...record } };
    });
  }

  async function markDeliveryFailed({ sessionId, ownerToken, error }) {
    return mutate((state) => {
      const record = state.bySessionId[sessionId];
      if (!record || record.leaseOwner !== ownerToken) return { changed: false, saved: false };
      record.status = 'pending_email';
      record.lastError = String(error || 'email delivery failed').slice(0, 500);
      record.leaseUntil = 0;
      record.leaseOwner = null;
      record.updatedAt = new Date().toISOString();
      state.byEventId[record.eventId] = record;
      state.bySessionId[sessionId] = record;
      return { changed: true, saved: true, record: { ...record } };
    });
  }

  return { filePath, readBySession, readByEvent, claim, saveLicense, claimDelivery, markDelivered, markDeliveryFailed };
}

module.exports = { createFulfillmentStore, DELIVERY_LEASE_MS };