/**
 * Prisma Service Generator
 *
 * Generates abstract business logic services
 * Services are NOT tied to specific models but can interact with multiple models
 */

import type { TemplateContext } from '@specverse/types';
import { generateBehaviorBody, generateBehaviorWithHelpers, type BehaviorContext, type BehaviorMetadata, type OperationMetadata } from './behavior-generator.js';

/**
 * Generate Prisma service
 */
export default function generatePrismaService(context: TemplateContext): string {
  const { service, spec } = context;

  if (!service) {
    throw new Error('Service is required in template context');
  }

  const serviceName = service.name;
  const hasEvents = (service.publishes && service.publishes.length > 0) ||
                    (service.subscribes && service.subscribes.length > 0);

  // Generate operations first so we can detect what symbols they use in the output
  const operationsCode = generateOperationsWithHelpers(service);
  // If the generated code contains `aiBehaviors.`, we need to import the AI file
  const hasAiBehaviors = /\baiBehaviors\./.test(operationsCode);
  // Only emit the prisma client binding + import if the operations actually use it
  const usesPrisma = /\bprisma\b/.test(operationsCode);

  return `/**
 * ${serviceName}
 * Abstract business logic service
 * ${service.description || ''}
 */
${usesPrisma ? `\nimport { PrismaClient } from '@prisma/client';` : ''}
${hasEvents ? `import { eventBus } from '../events/eventBus.js';` : ''}
${hasAiBehaviors ? `import * as aiBehaviors from '../behaviors/${serviceName}.ai.js';` : ''}
${usesPrisma ? `\nconst prisma = new PrismaClient();` : ''}

/**
 * ${serviceName} class
 */
export class ${serviceName} {
  ${generateConstructor(service)}
  ${operationsCode}
  ${generateEventSubscriptions(service)}
}

// Export singleton instance
export const ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)} = new ${serviceName}();
export default ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)};
`;
}

/**
 * Generate constructor with event subscriptions
 */
function generateConstructor(service: any): string {
  const hasSubscriptions = service.subscribes && service.subscribes.length > 0;

  if (!hasSubscriptions) {
    return '';
  }

  return `
  private unsubscribers: Array<() => void> = [];

  constructor() {
    // Subscribe to events
    this.setupEventSubscriptions();
  }

  /**
   * Setup event subscriptions
   */
  private setupEventSubscriptions(): void {
    ${service.subscribes.map((eventName: string) => `
    // Subscribe to ${eventName}
    const unsubscribe${eventName} = eventBus.subscribe(
      EventName.${eventName},
      this.on${eventName}.bind(this)
    );
    this.unsubscribers.push(unsubscribe${eventName});
    `).join('\n')}
  }

  /**
   * Cleanup subscriptions
   */
  public destroy(): void {
    this.unsubscribers.forEach(unsubscribe => unsubscribe());
    this.unsubscribers = [];
  }
`;
}

/**
 * Generate service operations + helper methods from step conventions.
 */
function generateOperationsWithHelpers(service: any): string {
  const ops = generateOperations(service);
  // Collect helpers from the operations module-level accumulator
  if (_collectedHelpers.length > 0) {
    const helpers = [...new Set(_collectedHelpers)].join('\n');
    _collectedHelpers.length = 0;
    return ops + '\n' + helpers;
  }
  return ops;
}

// Module-level accumulator for helper methods generated during operation building
const _collectedHelpers: string[] = [];

/**
 * Generate service operations
 */
function generateOperations(service: any): string {
  if (!service.operations ||
      (Array.isArray(service.operations) && service.operations.length === 0) ||
      (!Array.isArray(service.operations) && Object.keys(service.operations).length === 0)) {
    return `
  /**
   * Execute service logic
   */
  public async execute(params: any): Promise<any> {
    // TODO: Implement service logic
    throw new Error('Service logic not implemented');
  }
`;
  }

  // Normalize: array of operations (with .name) or object entries
  const entries: [string, any][] = Array.isArray(service.operations)
    ? service.operations.map((op: any) => [op.name, op])
    : Object.entries(service.operations);

  const operations: string[] = [];

  entries.forEach(([operationName, operation]) => {
    operations.push(generateOperation(operationName, operation, service));
  });

  return operations.join('\n');
}

/**
 * Generate individual operation
 */
function generateOperation(operationName: string, operation: any, service: any): string {
  const rawParams = generateOperationParams(operation);
  const hasPublish = service.publishes && service.publishes.length > 0;
  const body = generateOperationLogic(operation, service, operationName);
  // Rename any operation parameter that the body doesn't reference so tsc's
  // noUnusedParameters doesn't trip on placeholder service stubs.
  const params = renameUnusedParams(rawParams, body);

  return `
  /**
   * ${operationName}
   * ${operation.description || ''}
   */
  public async ${operationName}(${params}): Promise<any> {
    try {
      ${body}

      ${hasPublish ? `
      // Publish event (example)
      // eventBus.publish(EventName.SomeEvent, {
      //   operation: '${operationName}',
      //   timestamp: new Date().toISOString()
      // });
      ` : ''}

      // Return result
      return { success: true };
    } catch (error) {
      console.error(\`[${service.name}] ${operationName} failed:\`, error);
      throw error;
    }
  }
`;
}

/**
 * Rename each parameter with a leading underscore if its name isn't referenced
 * in the operation body (suppresses TS6133 on placeholder stubs).
 */
function renameUnusedParams(paramsString: string, body: string): string {
  if (!paramsString.trim()) return paramsString;
  return paramsString.split(',').map(segment => {
    const trimmed = segment.trim();
    const nameMatch = trimmed.match(/^(\w+)/);
    if (!nameMatch) return segment;
    const name = nameMatch[1];
    if (name.startsWith('_')) return segment;
    const re = new RegExp(`\\b${name}\\b`);
    if (re.test(body)) return segment;
    return segment.replace(new RegExp(`\\b${name}\\b`), `_${name}`);
  }).join(', ');
}

/**
 * Generate operation parameters
 */
function generateOperationParams(operation: any): string {
  if (!operation.parameters || Object.keys(operation.parameters).length === 0) {
    return 'params: any = {}';
  }

  const params = Object.entries(operation.parameters).map(([name, param]: [string, any]) => {
    const optional = !param.required;
    return `${name}${optional ? '?' : ''}: ${mapTypeToTypeScript(param.type)}`;
  });

  return params.join(', ');
}

/**
 * Generate operation logic from behavioral metadata (L3 generation).
 * Falls back to placeholder if no behavioral data available.
 */
function generateOperationLogic(operation: any, service: any, operationName?: string): string {
  // Check for behavioral metadata — supports both AI-optimized format (implementation.preconditions)
  // and SpecVerse convention format (requires/ensures/publishes)
  const impl = operation.implementation || {};
  const meta = operation.metadata;

  // Map convention format to implementation format
  if (!impl.preconditions && operation.requires) {
    impl.preconditions = Array.isArray(operation.requires) ? operation.requires : [operation.requires];
  }
  if (!impl.postconditions && operation.ensures) {
    impl.postconditions = Array.isArray(operation.ensures) ? operation.ensures : [operation.ensures];
  }
  if (!impl.sideEffects && operation.publishes) {
    impl.sideEffects = (Array.isArray(operation.publishes) ? operation.publishes : [operation.publishes])
      .map((e: string) => `Publish event: ${e}`);
  }

  // Realize L3 contract (mirrors controller-generator.ts): when an
  // operation declares `steps:`, the implementation lives in
  // `behaviors/<ServiceName>.ai.ts` as an exported async function
  // named EXACTLY `operation.name`. Both per-action and per-owner
  // emitters honour this contract. The service method here delegates:
  // collect the operation's parameters into a single `input` object
  // and forward to the per-action implementation in the .ai.ts.
  //
  // Pre-this-fix, the service emitted a per-STEP chain
  // (`step1Result = await aiBehaviors.<verboseStepName>(...)`,
  // `step2Result = await aiBehaviors.<verboseStepName>(step1Result)`,
  // ...) referencing function names that NO emitter produces anymore
  // — per-action and per-owner both export per-action names only.
  // Every service method was broken at compile time. Surfaced by the
  // 2026-05-11 per-owner trial v5: 411 TS errors in src/services/
  // tracing back to "Property '<verboseStepName>' does not exist on
  // type 'typeof import('.../behaviors/<X>.ai.js')'". Same defect as
  // the controller-side per-step mismatch.
  //
  // Operations WITHOUT steps still go through the legacy
  // behavior-generator path (preconditions/postconditions only,
  // no per-step chain to break).
  const hasSteps = Array.isArray(impl.steps) && impl.steps.length > 0
    || Array.isArray(operation.steps) && operation.steps.length > 0;
  if (hasSteps) {
    // operationName resolution: prefer the caller-supplied entry KEY
    // (always correct when operations is a name-keyed object — the
    // canonical spec shape), fall back to operation.name (array-shape
    // operations), final fallback 'execute' for malformed input.
    // Pre-this-fix line read `operation.name || 'execute'`, which
    // landed every operation on the `execute` delegate when the spec
    // keyed operations by name without a redundant inner `.name`
    // (the analyse-pipeline's canonical output). Result: the service
    // method delegated to `aiBehaviors.execute(input)` but the .ai.ts
    // only exported the real operation names → 69 TS2339 errors per
    // 2026-05-11 idle-meta stub trial.
    const resolvedOpName = operationName || operation.name || 'execute';
    const paramNames = Object.keys(operation.parameters || {});
    const inputObj = paramNames.length > 0
      ? `{ ${paramNames.join(', ')} }`
      : '{}';
    return `    const input = ${inputObj};
    return await aiBehaviors.${resolvedOpName}(input);`;
  }

  if (impl.preconditions?.length || impl.postconditions?.length || impl.transactional) {
    // L3: Generate from behavioral specification (no-steps path —
    // only requires/ensures/publishes scaffolding).
    const modelName = inferModelFromServiceName(service.name);
    const parameterNames = Object.keys(operation.parameters || {});
    const context: BehaviorContext = {
      modelName,
      serviceName: service.name,
      operationName: operationName || operation.name || 'execute',
      prismaModel: modelName,
      parameterNames,
    };

    const behavior: BehaviorMetadata = {
      preconditions: impl.preconditions || [],
      postconditions: impl.postconditions || [],
      sideEffects: impl.sideEffects || [],
      steps: [],  // steps path handled by the delegate branch above
      transactional: impl.transactional || false
    };

    const opMeta: OperationMetadata = {
      async: meta?.async ?? true,
      cacheable: meta?.cacheable ?? false,
      idempotent: meta?.idempotent ?? false
    };

    const result = generateBehaviorWithHelpers(behavior, opMeta, context);
    _collectedHelpers.push(...result.helperMethods);
    return result.body;
  }

  // Fallback: placeholder
  if (operation.description) {
    return `// ${operation.description}`;
  }
  return `// TODO: Implement operation logic`;
}

/**
 * Infer model name from service name.
 * "SpecificationRelationshipService" -> "Specification"
 * "ModelRelationshipService" -> "Model"
 */
function inferModelFromServiceName(serviceName: string): string {
  return serviceName
    .replace(/RelationshipService$/, '')
    .replace(/Service$/, '') || 'Entity';
}

/**
 * Generate event subscription handlers
 */
function generateEventSubscriptions(service: any): string {
  if (!service.subscribes || service.subscribes.length === 0) {
    return '';
  }

  const handlers: string[] = [];

  service.subscribes.forEach((eventName: string) => {
    handlers.push(`
  /**
   * Handle ${eventName} event
   */
  private async on${eventName}(payload: any): Promise<void> {
    try {
      console.log(\`[${service.name}] Received ${eventName} event\`, payload);

      // TODO: Implement event handling logic
      ${generateEventHandlerLogic(eventName, service)}

      console.log(\`[${service.name}] ${eventName} event processed successfully\`);
    } catch (error) {
      console.error(\`[${service.name}] Failed to process ${eventName} event\`, error);
      // TODO: Add error handling (retry, dead letter queue, etc.)
    }
  }`);
  });

  return handlers.join('\n');
}

/**
 * Generate event handler logic placeholder
 */
function generateEventHandlerLogic(eventName: string, service: any): string {
  return `
      // Example: Update related records
      // await prisma.someModel.update({
      //   where: { id: payload.id },
      //   data: { ... }
      // });

      // Example: Trigger workflows
      // await this.triggerWorkflow(payload);

      // Example: Publish follow-up events
      // eventBus.publish(EventName.AnotherEvent, {
      //   original: payload,
      //   processed: true
      // });
  `;
}

/**
 * Map SpecVerse types to TypeScript types
 */
function mapTypeToTypeScript(type: string): string {
  const typeMap: Record<string, string> = {
    String: 'string',
    Integer: 'number',
    Float: 'number',
    Boolean: 'boolean',
    Date: 'Date',
    DateTime: 'Date',
    UUID: 'string',
    Json: 'any',       // #43C: SpecVerse canonical casing → Prisma's Json
    JSON: 'any',       //       legacy/alt casing
    Jsonb: 'any',      //       Postgres-native variant — same TS shape
    JSONB: 'any',
    Array: 'any[]',
    Object: 'Record<string, any>'
  };

  return typeMap[type] || 'any';
}
