/**
 * validate-conventions/execute.ts — Scan a SmartStack project for convention violations.
 *
 * Three checks are implemented (ported from SmartStack.cli/src/mcp/tools/validate-conventions.ts):
 *   1. namespaces — each layer's .cs files should start with the expected namespace prefix
 *   2. entities   — classes inheriting BaseEntity must implement a tenant interface,
 *                   have a private ctor, and a Create() factory method
 *   3. controllers — controllers should use [NavRoute], not a hardcoded [Route] attribute,
 *                    and never both at once
 *
 * The report returned is a plain JSON object Claude reads from stdout.
 */
import path from 'node:path';
import { findSmartStackStructure, type SmartStackStructure } from '../../../../lib/detector.js';
import { findFiles, readText } from '../../../../lib/fs.js';
import { findCsprojFiles, detectNamespaces, type NamespaceConfig } from '../../../../lib/dotnet.js';
import { extractNavRoutes } from '../../../../lib/navroute-parser.js';
import type {
  ValidateConventionsArgs,
  ValidateConventionsOutput,
  Finding,
  Category,
  ConventionsReport,
} from './types.js';

// ─── Entry point ──────────────────────────────────────────────────────────

export async function execute(
  args: ValidateConventionsArgs,
): Promise<ValidateConventionsOutput> {
  const projectPath = args.projectPath || process.cwd();
  const structure = await findSmartStackStructure(projectPath);

  // Determine which checks to run
  const expanded: Category[] =
    args.checks.includes('all')
      ? ['namespaces', 'entities', 'controllers']
      : (args.checks.filter((c) => c !== 'all') as Category[]);

  // Detect namespace config (used by the namespaces check).
  const csprojFiles = await findCsprojFiles(projectPath);
  const detectedNs = await detectNamespaces(csprojFiles);
  const baseNamespace = args.baseNamespace ?? detectedNs?.baseNamespace ?? null;

  const findings: Finding[] = [];
  let filesInspected = 0;

  // Run each check
  if (expanded.includes('namespaces')) {
    filesInspected += await checkNamespaces(structure, baseNamespace, findings);
  }
  if (expanded.includes('entities')) {
    filesInspected += await checkEntities(structure, findings);
  }
  if (expanded.includes('controllers')) {
    filesInspected += await checkControllers(structure, findings);
  }

  // Build the report
  const counts = {
    errors: findings.filter((f) => f.severity === 'error').length,
    warnings: findings.filter((f) => f.severity === 'warning').length,
    infos: findings.filter((f) => f.severity === 'info').length,
  };

  const byCategory: Record<Category, number> = {
    namespaces: 0,
    entities: 0,
    controllers: 0,
  };
  for (const finding of findings) {
    byCategory[finding.category]++;
  }

  const report: ConventionsReport = {
    projectPath,
    structure: {
      domain: structure.domain ?? null,
      application: structure.application ?? null,
      infrastructure: structure.infrastructure ?? null,
      api: structure.api ?? null,
      web: structure.web ?? null,
    },
    checksRun: expanded,
    filesInspected,
    findings,
    counts,
    byCategory,
  };

  const valid = counts.errors === 0;
  const summary =
    valid
      ? `All checks passed (${filesInspected} files inspected, ${counts.warnings} warnings, ${counts.infos} infos)`
      : `${counts.errors} error(s), ${counts.warnings} warning(s), ${counts.infos} info(s) across ${filesInspected} files`;

  const nextSteps: string[] = [];
  if (counts.errors > 0) {
    nextSteps.push('Fix the "error"-severity findings before proceeding — they break conventions that are enforced at runtime');
  }
  if (counts.warnings > 0) {
    nextSteps.push('Review the warnings — they indicate drift from SmartStack conventions that may cause issues later');
  }
  if (valid && counts.warnings === 0) {
    nextSteps.push('No action needed — the project fully complies with SmartStack conventions');
  }

  return {
    success: true,
    command: 'validate-conventions',
    data: { valid, summary },
    report,
    errors: [],
    warnings: [],
    nextSteps,
  };
}

// ─── Check 1: namespaces ──────────────────────────────────────────────────

async function checkNamespaces(
  structure: SmartStackStructure,
  baseNamespace: string | null,
  findings: Finding[],
): Promise<number> {
  if (!baseNamespace) {
    findings.push({
      severity: 'warning',
      category: 'namespaces',
      message:
        'Could not detect a base namespace from .csproj files — skipping namespace checks',
      suggestion: 'Pass --base-namespace <Name> if the project does not follow the *.Domain/*.Application/*.Infrastructure/*.Api layout',
    });
    return 0;
  }

  const expectedByLayer: Array<{
    path: string | undefined;
    expected: string;
    layer: string;
  }> = [
    { path: structure.domain, expected: `${baseNamespace}.Domain`, layer: 'Domain' },
    { path: structure.application, expected: `${baseNamespace}.Application`, layer: 'Application' },
    { path: structure.infrastructure, expected: `${baseNamespace}.Infrastructure`, layer: 'Infrastructure' },
    { path: structure.api, expected: `${baseNamespace}.Api`, layer: 'Api' },
  ];

  let inspected = 0;

  for (const layer of expectedByLayer) {
    if (!layer.path) continue;

    const csFiles = await findFiles('**/*.cs', { cwd: layer.path });
    // Inspect at most 30 files per layer to keep the audit fast.
    const sample = csFiles.slice(0, 30);
    inspected += sample.length;

    for (const file of sample) {
      const content = await readText(file);
      const match = content.match(/namespace\s+([\w.]+)/);
      if (!match) continue;
      const ns = match[1];

      if (!ns.startsWith(layer.expected)) {
        // Downgrade to warning if the namespace still follows the Clean Architecture
        // pattern ({Base}.{Layer}) — common for client extension projects.
        const followsPattern =
          ['Domain', 'Application', 'Infrastructure', 'Api'].some(
            (l) => ns.includes(`.${l}`) || ns.endsWith(`.${l}`),
          );
        findings.push({
          severity: followsPattern ? 'warning' : 'error',
          category: 'namespaces',
          message: `${layer.layer} file has namespace "${ns}" (expected prefix: "${layer.expected}")`,
          file: path.relative(structure.root, file),
          suggestion: followsPattern
            ? `Namespace follows Clean Architecture pattern but differs from the detected base "${baseNamespace}". Expected to start with "${layer.expected}".`
            : `Should start with "${layer.expected}"`,
        });
      }
    }
  }

  return inspected;
}

// ─── Check 2: entities ────────────────────────────────────────────────────

async function checkEntities(
  structure: SmartStackStructure,
  findings: Finding[],
): Promise<number> {
  if (!structure.domain) {
    findings.push({
      severity: 'warning',
      category: 'entities',
      message: 'Domain project not found — skipping entity validation',
    });
    return 0;
  }

  const csFiles = await findFiles('**/*.cs', { cwd: structure.domain });
  let inspected = 0;

  for (const file of csFiles) {
    const fileName = path.basename(file, '.cs');

    // Skip obvious non-entity files
    if (
      fileName.endsWith('Dto') ||
      fileName.endsWith('Command') ||
      fileName.endsWith('Query') ||
      fileName.endsWith('Handler') ||
      fileName.endsWith('Validator') ||
      fileName.endsWith('Exception') ||
      fileName.startsWith('I')
    ) {
      continue;
    }

    const content = await readText(file);
    const classMatch = content.match(
      /public\s+(?:class|record)\s+(\w+)(?:\s*:\s*([^{]+))?/,
    );
    if (!classMatch) continue;

    inspected++;
    const entityName = classMatch[1];
    const inheritance = classMatch[2]?.trim() || '';

    const hasBaseEntity = inheritance.includes('BaseEntity');
    const hasSystemEntity = inheritance.includes('SystemEntity');
    if (!hasBaseEntity && !hasSystemEntity) continue; // value object / other

    const hasTenantInterface =
      inheritance.includes('ITenantEntity') ||
      inheritance.includes('IOptionalTenantEntity') ||
      inheritance.includes('IScopedTenantEntity');

    if (hasBaseEntity && !hasSystemEntity && !hasTenantInterface) {
      findings.push({
        severity: 'warning',
        category: 'entities',
        message: `Entity "${entityName}" inherits BaseEntity but does not implement any tenant interface`,
        file: path.relative(structure.root, file),
        suggestion:
          'Add ITenantEntity (strict), IOptionalTenantEntity (cross-tenant), or IScopedTenantEntity (with scope)',
      });
    }

    if (!content.includes(`private ${entityName}()`)) {
      findings.push({
        severity: 'warning',
        category: 'entities',
        message: `Entity "${entityName}" is missing a private constructor (required by EF Core)`,
        file: path.relative(structure.root, file),
        suggestion: `Add: private ${entityName}() { }`,
      });
    }

    if (!content.includes(`public static ${entityName} Create(`)) {
      findings.push({
        severity: 'warning',
        category: 'entities',
        message: `Entity "${entityName}" is missing a factory method`,
        file: path.relative(structure.root, file),
        suggestion: `Add: public static ${entityName} Create(...)`,
      });
    }
  }

  return inspected;
}

// ─── Check 3: controllers ─────────────────────────────────────────────────

async function checkControllers(
  structure: SmartStackStructure,
  findings: Finding[],
): Promise<number> {
  if (!structure.api) {
    findings.push({
      severity: 'warning',
      category: 'controllers',
      message: 'API project not found — skipping controller route validation',
    });
    return 0;
  }

  // System controllers that legitimately use hardcoded [Route] — whitelisted.
  // Keep this list short; controllers not in the navigation hierarchy should
  // migrate to [NavRoute] when possible.
  const SYSTEM_CONTROLLERS = new Set([
    'AuthController',
    'RegistrationController',
    'OnboardingController',
    'NavigationController',
    'LogsController',
    'EntraController',
    'PreferencesController',
    'ApplicationTrackingController',
    'NotificationsController',
    'DashboardController',
  ]);

  const controllerFiles = await findFiles('**/Controllers/**/*Controller.cs', {
    cwd: structure.api,
  });
  let inspected = 0;

  for (const file of controllerFiles) {
    const fileName = path.basename(file, '.cs');
    if (SYSTEM_CONTROLLERS.has(fileName)) continue;

    inspected++;
    const content = await readText(file);
    const hasNavRoute = content.includes('[NavRoute(');
    const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);

    if (hasNavRoute && routeMatch) {
      findings.push({
        severity: 'error',
        category: 'controllers',
        message: `Controller "${fileName}" has BOTH [NavRoute] and [Route("${routeMatch[1]}")]. Only [NavRoute] should be used.`,
        file: path.relative(structure.root, file),
        suggestion: `Remove [Route("${routeMatch[1]}")] — NavRoute resolves the path dynamically from the navigation database at startup`,
      });
    }

    if (hasNavRoute) {
      // Validate the structure of every [NavRoute(...)] attribute.
      const parsed = extractNavRoutes(content);
      for (const nr of parsed) {
        const parts = nr.navRoute.split('.');
        if (parts.length < 2) {
          findings.push({
            severity: 'error',
            category: 'controllers',
            message: `Controller "${fileName}" has a NavRoute with insufficient depth: "${nr.navRoute}"`,
            file: path.relative(structure.root, file),
            suggestion: 'NavRoute must have at least 2 segments: "application.module"',
          });
        }
        if (parts.some((p) => p !== p.toLowerCase())) {
          findings.push({
            severity: 'error',
            category: 'controllers',
            message: `Controller "${fileName}" has a NavRoute with uppercase characters: "${nr.navRoute}"`,
            file: path.relative(structure.root, file),
            suggestion: 'NavRoute segments must be lowercase (e.g., "administration.users")',
          });
        }
      }
    } else if (routeMatch) {
      // Hardcoded route without any NavRoute — flag as warning.
      findings.push({
        severity: 'warning',
        category: 'controllers',
        message: `Controller "${fileName}" uses hardcoded [Route("${routeMatch[1]}")] instead of [NavRoute]`,
        file: path.relative(structure.root, file),
        suggestion:
          'Migrate to [NavRoute("module.section")] so the URL is driven by the navigation database. Alternatively, whitelist the controller if it is truly system-level.',
      });
    }
  }

  return inspected;
}
