import fs from 'node:fs';
import path from 'node:path';
import { ARTIFACTS_DIR } from './paths.js';
import { getProject, defaultProjectFolderPath, listProjects, DEFAULT_PROJECT_ID } from './projects.js';

const ARTIFACT_SUBDIR = 'artifacts';

function projectFolderPath(project) {
  if (project?.folder_path) return project.folder_path;
  if (project?.slug) return defaultProjectFolderPath(project.slug);
  return defaultProjectFolderPath(DEFAULT_PROJECT_ID);
}

export function artifactsDirForProject(projectOrId) {
  let project = null;
  if (projectOrId && typeof projectOrId === 'object') {
    project = projectOrId;
  } else {
    project = getProject(projectOrId || DEFAULT_PROJECT_ID) || null;
  }
  if (!project) {
    return path.join(defaultProjectFolderPath(DEFAULT_PROJECT_ID), ARTIFACT_SUBDIR);
  }
  return path.join(projectFolderPath(project), ARTIFACT_SUBDIR);
}

export function ensureArtifactDir(projectOrId) {
  const dir = artifactsDirForProject(projectOrId);
  fs.mkdirSync(dir, { recursive: true });
  return dir;
}

function activeProjectArtifactDirs() {
  try {
    return listProjects({ status: 'all' })
      .map((p) => path.resolve(artifactsDirForProject(p)));
  } catch {
    return [];
  }
}

export function isInsideAnyArtifactDir(candidateAbsPath) {
  if (!candidateAbsPath) return false;
  const abs = path.resolve(candidateAbsPath);
  const allowed = [
    path.resolve(ARTIFACTS_DIR),
    ...activeProjectArtifactDirs(),
  ];
  return allowed.some((root) => abs === root || abs.startsWith(`${root}${path.sep}`));
}

/**
 * Read an artifact from disk, decrypting if artifact.encrypted === 1.
 *
 * The `artifact.encrypted` row flag (not the env var) is the gate — this allows
 * mixed plaintext/encrypted rows to coexist safely across env-var flips.
 *
 * On GCM authentication failure (wrong org, corruption, or tampered bytes), logs
 * an audit row and returns { ok: false, error: 'decryption_failed' } instead of
 * leaking the raw GCM error to callers.
 *
 * @param {object} artifact - Row from the artifacts table.
 * @param {string} diskPath - Absolute path to the artifact file on disk.
 * @returns {{ ok: true, content: string } | { ok: false, error: string }}
 */
export async function readArtifactDisk(artifact, diskPath) {
  if (artifact.encrypted === 1) {
    try {
      const diskBuf = fs.readFileSync(diskPath); // binary read
      const { decryptFromDiskBuffer } = await import('./artifact-crypto.js');
      const orgId = artifact.encryption_key_id || 'personal-org';
      const plain = decryptFromDiskBuffer(orgId, diskBuf);
      return { ok: true, content: plain.toString('utf8') };
    } catch (err) {
      // Write audit row for the decryption failure — use dynamic import to
      // avoid a circular dep (artifact-paths ← runtime ← artifact-paths).
      try {
        const { appendAudit } = await import('./runtime.js');
        appendAudit({
          type: 'artifact.decryption_failed',
          summary: `Decryption failed for artifact ${artifact.id}`,
          status: 'error',
          meta: {
            artifact_id: artifact.id,
            encryption_key_id: artifact.encryption_key_id,
            error: err?.message || String(err),
          },
        });
      } catch { /* audit write must never surface to callers */ }
      return { ok: false, error: 'decryption_failed' };
    }
  }

  // Plaintext artifact.
  try {
    const content = fs.readFileSync(diskPath, 'utf8');
    return { ok: true, content };
  } catch (err) {
    return { ok: false, error: 'read_failed' };
  }
}

