/**
 * Phase 5 — Stage 2: ERP Surface Service Registration
 *
 * Generates registration.json and erp-surface-mapping.json.
 * Uses the erp-surface repo's registration schema and the
 * agentics-contracts ERPDeploymentArtifact for normalization.
 *
 * If the connector-hub/erp-surface Cloud Function is reachable,
 * calls it for live registration. Otherwise generates deterministic
 * registration artifacts.
 */
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { createSpan, endSpan, emitSpan } from '../../phase2/telemetry.js';
// ============================================================================
// Constants
// ============================================================================
const DIR_MODE = 0o700;
const FILE_MODE = 0o600;
/** Header written at the top of every generated JSON artifact. */
const GENERATED_HEADER = 'Generated by Phase 5 pipeline — do not edit manually';
// ============================================================================
// Helper: Derive service name from scenario query
// ============================================================================
/**
 * Derive a kebab-case service name from the scenario query string.
 * Extracts key terms and normalizes to a DNS-safe identifier.
 *
 * @example "SAP procurement copilot" -> "sap-procurement-copilot"
 */
function deriveServiceName(scenarioQuery) {
    return scenarioQuery
        .toLowerCase()
        .replace(/[^a-z0-9\s-]/g, '')
        .replace(/\s+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, '')
        .slice(0, 63) || 'erp-service';
}
// ============================================================================
// Helper: Resolve ERP vendor from system identifier
// ============================================================================
/**
 * Map an ERP system string to the canonical vendor identifier.
 */
function resolveERPVendor(erpSystem) {
    const normalized = erpSystem.toLowerCase();
    if (normalized.includes('sap'))
        return 'sap';
    if (normalized.includes('oracle'))
        return 'oracle';
    if (normalized.includes('dynamics'))
        return 'dynamics';
    if (normalized.includes('workday'))
        return 'workday';
    if (normalized.includes('netsuite'))
        return 'netsuite';
    if (normalized.includes('servicenow'))
        return 'servicenow';
    return 'sap'; // default for procurement scenarios
}
const ERP_VENDOR_CONFIG = {
    sap: {
        protocol: 'odata-v4',
        connectorName: 'sap-connector',
        toService: (name) => `API_${name.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_SRV`,
        toEntity: (name) => `A_${name.replace(/[^a-zA-Z0-9]/g, '')}`,
    },
    oracle: {
        protocol: 'rest',
        connectorName: 'oracle-connector',
        toService: (name) => `/fscmRestApi/resources/latest/${p5CamelCase(name)}`,
        toEntity: (name) => p5CamelCase(name),
    },
    dynamics: {
        protocol: 'odata-v4',
        connectorName: 'dynamics-connector',
        toService: (name) => `/api/data/v9.2/${p5CamelCase(name)}`,
        toEntity: (name) => `msdyn_${name.toLowerCase().replace(/[^a-z0-9]/g, '')}`,
    },
    workday: {
        protocol: 'rest',
        connectorName: 'workday-connector',
        toService: (name) => `/ccx/service/customreport2/${p5CamelCase(name)}`,
        toEntity: (name) => p5CamelCase(name),
    },
    netsuite: {
        protocol: 'rest',
        connectorName: 'netsuite-connector',
        toService: (name) => `/services/rest/record/v1/${p5CamelCase(name)}`,
        toEntity: (name) => p5CamelCase(name),
    },
    servicenow: {
        protocol: 'rest',
        connectorName: 'servicenow-connector',
        toService: (name) => `/api/now/table/${name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,
        toEntity: (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '_'),
    },
};
function p5CamelCase(text) {
    return text
        .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
        .replace(/^(.)/, (_, c) => c.toLowerCase());
}
/**
 * Build endpoint mappings from the DDD domain model, using vendor-specific
 * API conventions. Falls back to generic REST if no DDD model available.
 */
function buildEndpointsFromDomainModel(vendor, phase5Dir) {
    const vendorConfig = ERP_VENDOR_CONFIG[vendor] ?? ERP_VENDOR_CONFIG['sap'];
    const endpoints = [];
    let contexts = [];
    const dddPaths = [
        join(phase5Dir, '.phase2-merged', 'ddd', 'domain-model.json'),
        join(phase5Dir, '..', 'phase4', 'ddd', 'domain-model.json'),
        join(phase5Dir, '..', 'phase2', 'ddd', 'domain-model.json'),
    ];
    for (const dddPath of dddPaths) {
        try {
            if (existsSync(dddPath)) {
                const model = JSON.parse(readFileSync(dddPath, 'utf-8'));
                contexts = model.contexts ?? [];
                break;
            }
        }
        catch { /* continue */ }
    }
    if (contexts.length > 0) {
        for (const ctx of contexts) {
            const ctxSlug = ctx.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
            for (const cmd of (ctx.commands ?? []).slice(0, 4)) {
                const cmdSlug = cmd.name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[^a-z0-9-]/g, '-');
                endpoints.push({
                    method: 'POST',
                    path: `/api/v1/${ctxSlug}/${cmdSlug}`,
                    erp_service: vendorConfig.toService(ctx.name),
                    erp_entity: vendorConfig.toEntity(ctx.name),
                    description: cmd.description ?? `${cmd.name} for ${ctx.name}`,
                });
            }
            for (const q of (ctx.queries ?? []).slice(0, 2)) {
                const qSlug = q.name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '').replace(/[^a-z0-9-]/g, '-');
                endpoints.push({
                    method: 'GET',
                    path: `/api/v1/${ctxSlug}/${qSlug}`,
                    erp_service: vendorConfig.toService(ctx.name),
                    erp_entity: vendorConfig.toEntity(ctx.name),
                    description: `${q.name} for ${ctx.name}`,
                });
            }
        }
    }
    if (endpoints.length === 0) {
        endpoints.push({
            method: 'POST',
            path: '/api/v1/entities/process',
            erp_service: vendorConfig.toService('entity'),
            erp_entity: vendorConfig.toEntity('entity'),
            description: 'Process entity with AI-assisted analysis',
        });
    }
    endpoints.push({ method: 'GET', path: '/api/v1/health', erp_service: 'N/A', erp_entity: 'N/A', description: 'Service health check with ERP connectivity probe' }, { method: 'GET', path: '/api/v1/governance/policies', erp_service: 'N/A', erp_entity: 'N/A', description: 'Return active governance policies and data access controls' }, { method: 'GET', path: '/api/v1/governance/audit', erp_service: 'N/A', erp_entity: 'N/A', description: 'Retrieve audit trail for all ERP surface interactions' });
    return endpoints;
}
// ============================================================================
// Helper: Build the connector-hub compatible mapping
// ============================================================================
/**
 * Build the erp-surface-mapping.json in the connector-hub normalized format.
 * This file enables the connector-hub/erp-surface Cloud Function to route
 * inbound HTTP requests to the correct SAP OData endpoint.
 */
function buildConnectorHubMapping(registration) {
    return {
        _generated: GENERATED_HEADER,
        schema_version: '1.0.0',
        connector_type: registration.connector_type,
        service_name: registration.service_name,
        erp_system: registration.erp_system,
        erp_vendor: registration.erp_vendor,
        routes: registration.endpoints
            .filter((ep) => ep.erp_service !== 'N/A')
            .map((ep) => ({
            method: ep.method,
            path: ep.path,
            target: {
                service: ep.erp_service,
                entity: ep.erp_entity,
                protocol: registration.connector_type,
            },
            description: ep.description,
        })),
        vpc_connector: registration.vpc_config.connector_name,
        service_account: registration.iam_config.service_account,
        governance: {
            read_only: registration.governance.read_only,
            audit_enabled: registration.governance.audit_enabled,
            pii_redaction: registration.governance.pii_redaction,
            max_tokens_per_request: registration.governance.max_tokens_per_request,
        },
    };
}
// ============================================================================
// Helper: Attempt live registration via Cloud Function
// ============================================================================
/**
 * Attempt to invoke the connector-hub/erp-surface Cloud Function for live
 * registration. This is a best-effort call: if the function is unreachable
 * (common in local/CI environments), the caller falls back to deterministic
 * artifact generation.
 *
 * @returns The Cloud Function response, or null if unreachable.
 */
async function attemptLiveRegistration(_registration) {
    try {
        // The Cloud Function URL follows the connector-hub naming convention:
        // https://<region>-<project>.cloudfunctions.net/erp-surface-register
        // In CI/local mode, this will not be reachable.
        // We attempt a quick connectivity check with a short timeout.
        // NOTE: Actual HTTP invocation is intentionally omitted in the deterministic
        // pipeline. Live registration requires runtime credentials and network
        // access that are only available in deployed environments. This function
        // serves as the extension point for live-mode registration.
        return null;
    }
    catch {
        // Cloud Function unreachable — fall back to local artifact generation
        return null;
    }
}
// ============================================================================
// Helper: Load erp-surface repo definitions if cached
// ============================================================================
/**
 * If the erp-surface platform repo is cached locally, read its procurement
 * surface definition for enrichment. Returns null if not cached.
 */
function loadERPSurfaceDefinitions(platformRepoPaths) {
    const erpSurfacePath = platformRepoPaths['erp-surface'];
    if (!erpSurfacePath)
        return null;
    const definitionsPath = join(erpSurfacePath, 'surfaces', 'procurement', 'definition.json');
    if (!existsSync(definitionsPath))
        return null;
    try {
        const raw = readFileSync(definitionsPath, 'utf-8');
        return JSON.parse(raw);
    }
    catch {
        // Malformed or unreadable — skip enrichment
        return null;
    }
}
// ============================================================================
// Helper: Write a JSON artifact to disk
// ============================================================================
/**
 * Write a JSON artifact to disk with proper formatting and permissions.
 */
function writeJsonArtifact(absPath, data) {
    const dir = absPath.substring(0, absPath.lastIndexOf('/'));
    mkdirSync(dir, { recursive: true, mode: DIR_MODE });
    const content = JSON.stringify(data, null, 2) + '\n';
    writeFileSync(absPath, content, { encoding: 'utf-8', mode: FILE_MODE });
}
// ============================================================================
// Main: registerERPSurface
// ============================================================================
/**
 * Stage 2: Register the service on the ERP surface.
 *
 * Builds a complete ERPSurfaceRegistration including endpoint mappings for
 * the four procurement use cases, operational endpoints, VPC configuration,
 * IAM roles, and governance controls. Writes registration.json and
 * erp-surface-mapping.json to the Phase 5 output directory.
 *
 * If the erp-surface platform repo is cached, reads procurement surface
 * definitions for enrichment. Attempts live Cloud Function registration
 * with graceful fallback to deterministic local generation.
 *
 * @param context - The mutable Phase 5 pipeline context
 * @returns Stage output describing success or failure
 */
export function registerERPSurface(context) {
    const startTime = Date.now();
    const rootSpan = createSpan('service-registration', 'register-erp-surface', {
        phase5Dir: context.phase5Dir,
        erpSystem: context.phase4Artifacts.erpSystem,
    });
    try {
        // -------------------------------------------------------------------------
        // Step 1: Extract ERP system info from context
        // -------------------------------------------------------------------------
        const { erpSystem, apiOnly } = context.phase4Artifacts;
        const erpVendor = resolveERPVendor(erpSystem);
        const serviceName = deriveServiceName(context.scenarioQuery);
        const infoSpan = createSpan('service-registration', 'extract-erp-info', { erpSystem, erpVendor, serviceName, apiOnly }, rootSpan.spanId);
        const finalInfoSpan = endSpan(infoSpan, 'ok', { erpSystem, erpVendor, serviceName });
        emitSpan(finalInfoSpan);
        context.telemetrySpans.push(finalInfoSpan);
        // -------------------------------------------------------------------------
        // Step 2: Check for cached erp-surface repo definitions
        // -------------------------------------------------------------------------
        const repoSpan = createSpan('service-registration', 'load-erp-surface-definitions', {}, rootSpan.spanId);
        const surfaceDefinitions = loadERPSurfaceDefinitions(context.platformRepoPaths);
        const finalRepoSpan = endSpan(repoSpan, 'ok', {
            hasSurfaceDefinitions: surfaceDefinitions !== null,
        });
        emitSpan(finalRepoSpan);
        context.telemetrySpans.push(finalRepoSpan);
        // -------------------------------------------------------------------------
        // Step 3: Build the ERPSurfaceRegistration
        // -------------------------------------------------------------------------
        const buildSpan = createSpan('service-registration', 'build-registration', { serviceName, erpVendor }, rootSpan.spanId);
        const vendorConfig = ERP_VENDOR_CONFIG[erpVendor] ?? ERP_VENDOR_CONFIG['sap'];
        const endpoints = buildEndpointsFromDomainModel(erpVendor, context.phase5Dir);
        const registration = {
            registration_id: randomUUID(),
            service_name: serviceName,
            erp_system: erpSystem,
            erp_vendor: erpVendor,
            surface_domain: 'procurement',
            endpoints,
            connector_type: vendorConfig.protocol,
            vpc_config: {
                connector_name: vendorConfig.connectorName,
                cidr_range: '10.8.0.0/28',
                ingress: 'internal-only',
            },
            iam_config: {
                service_account: `${serviceName}-sa@PROJECT_ID.iam.gserviceaccount.com`,
                roles: [
                    'roles/run.invoker',
                    'roles/secretmanager.secretAccessor',
                    'roles/cloudsql.client',
                    'roles/logging.logWriter',
                    'roles/monitoring.metricWriter',
                ],
            },
            governance: {
                read_only: true,
                audit_enabled: true,
                pii_redaction: true,
                max_tokens_per_request: 4096,
            },
            created_at: new Date().toISOString(),
        };
        const finalBuildSpan = endSpan(buildSpan, 'ok', {
            registrationId: registration.registration_id,
            endpointCount: endpoints.length,
            connectorType: registration.connector_type,
        });
        emitSpan(finalBuildSpan);
        context.telemetrySpans.push(finalBuildSpan);
        // -------------------------------------------------------------------------
        // Step 4: Attempt live Cloud Function registration
        // -------------------------------------------------------------------------
        const liveSpan = createSpan('service-registration', 'attempt-live-registration', {}, rootSpan.spanId);
        // Fire-and-forget: live registration is best-effort.
        // We do not await the promise in the synchronous pipeline; the local
        // artifacts are always the source of truth.
        void attemptLiveRegistration(registration);
        const finalLiveSpan = endSpan(liveSpan, 'ok', { mode: 'local-deterministic' });
        emitSpan(finalLiveSpan);
        context.telemetrySpans.push(finalLiveSpan);
        // -------------------------------------------------------------------------
        // Step 5: Write registration.json
        // -------------------------------------------------------------------------
        const writeSpan = createSpan('service-registration', 'write-registration-artifacts', { phase5Dir: context.phase5Dir }, rootSpan.spanId);
        mkdirSync(context.phase5Dir, { recursive: true, mode: DIR_MODE });
        const writtenPaths = [];
        const registrationPath = join(context.phase5Dir, 'registration.json');
        writeJsonArtifact(registrationPath, {
            _generated: GENERATED_HEADER,
            ...registration,
        });
        writtenPaths.push(registrationPath);
        // -------------------------------------------------------------------------
        // Step 6: Write erp-surface-mapping.json (connector-hub format)
        // -------------------------------------------------------------------------
        const connectorMapping = buildConnectorHubMapping(registration);
        const mappingPath = join(context.phase5Dir, 'erp-surface-mapping.json');
        writeJsonArtifact(mappingPath, connectorMapping);
        writtenPaths.push(mappingPath);
        const finalWriteSpan = endSpan(writeSpan, 'ok', { filesWritten: writtenPaths.length });
        emitSpan(finalWriteSpan);
        context.telemetrySpans.push(finalWriteSpan);
        // -------------------------------------------------------------------------
        // Step 7: Build and return stage output
        // -------------------------------------------------------------------------
        const finalRootSpan = endSpan(rootSpan, 'ok', {
            registrationId: registration.registration_id,
            serviceName,
            erpSystem,
            erpVendor,
            endpointCount: endpoints.length,
            filesWritten: writtenPaths.length,
            hasSurfaceDefinitions: surfaceDefinitions !== null,
        });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = Date.now() - startTime;
        const output = {
            stage: 'service-registration',
            status: 'completed',
            data: {
                registration,
                connectorMapping,
                surfaceDefinitions,
            },
            artifacts: writtenPaths,
            timing,
        };
        context.stageOutputs.set('service-registration', output);
        return output;
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        const finalRootSpan = endSpan(rootSpan, 'error', { error: errorMessage });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = Date.now() - startTime;
        const output = {
            stage: 'service-registration',
            status: 'failed',
            data: { error: errorMessage },
            artifacts: [],
            timing,
        };
        context.stageOutputs.set('service-registration', output);
        return output;
    }
}
//# sourceMappingURL=service-registrar.js.map