import 'server-only';

import { createHash } from 'node:crypto';
import { readdir, readFile, stat } from 'node:fs/promises';
import path from 'node:path';

import { getCsvTemplateRegistry } from './csv/template-registry';
import type { CsvPreviewFileInput } from './csv/types';

const SETUP_IMPORT_ENV_KEY = 'SETUP_IMPORT_DIR';
const DEFAULT_SETUP_IMPORT_DIR = './setup/ALLA-ONVALLA';

export interface SetupImportSourceFile {
  fileName: string;
  kind: 'required' | 'optional';
  bytes: number;
  modifiedAt: string;
  checksum: string;
  headers: string[];
}

export interface SetupImportIgnoredFile {
  fileName: string;
  reason: string;
}

export interface SetupImportSourceReport {
  envKey: typeof SETUP_IMPORT_ENV_KEY;
  exampleValue: typeof DEFAULT_SETUP_IMPORT_DIR;
  configuredValue: string | null;
  configured: boolean;
  resolvedPath: string | null;
  exists: boolean;
  files: SetupImportSourceFile[];
  missingRequiredFiles: string[];
  ignoredFiles: SetupImportIgnoredFile[];
  errors: string[];
  warnings: string[];
}

export interface SetupImportSourceReadResult {
  report: SetupImportSourceReport;
  files: CsvPreviewFileInput[];
}

function sha256(bytes: Uint8Array) {
  return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
}

function readCsvHeaders(bytes: Uint8Array) {
  const text = new TextDecoder('utf-8', { fatal: false })
    .decode(bytes)
    .replace(/^\uFEFF/, '');
  const [header = ''] = text.split(/\r?\n/, 1);
  return header.split(',').map((column) => column.trim()).filter(Boolean);
}

function resolveConfiguredPath(configuredValue: string) {
  return path.isAbsolute(configuredValue)
    ? path.normalize(configuredValue)
    : path.resolve(process.cwd(), configuredValue);
}

function createBaseReport(configuredValue: string | null): SetupImportSourceReport {
  return {
    envKey: SETUP_IMPORT_ENV_KEY,
    exampleValue: DEFAULT_SETUP_IMPORT_DIR,
    configuredValue,
    configured: Boolean(configuredValue),
    resolvedPath: configuredValue ? resolveConfiguredPath(configuredValue) : null,
    exists: false,
    files: [],
    missingRequiredFiles: [],
    ignoredFiles: [],
    errors: [],
    warnings: []
  };
}

export async function readConfiguredSetupImportSource(input: {
  requiredFiles: readonly string[];
  optionalFiles: readonly string[];
}): Promise<SetupImportSourceReadResult> {
  const configuredValue = process.env[SETUP_IMPORT_ENV_KEY]?.trim() || null;
  const report = createBaseReport(configuredValue);

  if (!configuredValue || !report.resolvedPath) {
    report.errors.push(
      `Add ${SETUP_IMPORT_ENV_KEY}=${DEFAULT_SETUP_IMPORT_DIR} to .env and restart the server.`
    );
    report.missingRequiredFiles = [...input.requiredFiles];
    return { report, files: [] };
  }

  const registry = await getCsvTemplateRegistry();
  const supportedFiles = new Map(
    registry.templates
      .filter((template) => template.supported)
      .map((template) => [template.file.toLowerCase(), template])
  );
  const requiredFiles = new Set(input.requiredFiles.map((fileName) => fileName.toLowerCase()));
  const discovered = new Set<string>();
  const files: CsvPreviewFileInput[] = [];

  try {
    const directoryStat = await stat(report.resolvedPath);
    if (!directoryStat.isDirectory()) {
      report.errors.push(`${report.resolvedPath} is not a directory.`);
      report.missingRequiredFiles = [...input.requiredFiles];
      return { report, files: [] };
    }

    report.exists = true;
    const entries = await readdir(report.resolvedPath, { withFileTypes: true });

    for (const entry of entries) {
      if (!entry.isFile()) {
        continue;
      }

      const fileName = path.basename(entry.name);
      const normalized = fileName.toLowerCase();
      const template = supportedFiles.get(normalized);

      if (!template) {
        report.ignoredFiles.push({
          fileName,
          reason: 'File is not a registered supported setup CSV template.'
        });
        continue;
      }

      const filePath = path.join(report.resolvedPath, fileName);
      const [fileStat, bytes] = await Promise.all([stat(filePath), readFile(filePath)]);
      const byteArray = new Uint8Array(bytes);
      const kind = requiredFiles.has(normalized) ? 'required' : 'optional';

      discovered.add(normalized);
      files.push({ fileName: template.file, bytes: byteArray });
      report.files.push({
        fileName: template.file,
        kind,
        bytes: byteArray.byteLength,
        modifiedAt: fileStat.mtime.toISOString(),
        checksum: sha256(byteArray),
        headers: readCsvHeaders(byteArray)
      });
    }
  } catch (error) {
    report.errors.push(
      error instanceof Error
        ? `Unable to read ${report.resolvedPath}: ${error.message}`
        : `Unable to read ${report.resolvedPath}.`
    );
  }

  report.missingRequiredFiles = input.requiredFiles.filter(
    (fileName) => !discovered.has(fileName.toLowerCase())
  );

  if (report.missingRequiredFiles.length > 0) {
    report.errors.push(
      `Missing required setup CSV files: ${report.missingRequiredFiles.join(', ')}.`
    );
  }

  if (report.ignoredFiles.length > 0) {
    report.warnings.push('Some files in the setup import directory are ignored.');
  }

  files.sort((left, right) => {
    const leftTemplate = supportedFiles.get(left.fileName.toLowerCase());
    const rightTemplate = supportedFiles.get(right.fileName.toLowerCase());
    return (leftTemplate?.importOrder ?? 0) - (rightTemplate?.importOrder ?? 0);
  });
  report.files.sort((left, right) => {
    const leftTemplate = supportedFiles.get(left.fileName.toLowerCase());
    const rightTemplate = supportedFiles.get(right.fileName.toLowerCase());
    return (leftTemplate?.importOrder ?? 0) - (rightTemplate?.importOrder ?? 0);
  });

  return { report, files };
}
