#!/usr/bin/env node
/**
 * Seed Directus : crée les 2 collections (site_settings singleton + series),
 * leurs fields, les permissions publiques en lecture, et pre-remplit
 * le singleton avec les valeurs par défaut du site.
 *
 * Idempotent : skip ce qui existe déjà.
 *
 * Usage :
 *   node scripts/seed-directus.mjs
 *
 * Vars d'env attendues (default = valeurs du docker-compose) :
 *   DIRECTUS_PUBLIC_URL (default http://localhost:8055)
 *   DIRECTUS_ADMIN_EMAIL
 *   DIRECTUS_ADMIN_PASSWORD
 */

import 'node:process';

const URL_BASE = process.env.DIRECTUS_PUBLIC_URL || 'http://localhost:8055';
const EMAIL = process.env.DIRECTUS_ADMIN_EMAIL || 'corentin.jog@gmail.com';
const PASSWORD = process.env.DIRECTUS_ADMIN_PASSWORD || 'changeme-please';

let TOKEN = null;

async function api(method, path, body) {
  const headers = { 'Content-Type': 'application/json' };
  if (TOKEN) headers.Authorization = `Bearer ${TOKEN}`;
  const res = await fetch(`${URL_BASE}${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });
  const text = await res.text();
  let json;
  try { json = text ? JSON.parse(text) : null; } catch { json = text; }
  if (!res.ok) {
    const isAlreadyExists = res.status === 400 && JSON.stringify(json).includes('already exists');
    if (isAlreadyExists) return { skipped: true, json };
    throw new Error(`${method} ${path} → ${res.status}\n${JSON.stringify(json, null, 2)}`);
  }
  return json;
}

async function login() {
  const r = await api('POST', '/auth/login', { email: EMAIL, password: PASSWORD });
  TOKEN = r.data.access_token;
  console.log('✓ logged in as', EMAIL);
}

async function ensureCollection(collection, meta = {}, schema = {}) {
  try {
    await api('GET', `/collections/${collection}`);
    console.log(`= collection ${collection} already exists`);
    return false;
  } catch {
    await api('POST', '/collections', { collection, meta, schema });
    console.log(`✓ collection ${collection} created`);
    return true;
  }
}

async function ensureField(collection, field, type, meta = {}, schema = {}) {
  try {
    await api('GET', `/fields/${collection}/${field}`);
    return false;
  } catch {
    const body = { field, type, meta: { interface: 'input', ...meta }, schema };
    await api('POST', `/fields/${collection}`, body);
    console.log(`  + ${collection}.${field}`);
    return true;
  }
}

async function ensureRelation(collection, field, related_collection) {
  // Sans cet appel, l'UI Directus admin affiche le champ file vide même quand
  // un UUID est stocké en DB (l'API REST POST /fields ne crée pas la foreign key
  // par défaut). Idempotent : skip si la relation existe déjà.
  try {
    const existing = await api('GET', `/relations/${collection}/${field}`);
    if (existing?.data) {
      return false;
    }
  } catch {
    // 404 → relation n'existe pas, on continue
  }
  await api('POST', '/relations', { collection, field, related_collection });
  console.log(`  ↪ relation ${collection}.${field} → ${related_collection}`);
  return true;
}

async function ensureM2MFiles(collection, field) {
  // Setup d'un champ "gallery" M2M files (Directus 11 ne le fait pas auto via REST).
  // Crée la collection junction, ses 2 champs M2O, le champ alias, et les 2 relations.
  // Idempotent : skip ce qui existe déjà.
  const junction = `${collection}_files`;
  const fkOne = `${collection}_id`;
  const fkMany = 'directus_files_id';

  // 1. Collection junction
  try {
    await api('GET', `/collections/${junction}`);
  } catch {
    await api('POST', '/collections', {
      collection: junction,
      meta: { hidden: true, icon: 'import_export' },
      schema: {},
      fields: [{
        field: 'id',
        type: 'integer',
        meta: { hidden: true, interface: 'input', readonly: true },
        schema: { is_primary_key: true, has_auto_increment: true },
      }],
    });
    console.log(`  + collection ${junction}`);
  }

  // 2. Champs M2O dans la junction
  await ensureField(junction, fkOne, 'integer', { interface: 'select-dropdown-m2o', hidden: true });
  await ensureField(junction, fkMany, 'uuid', { interface: 'file', hidden: true });

  // 3. Champ alias dans la collection parente
  try {
    await api('GET', `/fields/${collection}/${field}`);
  } catch {
    await api('POST', `/fields/${collection}`, {
      field,
      type: 'alias',
      meta: {
        interface: 'files',
        special: ['files'],
        options: { enableCreate: true, enableSelect: true },
      },
      schema: null,
    });
    console.log(`  + ${collection}.${field} (alias files)`);
  }

  // 4. Relations M2M (idempotent par try/catch sur déjà existant)
  try {
    await api('GET', `/relations/${junction}/${fkMany}`);
  } catch {
    await api('POST', '/relations', {
      collection: junction,
      field: fkMany,
      related_collection: 'directus_files',
      meta: { junction_field: fkOne },
    });
    console.log(`  ↪ relation ${junction}.${fkMany} → directus_files`);
  }
  try {
    await api('GET', `/relations/${junction}/${fkOne}`);
  } catch {
    await api('POST', '/relations', {
      collection: junction,
      field: fkOne,
      related_collection: collection,
      meta: { one_field: field, junction_field: fkMany },
    });
    console.log(`  ↪ relation ${junction}.${fkOne} → ${collection} (M2M lié à .${field})`);
  }
}

async function ensurePublicRead(collection) {
  // Directus 11 : permissions sont attachees a des policies. La policy "Public"
  // est creee par defaut. On essaie de la trouver et de lui attacher read.
  try {
    const policies = await api('GET', `/policies?filter[name][_eq]=Public&limit=1`);
    const publicPolicy = policies?.data?.[0];
    if (!publicPolicy) {
      console.log(`  ! pas de policy Public trouvee, skip ${collection} (a faire via UI)`);
      return false;
    }
    await api('POST', '/permissions', {
      policy: publicPolicy.id,
      collection,
      action: 'read',
      fields: ['*'],
    });
    console.log(`  ✓ public read on ${collection}`);
    return true;
  } catch (e) {
    if (String(e.message).includes('already exists') || String(e.message).includes('RECORD_NOT_UNIQUE')) {
      return false;
    }
    console.log(`  ! permission ${collection} : ${e.message.split('\n')[0]} (a faire via UI Settings -> Access Policies -> Public)`);
    return false;
  }
}

async function setSingleton(collection, values) {
  // Seed seulement les champs vides — on n'écrase pas les modifs déjà saisies.
  // Le seed sert à pré-remplir un singleton fraichement créé, pas à le reset.
  let current = {};
  try {
    const r = await api('GET', `/items/${collection}`);
    current = r?.data || {};
  } catch {
    // singleton vide / inexistant
  }
  const toSet = {};
  for (const [k, v] of Object.entries(values)) {
    const cur = current[k];
    const isEmpty = cur === null || cur === undefined || cur === '' ||
      (Array.isArray(cur) && cur.length === 0);
    if (isEmpty) toSet[k] = v;
  }
  if (Object.keys(toSet).length === 0) {
    console.log(`= ${collection} déjà rempli, skip defaults`);
    return;
  }
  await api('PATCH', `/items/${collection}`, toSet);
  console.log(`✓ defaults set on ${collection} (${Object.keys(toSet).length} champ(s) vides remplis)`);
}

(async () => {
  console.log(`Seeding Directus at ${URL_BASE}\n`);
  await login();

  // ============ site_settings (singleton) ============
  console.log('\n=== site_settings (singleton) ===');
  await ensureCollection('site_settings', {
    icon: 'tune',
    singleton: true,
    note: 'Contenu marketing du site (hero, manifesto, contact, etc.). Une seule ligne.',
  });

  const settingsFields = [
    ['studio_name', 'string', { width: 'half' }],
    ['city', 'string', { width: 'half' }],
    ['region', 'string', { width: 'half' }],
    ['country', 'string', { width: 'half' }],
    ['coords', 'string', { width: 'half', note: '"43.55N 7.02E"' }],
    ['email', 'string', { width: 'half', interface: 'input', validation: { _and: [{ email: { _regex: '^[^@]+@[^@]+\\.[^@]+$' } }] } }],
    ['est_year', 'integer', { width: 'half' }],
    ['current_year', 'integer', { width: 'half' }],
    // hero
    ['hero_tag', 'string'],
    ['hero_display_lines', 'json', { interface: 'tags', note: 'Lignes display du hero, ex: ["STUDIO PHOTO.", "CANNES / PACA."]' }],
    ['hero_italic_line', 'string'],
    ['hero_lede', 'text'],
    ['hero_stat', 'string', { note: 'Signature courte hero, ex: "ON OBSERVE / ON ENREGISTRE / ON DERANGE PEU"' }],
    ['hero_year_prefix', 'string', { width: 'half' }],
    ['hero_year_suffix', 'string', { width: 'half' }],
    // manifesto
    ['manifesto_tag', 'string'],
    ['manifesto_italic', 'string'],
    ['manifesto_end', 'string'],
    ['manifesto_paragraphs', 'json', { interface: 'list', note: 'Array of paragraph strings' }],
    // pricing categories (4 cards on home)
    ['pricing_categories', 'json', { interface: 'list', note: 'Array of {num, name, slug, startsAt, formules}' }],
    // contact
    ['contact_title', 'string'],
    ['contact_body', 'text'],
    ['contact_addr', 'string'],
  ];

  for (const [name, type, meta = {}, schema = {}] of settingsFields) {
    await ensureField('site_settings', name, type, meta, schema);
  }

  await ensurePublicRead('site_settings');

  await setSingleton('site_settings', {
    studio_name: 'Mostuki Photo',
    city: 'CANNES',
    region: 'PACA',
    country: 'FR',
    coords: '43.55N 7.02E',
    email: 'corentin.jog@gmail.com',
    est_year: 2018,
    current_year: 2026,
    hero_tag: '[01] STUDIO.',
    hero_display_lines: ['STUDIO PHOTO.', 'CANNES / PACA.'],
    hero_italic_line: 'documentaire.',
    hero_lede: 'MARIAGE / PORTRAIT / REPORTAGE / EVENEMENTIEL. UNE METHODE EDITORIALE ET DOCUMENTAIRE QUI VAUT POUR TOUS LES TERRAINS.',
    hero_stat: 'ON OBSERVE · ON ENREGISTRE · ON DERANGE PEU',
    hero_year_prefix: '20',
    hero_year_suffix: '26',
    manifesto_tag: '[02] MANIFESTE.',
    manifesto_italic: 'moins de poses,',
    manifesto_end: 'PLUS DE REGARDS.',
    manifesto_paragraphs: [
      "STUDIO PHOTO BASE A CANNES, COUVERTURE PACA ET PARTOUT EN FRANCE. APPROCHE DOCUMENTAIRE : ON OBSERVE, ON ENREGISTRE, ON DERANGE PEU.",
      "QUATRE TERRAINS, UNE METHODE : MARIAGE, PORTRAIT / LIFESTYLE, REPORTAGE / EDITORIAL, EVENEMENTIEL. LE STYLE NE CHANGE PAS AVEC LE SUJET.",
    ],
    pricing_categories: [
      { num: '01', name: 'MARIAGE', slug: 'mariage', startsAt: '1 200€', formules: 'DEMI-JOURNEE / JOURNEE / SUR-MESURE' },
      { num: '02', name: 'PORTRAIT / LIFESTYLE', slug: 'portrait', startsAt: '400€', formules: 'SEANCE 1H / ETENDU / SUR-MESURE' },
      { num: '03', name: 'REPORTAGE / EDITORIAL', slug: 'reportage', startsAt: '500€', formules: 'DEMI-JOURNEE / JOURNEE / PROJET' },
      { num: '04', name: 'EVENEMENTIEL', slug: 'evenementiel', startsAt: '700€', formules: '2H / DEMI-JOURNEE / JOURNEE' },
    ],
    contact_title: 'ON SE PARLE ?',
    contact_body: "DECRIS-MOI TON PROJET EN QUELQUES LIGNES : TYPE DE SHOOT, DATE APPROXIMATIVE, LIEU, BUDGET INDICATIF. REPONSE SOUS 48H.",
    contact_addr: 'STUDIO MOSTUKI · CANNES · FR',
  });

  // ============ series (collection) ============
  console.log('\n=== series (collection) ===');
  await ensureCollection('series', {
    icon: 'collections',
    note: 'Series du portfolio. Une entree par shoot.',
    sort_field: 'sort',
  }, {});

  const seriesFields = [
    ['status', 'string', { interface: 'select-dropdown', options: { choices: [{ text: 'Publie', value: 'published' }, { text: 'Brouillon', value: 'draft' }, { text: 'Archive', value: 'archived' }] } }, { default_value: 'published' }],
    ['sort', 'integer', { interface: 'input', hidden: true }],
    ['index', 'integer', { width: 'half' }],
    ['category', 'string', { width: 'half', interface: 'select-dropdown', options: { choices: [
      { text: 'Mariage', value: 'mariage' },
      { text: 'Portrait / Lifestyle', value: 'portrait-lifestyle' },
      { text: 'Reportage / Editorial', value: 'reportage-editorial' },
      { text: 'Evenementiel', value: 'evenementiel' },
    ] } }],
    ['title', 'string'],
    ['lieu', 'string', { width: 'half' }],
    ['date_shoot', 'string', { width: 'half', note: 'Format libre ex: "Mai 2026" ou "05/2026"' }],
    ['cover_alt', 'string'],
    ['excerpt', 'text', { interface: 'input-multiline' }],
    ['cover', 'uuid', { width: 'half', interface: 'file-image', special: ['file'] }],
    // mariage-specific (optional)
    ['couple', 'string', { width: 'half', note: 'Mariage uniquement' }],
    ['invites', 'integer', { width: 'half', note: 'Mariage uniquement' }],
    ['formule', 'string', { width: 'half', interface: 'select-dropdown', options: { choices: [
      { text: 'Demi-journee', value: 'demi-journee' },
      { text: 'Journee complete', value: 'journee-complete' },
      { text: 'Sur-mesure', value: 'sur-mesure' },
    ] }, note: 'Mariage uniquement' }],
    // generic (optional)
    ['client', 'string', { width: 'half', note: 'Reportage / Evenementiel' }],
    ['duree', 'string', { width: 'half', note: 'Ex: "2h", "Journee"' }],
    ['published', 'boolean', { width: 'half', interface: 'boolean' }, { default_value: true }],
  ];

  for (const [name, type, meta = {}, schema = {}] of seriesFields) {
    await ensureField('series', name, type, meta, schema);
  }

  await ensureRelation('series', 'cover', 'directus_files');
  await ensureM2MFiles('series', 'gallery');

  await ensurePublicRead('series');
  await ensurePublicRead('directus_files');

  console.log('\n=== Done ===');
  console.log(`\n→ Open ${URL_BASE} and login with ${EMAIL}`);
  console.log(`→ site_settings has been pre-filled with defaults`);
  console.log(`→ series collection is empty — add entries via the UI`);
})().catch((e) => {
  console.error('\nFAILED:', e.message);
  process.exit(1);
});
