import path from 'node:path';
import { findFiles } from '../../../lib/fs.js';
import { detectDbContexts, findStartupProject, hasDotnetProject } from '../lib/detect-dbcontexts.js';
import { runDotnet, dotnetAvailable, dotnetEfAvailable } from '../lib/ef-runner.js';
import { buildMigrationName } from '../lib/migration-name.js';
import type { CreateSpec, CreateResult, CreateAssemblyResult } from './types.js';

function capitalize(s: string): string {
  if (!s) return s;
  if (s.toLowerCase() === 'sqlserver') return 'SqlServer';
  return s.charAt(0).toUpperCase() + s.slice(1);
}

async function countExistingMigrations(migrationsDir: string): Promise<number> {
  const files = await findFiles('*.cs', { cwd: migrationsDir });
  return files.filter(
    (f) =>
      !f.endsWith('ModelSnapshot.cs') &&
      !f.endsWith('.Designer.cs') &&
      path.dirname(f) === migrationsDir,
  ).length;
}

export async function execute(spec: CreateSpec): Promise<CreateResult> {
  const warnings: string[] = [];

  if (!(await hasDotnetProject(spec.cwd))) {
    return {
      success: false,
      cwd: spec.cwd,
      contextName: spec.contextName,
      assemblies: [],
      warnings,
      error: 'No .csproj in worktree — cannot create a migration.',
    };
  }

  if (!spec.dryRun) {
    if (!(await dotnetAvailable())) {
      return {
        success: false,
        cwd: spec.cwd,
        contextName: spec.contextName,
        assemblies: [],
        warnings,
        error: 'dotnet CLI is not available on PATH',
      };
    }
    if (!(await dotnetEfAvailable())) {
      return {
        success: false,
        cwd: spec.cwd,
        contextName: spec.contextName,
        assemblies: [],
        warnings,
        error: 'dotnet-ef is not installed — run: dotnet tool install --global dotnet-ef',
      };
    }
  }

  const detection = await detectDbContexts(spec.cwd);
  const ctx = detection.contexts.find((c) => c.name === spec.contextName);
  if (!ctx) {
    return {
      success: false,
      cwd: spec.cwd,
      contextName: spec.contextName,
      assemblies: [],
      warnings,
      error: `Context "${spec.contextName}" not found in worktree. Detected: ${detection.contexts.map((c) => c.name).join(', ') || 'none'}`,
    };
  }

  const targetAssemblies = spec.assembly
    ? ctx.assemblies.filter((a) => a.assemblyName === spec.assembly)
    : spec.allProviders
      ? ctx.assemblies
      : [ctx.assemblies[0]].filter(Boolean);

  if (targetAssemblies.length === 0) {
    return {
      success: false,
      cwd: spec.cwd,
      contextName: spec.contextName,
      assemblies: [],
      warnings,
      error: spec.assembly
        ? `Assembly "${spec.assembly}" not found for context ${spec.contextName}`
        : `No migration assembly detected for context ${spec.contextName}`,
    };
  }

  const startupProject = spec.startupProject ?? (await findStartupProject(spec.cwd)) ?? undefined;
  if (!startupProject && !spec.dryRun) {
    warnings.push('No startup project detected — dotnet ef may fail. Pass --startupProject if necessary.');
  }

  const results: CreateAssemblyResult[] = [];

  for (const asm of targetAssemblies) {
    const sequence = (await countExistingMigrations(asm.migrationsDir)) + 1;
    const migrationName = buildMigrationName({
      contextName: spec.contextName,
      version: asm.version,
      sequence,
      description: spec.description,
    });

    if (spec.dryRun) {
      results.push({
        assemblyName: asm.assemblyName,
        migrationName,
        csprojPath: asm.csprojPath,
        output: `[dry-run] dotnet ef migrations add ${migrationName} --project ${asm.csprojPath} --context ${spec.contextName}${startupProject ? ` --startup-project ${startupProject}` : ''}`,
        success: true,
        filesCreated: [],
      });
      continue;
    }

    const extraEnv = asm.provider ? { STUDIO_DESIGN_PROVIDER: capitalize(asm.provider) } : undefined;

    const args = [
      'ef', 'migrations', 'add', migrationName,
      '--project', asm.csprojPath,
      '--context', spec.contextName,
      '--no-color',
    ];
    if (startupProject) {
      args.push('--startup-project', startupProject);
    }

    const r = await runDotnet(args, spec.cwd, { timeoutMs: 5 * 60_000, extraEnv });
    const success = r.exitCode === 0;

    const filesCreated = success
      ? [
          path.join(asm.migrationsDir, `${migrationName}.cs`),
          path.join(asm.migrationsDir, `${migrationName}.Designer.cs`),
          asm.snapshotPath,
        ]
      : [];

    results.push({
      assemblyName: asm.assemblyName,
      migrationName,
      csprojPath: asm.csprojPath,
      output: `${r.stdout}${r.stderr ? `\n${r.stderr}` : ''}`,
      success,
      error: success ? undefined : r.stderr || r.stdout || 'dotnet ef migrations add failed',
      filesCreated,
    });
  }

  const allOk = results.every((r) => r.success);
  return {
    success: allOk,
    cwd: detection.cwd,
    contextName: spec.contextName,
    assemblies: results,
    warnings,
    error: allOk ? undefined : 'One or more assemblies failed to add migration — see per-assembly error',
  };
}
