/**
 * lib/dotnet.ts — .NET / EF Core helpers for dev CLIs.
 * Ported from SmartStack.cli/src/mcp/utils/dotnet.ts.
 *
 * Used by detector.ts and any CLI that needs to reason about .csproj files,
 * target frameworks, EF Core migrations, or package references.
 */
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import path from 'node:path';
import { findFiles, readText } from './fs.js';

const execAsync = promisify(exec);

export async function dotnet(command: string, cwd?: string): Promise<string> {
  const options = cwd ? { cwd } : {};
  const { stdout } = await execAsync(`dotnet ${command}`, options);
  return stdout.trim();
}

export async function isDotnetAvailable(): Promise<boolean> {
  try {
    await execAsync('dotnet --version');
    return true;
  } catch {
    return false;
  }
}

export async function getDotnetVersion(): Promise<string | null> {
  try {
    return await dotnet('--version');
  } catch {
    return null;
  }
}

export async function findCsprojFiles(cwd?: string): Promise<string[]> {
  return findFiles('**/*.csproj', { cwd: cwd || process.cwd() });
}

export async function hasEfCore(csprojPath: string): Promise<boolean> {
  try {
    const content = await readText(csprojPath);
    return content.includes('Microsoft.EntityFrameworkCore');
  } catch {
    return false;
  }
}

export async function findDbContextName(projectPath: string): Promise<string | null> {
  try {
    const csFiles = await findFiles('**/*.cs', { cwd: projectPath });
    for (const file of csFiles) {
      const content = await readText(file);
      const match = content.match(/class\s+(\w+)\s*:\s*(?:\w+,\s*)*DbContext/);
      if (match) return match[1];
    }
    return null;
  } catch {
    return null;
  }
}

export async function listMigrations(
  projectPath: string,
  context?: string,
): Promise<string[]> {
  try {
    const contextArg = context ? `--context ${context}` : '';
    const result = await dotnet(`ef migrations list ${contextArg}`, projectPath);
    return result
      .split('\n')
      .filter((line) => line && !line.startsWith('Build') && !line.includes('...'))
      .map((line) => line.trim());
  } catch {
    return [];
  }
}

export async function getPendingMigrations(
  projectPath: string,
  context?: string,
): Promise<string[]> {
  try {
    const contextArg = context ? `--context ${context}` : '';
    const result = await dotnet(`ef migrations list ${contextArg} --pending`, projectPath);
    return result
      .split('\n')
      .filter((line) => line && !line.startsWith('Build') && !line.includes('...'))
      .map((line) => line.trim());
  } catch {
    return [];
  }
}

export async function buildProject(
  projectPath: string,
): Promise<{ success: boolean; output: string }> {
  try {
    const output = await dotnet('build --no-restore', projectPath);
    return { success: true, output };
  } catch (error) {
    const err = error as { stdout?: string; stderr?: string };
    return {
      success: false,
      output: err.stdout || err.stderr || 'Build failed',
    };
  }
}

export async function getPackageReferences(
  csprojPath: string,
): Promise<Array<{ name: string; version: string }>> {
  try {
    const content = await readText(csprojPath);
    const packages: Array<{ name: string; version: string }> = [];
    const regex = /<PackageReference\s+Include="([^"]+)"\s+Version="([^"]+)"/g;
    let match;
    while ((match = regex.exec(content)) !== null) {
      packages.push({ name: match[1], version: match[2] });
    }
    return packages;
  } catch {
    return [];
  }
}

export async function getTargetFramework(csprojPath: string): Promise<string | null> {
  try {
    const content = await readText(csprojPath);
    const match = content.match(/<TargetFramework>([^<]+)<\/TargetFramework>/);
    return match ? match[1] : null;
  } catch {
    return null;
  }
}

export function getProjectName(csprojPath: string): string {
  return path.basename(csprojPath, '.csproj');
}

export interface NamespaceConfig {
  baseNamespace: string;
  domain: string;
  application: string;
  infrastructure: string;
  api: string;
}

/**
 * Detect the base namespace + per-layer namespaces from a list of .csproj files.
 * Recognizes the common layer suffixes (Domain, Application, Infrastructure, Api).
 *   ["MyProject.Domain.csproj", "MyProject.Api.csproj"] → { baseNamespace: "MyProject", ... }
 *   ["Acme.Corp.Domain.csproj", "Acme.Corp.Api.csproj"] → { baseNamespace: "Acme.Corp", ... }
 */
export async function detectNamespaces(csprojFiles: string[]): Promise<NamespaceConfig | null> {
  if (csprojFiles.length === 0) return null;

  const projectNames = csprojFiles.map((f) => path.basename(f, '.csproj'));
  const layerSuffixes = [
    'Domain',
    'Application',
    'Infrastructure',
    'Api',
    'Web',
    'Core',
    'Tests',
  ];

  const baseNamespaces: string[] = [];

  for (const name of projectNames) {
    const parts = name.split('.');
    for (let i = parts.length - 1; i >= 0; i--) {
      const part = parts[i];
      if (layerSuffixes.some((s) => s.toLowerCase() === part.toLowerCase())) {
        const baseParts = parts.slice(0, i);
        if (baseParts.length > 0) baseNamespaces.push(baseParts.join('.'));
        break;
      }
    }
  }

  if (baseNamespaces.length === 0) {
    const commonPrefix = findCommonPrefix(projectNames);
    if (commonPrefix && commonPrefix.length > 0) {
      const base = commonPrefix.replace(/\.$/, '');
      if (base) baseNamespaces.push(base);
    }
  }

  if (baseNamespaces.length === 0) return null;

  const baseNamespace = getMostCommon(baseNamespaces) || baseNamespaces[0];
  return {
    baseNamespace,
    domain: `${baseNamespace}.Domain`,
    application: `${baseNamespace}.Application`,
    infrastructure: `${baseNamespace}.Infrastructure`,
    api: `${baseNamespace}.Api`,
  };
}

function findCommonPrefix(strings: string[]): string {
  if (strings.length === 0) return '';
  if (strings.length === 1) {
    const parts = strings[0].split('.');
    if (parts.length > 1) return parts.slice(0, -1).join('.') + '.';
    return strings[0] + '.';
  }
  let prefix = strings[0];
  for (let i = 1; i < strings.length; i++) {
    while (strings[i].indexOf(prefix) !== 0 && prefix.length > 0) {
      prefix = prefix.slice(0, -1);
    }
  }
  const lastDot = prefix.lastIndexOf('.');
  if (lastDot > 0 && !prefix.endsWith('.')) {
    prefix = prefix.slice(0, lastDot + 1);
  }
  return prefix;
}

function getMostCommon(arr: string[]): string | null {
  if (arr.length === 0) return null;
  const counts = new Map<string, number>();
  for (const item of arr) counts.set(item, (counts.get(item) || 0) + 1);
  let maxCount = 0;
  let mostCommon = arr[0];
  for (const [item, count] of counts) {
    if (count > maxCount) {
      maxCount = count;
      mostCommon = item;
    }
  }
  return mostCommon;
}
