/**
 * uat-plan/validate.ts — Zod parse + project-structure resolution + DB connection.
 *
 * Resolves the API project (controllers), the web app (componentRegistry) and the
 * SQL connection (explicit override, else the generated app's appsettings — the
 * gitignored appsettings.Local.json that `ss dev` writes wins). A missing web app
 * degrades (warn) — routes fall back to nav-DB list pages. A missing connection is
 * fatal: the nav/RBAC skeleton lives in the runtime DB.
 */
import path from 'node:path';
import { existsSync, statSync } from 'node:fs';
import { findSmartStackStructure } from '../../../lib/detector.js';
import {
  resolveConnectionString,
  parseConnectionString,
  type ParsedConnection,
} from '../../../lib/appsettings.js';
import { UatPlanInputSchema, type UatPlanInput } from './types.js';

export interface UatPlanLayout {
  /** Absolute API project dir (controllers). */
  apiDir: string;
  /** Absolute web app dir, or '' if not located. */
  webDir: string;
  /** Artifact dir, relative to projectRoot (POSIX). */
  outDir: string;
  /** Artifact basename. */
  name: string;
  /** Application code (first path segment). */
  application: string;
}

export interface ValidationResult {
  valid: boolean;
  spec: UatPlanInput | null;
  layout: UatPlanLayout | null;
  connection: ParsedConnection | null;
  connectionSource: string | null;
  errors: string[];
  warnings: string[];
}

function fail(extra: Partial<ValidationResult>): ValidationResult {
  return {
    valid: false,
    spec: null,
    layout: null,
    connection: null,
    connectionSource: null,
    errors: [],
    warnings: [],
    ...extra,
  };
}

export async function validate(raw: unknown): Promise<ValidationResult> {
  const parsed = UatPlanInputSchema.safeParse(raw);
  if (!parsed.success) {
    return fail({ errors: parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`) });
  }
  const spec = parsed.data;
  const warnings: string[] = [];

  const root = path.resolve(spec.projectPath);
  if (!existsSync(root)) return fail({ spec, errors: [`projectPath does not exist: ${root}`] });
  if (!statSync(root).isDirectory()) return fail({ spec, errors: [`projectPath is not a directory: ${root}`] });

  const segments = spec.path
    .split('/')
    .map((s) => s.trim())
    .filter(Boolean);
  if (segments.length === 0) return fail({ spec, errors: ['path must name at least an Application segment'] });
  const application = segments[0];
  const name = spec.name ?? segments[segments.length - 1];

  const structure = await findSmartStackStructure(root);
  const apiDir = structure.api ?? structure.apiExtensions ?? structure.apiCore ?? null;
  if (!apiDir) {
    return fail({
      spec,
      errors: ['Could not locate the API project (no *.Api*.csproj). Run /uat against a generated SmartStack project.'],
    });
  }
  const webDir = structure.web ?? '';
  if (!webDir) {
    warnings.push('Web app not located — componentRegistry views unavailable; routes limited to nav-DB list pages.');
  }

  // Connection: explicit spec override, else resolved from the app's appsettings.
  let connStr = spec.connectionString ?? null;
  let connectionSource: string | null = spec.connectionString ? 'spec.connectionString' : null;
  if (!connStr) {
    const resolved = resolveConnectionString(apiDir);
    connStr = resolved.value;
    connectionSource = resolved.source;
  }
  if (!connStr) {
    return fail({
      spec,
      errors: [
        `No SQL connection found (looked under ${apiDir}). Run \`ss dev\` to write appsettings.Local.json, or pass "connectionString" in the spec.`,
      ],
    });
  }
  const connection = parseConnectionString(connStr);

  const outDir = spec.outDir ?? `.application-test/uat/${application}`;

  return {
    valid: true,
    spec,
    layout: { apiDir, webDir, outDir, name, application },
    connection,
    connectionSource,
    errors: [],
    warnings,
  };
}
