// lib/tools/adapters/mines/project-crud.js
//
// Adapters for mines_project_create and mines_project_read.

import fs from 'node:fs';
import path from 'node:path';
import { getDb, nowIso, newId } from '../../../db.js';
import { createProject, ensureProjectRootFolder, slugifyProject } from '../../../projects.js';
import { getRootFolder } from '../../../disk.js';
import { appendComplianceAudit } from '../../../compliance-audit.js';
import { WORKSPACE } from '../../../paths.js';

// Resolve (or lazily create) the default portfolio every project entry hangs
// off. The portfolio list / project pages route by portfolio-entry id, so a
// project with no entry is invisible in the UI — every project gets one.
function ensureDefaultPortfolio(db, now) {
  const existing = db.prepare('SELECT id FROM mines_portfolios ORDER BY created_at ASC LIMIT 1').get();
  if (existing) return existing.id;
  const id = newId('mpf');
  db.prepare('INSERT INTO mines_portfolios (id, name, created_at, updated_at) VALUES (?, ?, ?, ?)').run(id, 'My Portfolio', now, now);
  return id;
}

// ── minesProjectCreate ────────────────────────────────────────────────────────

/**
 * Create a mine project — writes an OS projects row, a mines_projects row,
 * a disk root folder, and workspace files.
 *
 * @param {object} input
 * @param {string}  input.name               Required.
 * @param {string}  [input.slug]             Lowercased kebab-case. Auto-derived from name if missing.
 * @param {string}  [input.asset_stage]      Default: 'exploration'.
 * @param {string}  [input.commodity]
 * @param {string}  [input.deposit_type]
 * @param {string}  [input.portfolio_entry_id]  If set, links that entry to this project.
 * @param {string}  [input.description]
 *
 * @param {object}  options
 * @param {boolean} [options.owner_trusted]
 * @param {string}  [options.acting_on_behalf_of_user_id]
 * @param {string}  [options.user_id]
 *
 * @returns {Promise<object>} { status, project_id, os_project_id, slug, disk_folder_id }
 */
export async function minesProjectCreate(input, options = {}) {
  const {
    name,
    slug: slugInput,
    asset_stage = 'exploration',
    commodity = null,
    deposit_type = null,
    portfolio_entry_id = null,
    description = null,
  } = input || {};

  if (!name || !String(name).trim()) {
    return { status: 'error', error: 'name_required' };
  }

  const db = getDb();
  const now = nowIso();

  // Determine slug — slugify from input or name.
  const desiredSlug = slugifyProject(slugInput || name);

  // Check if mines_projects slug is already taken.
  const slugTaken = db.prepare('SELECT id FROM mines_projects WHERE slug = ? LIMIT 1').get(desiredSlug);
  if (slugTaken) {
    return { status: 'error', error: 'slug_in_use', slug: desiredSlug };
  }

  // Validate the portfolio-entry link UP FRONT so we never create an OS/mines
  // project and then silently fail (or silently overwrite an existing link).
  if (portfolio_entry_id) {
    const entryRow = db.prepare(
      'SELECT id, project_id FROM mines_portfolio_entries WHERE id = ? LIMIT 1'
    ).get(portfolio_entry_id);
    if (!entryRow) {
      return { status: 'error', error: 'entry_not_found', portfolio_entry_id };
    }
    if (entryRow.project_id) {
      return { status: 'error', error: 'entry_already_linked', portfolio_entry_id };
    }
  }

  // 1. Create OS project row via createProject().
  //    createProject throws with code 'PROJECT_SLUG_EXISTS' if slug is taken;
  //    we catch that and return a clean error.
  let osProject;
  try {
    osProject = createProject({
      name: String(name).trim(),
      slug: desiredSlug,
      description: description || 'Mine project: ' + String(name).trim(),
      color: '#2A5C45', // dark green — mines brand
    });
  } catch (err) {
    if (err?.code === 'PROJECT_SLUG_EXISTS') {
      return { status: 'error', error: 'slug_in_use', slug: desiredSlug };
    }
    throw err;
  }

  const os_project_id = osProject.id;
  const slug = osProject.slug;

  // 2. Insert mines_projects row + link the portfolio entry in ONE transaction
  //    so a failure can't leave a half-linked record. (createProject already
  //    committed the OS project row; a UNIQUE failure here is only reachable via
  //    a concurrent TOCTOU race in this single-user app and rolls back cleanly.)
  //    BEGIN/COMMIT/ROLLBACK run via prepare().run() — the equivalent of
  //    db.exec() for node:sqlite, kept this form for static-analysis hygiene.
  const mines_project_id = newId('mp');
  let created_entry_id = portfolio_entry_id || null;
  db.prepare('BEGIN').run();
  try {
    db.prepare(`
      INSERT INTO mines_projects
        (id, os_project_id, name, slug, asset_stage, commodity, deposit_type,
         country, state_province, has_technical_report, data_completeness, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, NULL, ?, ?)
    `).run(
      mines_project_id,
      os_project_id,
      String(name).trim(),
      slug,
      asset_stage || 'exploration',
      commodity || null,
      deposit_type || null,
      'Mexico',
      now,
      now
    );

    if (portfolio_entry_id) {
      // Re-assert project_id IS NULL to guard against a concurrent link between
      // the up-front check and here. changes === 0 means it was taken — abort.
      const linked = db.prepare(
        'UPDATE mines_portfolio_entries SET project_id = ?, updated_at = ? WHERE id = ? AND project_id IS NULL'
      ).run(mines_project_id, now, portfolio_entry_id);
      if (!linked || linked.changes === 0) {
        throw Object.assign(new Error('entry_link_failed'), { code: 'ENTRY_LINK_FAILED' });
      }
    } else {
      // No existing entry to link → create one so the project is visible and
      // navigable in the portfolio UI.
      created_entry_id = newId('mpe');
      const portfolioId = ensureDefaultPortfolio(db, now);
      db.prepare(`
        INSERT INTO mines_portfolio_entries
          (id, portfolio_id, project_id, name, commodity, deposit_type, has_drilling, created_at, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
      `).run(created_entry_id, portfolioId, mines_project_id, String(name).trim(), commodity || null, deposit_type || null, now, now);
    }

    db.prepare('COMMIT').run();
  } catch (err) {
    try { db.prepare('ROLLBACK').run(); } catch {}
    if (err?.code === 'ENTRY_LINK_FAILED') {
      return { status: 'error', error: 'entry_link_failed', portfolio_entry_id };
    }
    if (/UNIQUE constraint failed/i.test(String(err?.message || ''))) {
      return { status: 'error', error: 'slug_in_use', slug };
    }
    throw err;
  }

  // 3. Ensure disk root folder exists and capture its id.
  let disk_folder_id = null;
  try {
    disk_folder_id = ensureProjectRootFolder(os_project_id, slug, db);
    if (!disk_folder_id) {
      // getRootFolder triggers ensureProjectRootFolder internally; call it for the side-effect.
      const rootFolder = getRootFolder(os_project_id);
      disk_folder_id = rootFolder?.id ?? null;
    }
  } catch {
    // Non-fatal — folder creation failure should not block the project record.
  }

  // 5. Write workspace files under workspace/agents/mines-analyst/projects/<mines_project_id>/.
  //    This mirrors the createAgent pattern. Uses WORKSPACE (overridable via
  //    VILLA515_WORKSPACE in tests, so isolation is guaranteed).
  try {
    const projectWorkDir = path.join(
      WORKSPACE,
      'agents',
      'mines-analyst',
      'projects',
      mines_project_id
    );
    fs.mkdirSync(projectWorkDir, { recursive: true });

    const projectJson = {
      id: mines_project_id,
      os_project_id,
      name: String(name).trim(),
      slug,
      asset_stage: asset_stage || 'exploration',
      commodity: commodity || null,
      deposit_type: deposit_type || null,
      country: 'Mexico',
      created_at: now,
    };

    fs.writeFileSync(
      path.join(projectWorkDir, 'project.json'),
      JSON.stringify(projectJson, null, 2)
    );

    fs.writeFileSync(
      path.join(projectWorkDir, 'README.md'),
      [
        '# ' + String(name).trim(),
        '',
        '**Mines Project ID:** `' + mines_project_id + '`  ',
        '**OS Project ID:** `' + os_project_id + '`  ',
        '**Slug:** `' + slug + '`  ',
        '**Asset Stage:** ' + (asset_stage || 'exploration'),
        commodity ? '**Commodity:** ' + commodity : '',
        deposit_type ? '**Deposit Type:** ' + deposit_type : '',
        '',
        '_Created: ' + now + '_',
      ].filter(line => line !== '').join('\n')
    );
  } catch {
    // Workspace write failure is non-fatal.
  }

  // 6. Compliance audit row.
  try {
    appendComplianceAudit({
      user_id: options.acting_on_behalf_of_user_id || options.user_id || 'system',
      org_id: 'personal-org',
      action: 'mines.project.created',
      status: 'success',
      summary: 'Created mine project "' + String(name).trim() + '" (slug: ' + slug + ')',
      action_target_id: mines_project_id,
      action_target_type: 'mines_project',
      meta: { mines_project_id, os_project_id, slug, portfolio_entry_id },
    });
  } catch {
    // Non-fatal.
  }

  return {
    status: 'ok',
    project_id: mines_project_id,
    os_project_id,
    slug,
    disk_folder_id,
    portfolio_entry_id: created_entry_id,
  };
}

// ── minesProjectRead ──────────────────────────────────────────────────────────

/**
 * Read a mine project record with document/sample/drill-hole counts.
 *
 * @param {object} input
 * @param {string} input.project_id   mines_projects.id
 *
 * @param {object} options
 *
 * @returns {Promise<object>} { status, project, portfolio_entry, counts, last_import_at }
 */
export async function minesProjectRead(input, options = {}) {
  const { project_id } = input || {};

  if (!project_id) {
    return { status: 'error', error: 'project_id_required' };
  }

  const db = getDb();

  const pRow = db.prepare(`
    SELECT id, os_project_id, name, slug, asset_stage, commodity, deposit_type,
           country, state_province, has_technical_report, data_completeness,
           created_at, updated_at
    FROM mines_projects
    WHERE id = ?
  `).get(project_id);

  if (!pRow) {
    return { status: 'error', error: 'not_found' };
  }

  const project = {
    id: pRow.id,
    name: pRow.name,
    slug: pRow.slug,
    asset_stage: pRow.asset_stage,
    commodity: pRow.commodity ?? null,
    deposit_type: pRow.deposit_type ?? null,
    country: pRow.country,
    state_province: pRow.state_province ?? null,
    has_technical_report: pRow.has_technical_report === 1 ? 1 : 0,
    data_completeness: pRow.data_completeness ?? null,
  };

  // Linked portfolio entry (if any).
  const peRow = db.prepare(`
    SELECT id, portfolio_id, name, commodity, deposit_type, owner,
           available_information, has_drilling, deal_terms, data_completeness, notes
    FROM mines_portfolio_entries
    WHERE project_id = ?
    LIMIT 1
  `).get(project_id);

  const portfolio_entry = peRow ? {
    id: peRow.id,
    portfolio_id: peRow.portfolio_id,
    name: peRow.name,
    commodity: peRow.commodity ?? null,
    deposit_type: peRow.deposit_type ?? null,
    owner: peRow.owner ?? null,
    available_information: peRow.available_information ?? null,
    has_drilling: peRow.has_drilling === 1 ? 1 : 0,
    deal_terms: peRow.deal_terms ?? null,
    data_completeness: peRow.data_completeness ?? null,
    notes: peRow.notes ?? null,
  } : null;

  // Document / assay / drill-hole counts.
  const docsRow = db.prepare(
    'SELECT COUNT(*) AS n, MAX(created_at) AS last_at FROM mines_source_documents WHERE project_id = ?'
  ).get(project_id);
  const assaysRow = db.prepare(
    'SELECT COUNT(*) AS n FROM mines_assay_samples WHERE project_id = ?'
  ).get(project_id);
  const drillRow = db.prepare(
    'SELECT COUNT(*) AS n FROM mines_drill_holes WHERE project_id = ?'
  ).get(project_id);

  const counts = {
    documents: docsRow?.n ?? 0,
    assays: assaysRow?.n ?? 0,
    drill_holes: drillRow?.n ?? 0,
  };
  const last_import_at = docsRow?.last_at ?? null;

  return {
    status: 'ok',
    project,
    portfolio_entry,
    counts,
    last_import_at,
  };
}
