import { NextRequest, NextResponse } from 'next/server';

import { commitSetupCsvImport } from '@/features/setup/server/csv-commit.service';
import { createCsvImportSeedManifest } from '@/features/setup/server/seed-manifest.service';
import type { ImportReport } from '@/features/setup/server/csv/import-report';
import type { CsvPreviewFileInput } from '@/features/setup/server/csv/types';
import {
  assertBootstrapSetupAvailable,
  assertBootstrapStepFiles,
  assertBootstrapToken,
  BOOTSTRAP_ACTOR_ID,
  BOOTSTRAP_OPTIONAL_TEMPLATE_FILES,
  BOOTSTRAP_REQUIRED_STEPS,
  BOOTSTRAP_REQUIRED_TEMPLATE_FILES,
  BootstrapSetupError,
  setupStepStatusFromImportReport,
  setupStepStatusFromReportStatus,
  toSerializableRecord
} from '@/features/setup/server/setup-bootstrap.service';
import { readConfiguredSetupImportSource } from '@/features/setup/server/setup-import-source.service';
import { saveSetupStep, startSetupRun } from '@/features/setup/server/setup-state.service';

function isFile(value: FormDataEntryValue): value is File {
  return (
    typeof value === 'object' &&
    'arrayBuffer' in value &&
    'name' in value &&
    typeof value.name === 'string' &&
    value.name.trim().length > 0
  );
}

function parseBoolean(value: FormDataEntryValue | string | boolean | undefined | null): boolean {
  if (typeof value === 'boolean') return value;
  if (typeof value !== 'string') return false;
  return value.trim().toLowerCase() === 'true';
}

async function readMultipartFiles(formData: FormData): Promise<CsvPreviewFileInput[]> {
  const uploadedFiles = Array.from(formData.values()).filter(isFile);

  return Promise.all(
    uploadedFiles.map(async (file) => ({
      fileName: file.name,
      bytes: new Uint8Array(await file.arrayBuffer())
    }))
  );
}

async function saveConfiguredCommitSteps(report: ImportReport) {
  await Promise.all(
    BOOTSTRAP_REQUIRED_STEPS.map((step) => {
      const file = report.files.find(
        (candidate) => candidate.fileName.toLowerCase() === step.fileName.toLowerCase()
      );

      if (!file) return Promise.resolve();

      return saveSetupStep({
        stepKey: step.stepKey,
        status: setupStepStatusFromReportStatus(file.status),
        inputHash: report.committedHash,
        output: toSerializableRecord({
          status: file.status,
          generatedAt: report.generatedAt,
          previewHash: report.previewHash,
          committedHash: report.committedHash,
          dryRun: report.dryRun,
          file: {
            fileName: file.fileName,
            template: file.template,
            status: file.status,
            imported: file.imported,
            updated: file.updated,
            skipped: file.skipped,
            errors: file.errors,
            warnings: file.warnings
          }
        }),
        errors: file.errors,
        warnings: file.warnings,
        skipStaleInvalidation: true
      });
    })
  );
}

async function commitConfiguredDirectory(request: NextRequest) {
  const body = (await request.json().catch(() => ({}))) as {
    bootstrapToken?: string;
    source?: string;
    previewHash?: string;
    dryRun?: boolean | string;
  };

  assertBootstrapToken(body.bootstrapToken ?? request.headers.get('x-bootstrap-setup-token') ?? '');

  if (body.source !== 'configured-directory') {
    return NextResponse.json({ message: 'Unsupported bootstrap commit source.' }, { status: 400 });
  }

  const source = await readConfiguredSetupImportSource({
    requiredFiles: BOOTSTRAP_REQUIRED_TEMPLATE_FILES,
    optionalFiles: BOOTSTRAP_OPTIONAL_TEMPLATE_FILES
  });

  if (source.report.errors.length > 0) {
    return NextResponse.json(
      { message: 'Configured setup import folder is not ready.', importSource: source.report },
      { status: 400 }
    );
  }

  await startSetupRun({ actorId: BOOTSTRAP_ACTOR_ID, mode: 'production', targetOrganizationId: null });

  const report = await commitSetupCsvImport({
    files: source.files,
    previewHash: body.previewHash ?? '',
    dryRun: parseBoolean(body.dryRun),
    actorId: BOOTSTRAP_ACTOR_ID
  });

  if (!report.dryRun && report.status !== 'FAIL') {
    await createCsvImportSeedManifest({ files: source.files, report, actorId: BOOTSTRAP_ACTOR_ID });
  }

  await saveConfiguredCommitSteps(report);

  return NextResponse.json({ ...report, importSource: source.report }, { status: report.status === 'FAIL' ? 400 : 200 });
}

async function commitUploadedStep(request: NextRequest) {
  const formData = await request.formData();
  assertBootstrapToken(String(formData.get('bootstrapToken') ?? request.headers.get('x-bootstrap-setup-token') ?? ''));

  const stepKey = String(formData.get('stepKey') ?? '');
  const previewHash = String(formData.get('previewHash') ?? '');
  const dryRun = parseBoolean(formData.get('dryRun'));
  const files = await readMultipartFiles(formData);

  if (files.length === 0) {
    return NextResponse.json(
      { message: 'Upload at least one setup CSV file using multipart/form-data.' },
      { status: 400 }
    );
  }

  const step = assertBootstrapStepFiles(stepKey, files);
  await startSetupRun({ actorId: BOOTSTRAP_ACTOR_ID, mode: 'production', targetOrganizationId: null });

  const report = await commitSetupCsvImport({
    files,
    previewHash,
    dryRun,
    actorId: BOOTSTRAP_ACTOR_ID
  });

  if (!dryRun && report.status !== 'FAIL') {
    await createCsvImportSeedManifest({ files, report, actorId: BOOTSTRAP_ACTOR_ID });
  }

  await saveSetupStep({
    stepKey: step.stepKey,
    status: setupStepStatusFromImportReport(report),
    inputHash: report.committedHash,
    output: toSerializableRecord(report),
    errors: report.errors,
    warnings: report.warnings
  });

  return NextResponse.json(report, { status: report.status === 'FAIL' ? 400 : 200 });
}

export async function POST(request: NextRequest) {
  try {
    await assertBootstrapSetupAvailable();

    if (request.headers.get('content-type')?.includes('application/json')) {
      return await commitConfiguredDirectory(request);
    }

    return await commitUploadedStep(request);
  } catch (error) {
    if (error instanceof BootstrapSetupError) {
      return NextResponse.json({ message: error.message, details: error.details }, { status: error.status });
    }

    if (error instanceof TypeError && /multipart\/form-data|application\/x-www-form-urlencoded/i.test(error.message)) {
      return NextResponse.json({ message: 'Upload setup CSV files using multipart/form-data.' }, { status: 400 });
    }

    return NextResponse.json({ message: 'Unable commit bootstrap setup CSV import' }, { status: 500 });
  }
}
