import path from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { detectDbContexts, findStartupProject, hasDotnetProject } from '../lib/detect-dbcontexts.js';
import { listMigrationsJson, dotnetAvailable, dotnetEfAvailable } from '../lib/ef-runner.js';
import type { AssemblyStatus, StatusResult, StatusSpec } from './types.js';

const execFileAsync = promisify(execFile);

async function git(args: string[], cwd: string): Promise<{ stdout: string; exitCode: number }> {
  try {
    const { stdout } = await execFileAsync('git', args, { cwd, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
    return { stdout: stdout.trim(), exitCode: 0 };
  } catch (err) {
    const e = err as { stdout?: string; code?: number };
    return { stdout: (e.stdout || '').trim(), exitCode: typeof e.code === 'number' ? e.code : 1 };
  }
}

async function resolveBaseBranch(cwd: string, preferred?: string): Promise<string> {
  if (preferred) return preferred;
  // Prefer develop when it exists, fall back to main.
  for (const candidate of ['origin/develop', 'develop', 'origin/main', 'main']) {
    const r = await git(['rev-parse', '--verify', candidate], cwd);
    if (r.exitCode === 0) return candidate;
  }
  return 'HEAD';
}

async function listBranchOnlyMigrations(
  migrationsDir: string,
  cwd: string,
  base: string,
): Promise<string[]> {
  const rel = path.relative(cwd, migrationsDir).replace(/\\/g, '/');
  if (!rel || rel.startsWith('..')) return [];

  const current = await git(['ls-files', rel], cwd);
  if (current.exitCode !== 0) return [];

  const currentSet = new Set(
    current.stdout
      .split('\n')
      .filter((l) => l.endsWith('.cs'))
      .filter((l) => !l.endsWith('ModelSnapshot.cs'))
      .filter((l) => !l.endsWith('.Designer.cs')),
  );

  const baseResult = await git(['ls-tree', '-r', '--name-only', base, rel], cwd);
  const baseSet = new Set(
    baseResult.stdout.split('\n').filter((l) => l.endsWith('.cs') && !l.endsWith('ModelSnapshot.cs') && !l.endsWith('.Designer.cs')),
  );

  return Array.from(currentSet)
    .filter((f) => !baseSet.has(f))
    .map((f) => path.basename(f, '.cs'));
}

async function isSnapshotDirty(snapshotPath: string, cwd: string): Promise<boolean> {
  const rel = path.relative(cwd, snapshotPath).replace(/\\/g, '/');
  const r = await git(['status', '--porcelain', '--', rel], cwd);
  return r.stdout.length > 0;
}

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

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

  if (!(await hasDotnetProject(spec.cwd))) {
    return {
      success: true,
      cwd: spec.cwd,
      kind: 'unknown',
      currentBranch: '',
      baseBranch: '',
      assemblies: [],
      warnings: ['No .csproj found in worktree.'],
    };
  }

  if (!(await dotnetAvailable())) {
    return {
      success: false,
      cwd: spec.cwd,
      kind: 'unknown',
      currentBranch: '',
      baseBranch: '',
      assemblies: [],
      warnings,
      error: 'dotnet CLI is not available on PATH',
    };
  }

  if (!(await dotnetEfAvailable())) {
    warnings.push('dotnet-ef is not installed globally — install via: dotnet tool install --global dotnet-ef');
  }

  const currentBranch = (await git(['branch', '--show-current'], spec.cwd)).stdout || 'HEAD';
  const baseBranch = await resolveBaseBranch(spec.cwd, spec.baseBranch);

  const detection = await detectDbContexts(spec.cwd);
  const startupProject = await findStartupProject(spec.cwd);

  const assemblies: AssemblyStatus[] = [];

  for (const ctx of detection.contexts) {
    for (const asm of ctx.assemblies) {
      const extraEnv = asm.provider
        ? { STUDIO_DESIGN_PROVIDER: capitalize(asm.provider) }
        : undefined;

      const listResult = await listMigrationsJson(spec.cwd, {
        context: ctx.name,
        projectPath: asm.csprojPath,
        startupProjectPath: startupProject ?? undefined,
        extraEnv,
      });

      const branchOnly = await listBranchOnlyMigrations(asm.migrationsDir, spec.cwd, baseBranch);
      const snapshotDirty = await isSnapshotDirty(asm.snapshotPath, spec.cwd);

      assemblies.push({
        assemblyName: asm.assemblyName,
        contextName: ctx.name,
        migrationsDir: asm.migrationsDir,
        provider: asm.provider,
        version: asm.version,
        total: listResult.migrations.length,
        applied: listResult.migrations.filter((m) => m.applied).length,
        pending: listResult.migrations.filter((m) => !m.applied).length,
        branchOnly,
        snapshotDirty,
        error: listResult.success ? undefined : listResult.error,
      });
    }
  }

  return {
    success: true,
    cwd: detection.cwd,
    kind: detection.kind,
    currentBranch,
    baseBranch,
    assemblies,
    warnings,
  };
}
