require('dotenv').config({ path: __dirname + '/.env' });

const NOCODB_URL = process.env.NOCODB_URL || 'http://localhost:8090';
const NOCODB_TOKEN = process.env.NOCODB_TOKEN;
const NOCODB_BASE_ID = process.env.NOCODB_BASE_ID;

class NocoDBError extends Error {
  constructor(message, status, details) {
    super(message);
    this.name = 'NocoDBError';
    this.status = status;
    this.details = details;
  }
}

// Cache: table name → table ID
const tableIdCache = {};

async function resolveTableId(tableName) {
  if (tableIdCache[tableName]) return tableIdCache[tableName];

  const res = await fetch(`${NOCODB_URL}/api/v2/meta/bases/${NOCODB_BASE_ID}/tables`, {
    headers: { 'xc-token': NOCODB_TOKEN },
  });
  if (!res.ok) throw new NocoDBError('Failed to fetch tables', res.status);
  const data = await res.json();
  for (const t of data.list || []) {
    tableIdCache[t.title] = t.id;
  }
  if (!tableIdCache[tableName]) {
    throw new NocoDBError(`Table "${tableName}" not found in base ${NOCODB_BASE_ID}`, 404);
  }
  return tableIdCache[tableName];
}

function buildWhere(conditions) {
  if (!conditions || conditions.length === 0) return '';
  return conditions
    .map(c => `(${c.field},${c.op},${c.value})`)
    .join('~and');
}

async function request(method, url, body) {
  const opts = {
    method,
    headers: {
      'xc-token': NOCODB_TOKEN,
      'Content-Type': 'application/json',
    },
  };
  if (body !== undefined) opts.body = JSON.stringify(body);

  const res = await fetch(url, opts);
  if (!res.ok) {
    let details;
    try { details = await res.json(); } catch {}
    throw new NocoDBError(
      `NocoDB ${method} ${url} failed: ${res.status}`,
      res.status,
      details
    );
  }
  // DELETE returns empty or {msg}
  const text = await res.text();
  return text ? JSON.parse(text) : {};
}

// ─── Link Resolution ─────────────────────────────────────────

// Cache: "Table.Field" → { colId, tableId }
const linkColCache = {};

async function getLinkColId(table, linkField) {
  const key = `${table}.${linkField}`;
  if (linkColCache[key]) return linkColCache[key];
  const tableId = await resolveTableId(table);
  const res = await fetch(`${NOCODB_URL}/api/v2/meta/tables/${tableId}`, {
    headers: { 'xc-token': NOCODB_TOKEN },
  });
  if (!res.ok) throw new NocoDBError('Failed to fetch table metadata', res.status);
  const meta = await res.json();
  for (const c of meta.columns || []) {
    if (c.uidt === 'Links' || c.uidt === 'LinkToAnotherRecord') {
      linkColCache[`${table}.${c.title}`] = { colId: c.id, tableId };
    }
  }
  return linkColCache[key] || null;
}

async function fetchLinkedRecords(table, recordId, linkField) {
  const info = await getLinkColId(table, linkField);
  if (!info) return [];
  try {
    const data = await request('GET',
      `${NOCODB_URL}/api/v2/tables/${info.tableId}/links/${info.colId}/records/${recordId}`);
    return data.list || [];
  } catch { return []; }
}

async function resolveLinks(table, records, linkFields) {
  if (!records || !linkFields || linkFields.length === 0) return;
  const arr = Array.isArray(records) ? records : [records];
  const promises = [];
  for (const record of arr) {
    for (const field of linkFields) {
      const val = record[field];
      if (typeof val === 'number' && val > 0) {
        promises.push(
          fetchLinkedRecords(table, record.Id, field)
            .then(linked => { record[field] = linked; })
        );
      } else if (typeof val === 'number') {
        record[field] = [];
      }
    }
  }
  await Promise.all(promises);
}

const nocodb = {
  /**
   * List records with optional filtering, sorting, pagination.
   * Pass `links: ['Field1','Field2']` to resolve linked records.
   */
  async list(table, { where, sort, fields, limit, offset, links } = {}) {
    const tableId = await resolveTableId(table);
    const params = new URLSearchParams();
    if (where) params.set('where', typeof where === 'string' ? where : buildWhere(where));
    if (sort) params.set('sort', sort);
    if (fields) params.set('fields', Array.isArray(fields) ? fields.join(',') : fields);
    if (limit) params.set('limit', String(limit));
    if (offset) params.set('offset', String(offset));
    const qs = params.toString();
    const data = await request('GET', `${NOCODB_URL}/api/v2/tables/${tableId}/records${qs ? '?' + qs : ''}`);
    const records = data.list || [];
    if (links && links.length > 0) {
      await resolveLinks(table, records, links);
    }
    return records;
  },

  /**
   * Get a single record by row ID.
   * Pass `{ links: ['Field1'] }` as third arg to resolve linked records.
   */
  async get(table, rowId, { links } = {}) {
    const tableId = await resolveTableId(table);
    const record = await request('GET', `${NOCODB_URL}/api/v2/tables/${tableId}/records/${rowId}`);
    if (links && links.length > 0) {
      await resolveLinks(table, [record], links);
    }
    return record;
  },

  /**
   * Create a single record, returns the created record
   */
  async create(table, data) {
    const tableId = await resolveTableId(table);
    return request('POST', `${NOCODB_URL}/api/v2/tables/${tableId}/records`, data);
  },

  /**
   * Update a single record by row ID
   */
  async update(table, rowId, data) {
    const tableId = await resolveTableId(table);
    return request('PATCH', `${NOCODB_URL}/api/v2/tables/${tableId}/records`, { Id: rowId, ...data });
  },

  /**
   * Delete a single record by row ID
   */
  async delete(table, rowId) {
    const tableId = await resolveTableId(table);
    return request('DELETE', `${NOCODB_URL}/api/v2/tables/${tableId}/records`, { Id: rowId });
  },

  /**
   * Bulk create records
   */
  async bulkCreate(table, records) {
    const tableId = await resolveTableId(table);
    return request('POST', `${NOCODB_URL}/api/v2/tables/${tableId}/records`, records);
  },

  /**
   * Bulk update records (each must include Id)
   */
  async bulkUpdate(table, records) {
    const tableId = await resolveTableId(table);
    return request('PATCH', `${NOCODB_URL}/api/v2/tables/${tableId}/records`, records);
  },

  /**
   * Bulk delete records (each must include Id)
   */
  async bulkDelete(table, records) {
    const tableId = await resolveTableId(table);
    return request('DELETE', `${NOCODB_URL}/api/v2/tables/${tableId}/records`, records);
  },

  // Expose helpers
  buildWhere,
  resolveTableId,
  getLinkColId,
  NocoDBError,
  clearCache() { Object.keys(tableIdCache).forEach(k => delete tableIdCache[k]); },

  // Config getters
  get url() { return NOCODB_URL; },
  get token() { return NOCODB_TOKEN; },
  get baseId() { return NOCODB_BASE_ID; },
};

module.exports = nocodb;
