/**
 * @fileoverview OrdoJS RPC Generator - Automatic RPC stub generation
 */

import {
    type ComponentAST,
    type ParameterNode,
    type TypeAnnotation
} from '../types/index.js';

/**
 * RPC generation options
 */
export interface RPCGeneratorOptions {
  endpoint?: string;
  timeout?: number;
  retries?: number;
  errorHandling?: 'throw' | 'return-null' | 'return-error';
  authentication?: boolean;
  compression?: boolean;
}

/**
 * Default RPC generator options
 */
const DEFAULT_RPC_OPTIONS: RPCGeneratorOptions = {
  endpoint: '/api/rpc',
  timeout: 30000,
  retries: 3,
  errorHandling: 'throw',
  authentication: false,
  compression: false
};

/**
 * RPC call metadata
 */
export interface RPCCallMetadata {
  componentName: string;
  functionName: string;
  parameters: ParameterNode[];
  returnType: TypeAnnotation;
  isAsync: boolean;
  middleware: string[];
  permissions: string[];
}

/**
 * Generated RPC stub
 */
export interface RPCStub {
  functionName: string;
  clientCode: string;
  serverEndpoint: string;
  metadata: RPCCallMetadata;
}

/**
 * RPC Generator for automatic stub generation
 */
export class RPCGenerator {
  private options: RPCGeneratorOptions;
  private indentLevel: number = 0;

  constructor(options: Partial<RPCGeneratorOptions> = {}) {
    this.options = { ...DEFAULT_RPC_OPTIONS, ...options };
  }

  /**
   * Generate RPC stubs for all public server functions in a component
   */
  generateRPCStubs(ast: ComponentAST): RPCStub[] {
    if (!ast.component.serverBlock) {
      return [];
    }

    const publicFunctions = ast.component.serverBlock.functions.filter(f => f.isPublic);
    const stubs: RPCStub[] = [];

    for (const func of publicFunctions) {
      const metadata: RPCCallMetadata = {
        componentName: ast.component.name,
        functionName: func.name,
        parameters: func.parameters,
        returnType: func.returnType,
        isAsync: func.isAsync,
        middleware: func.middleware,
        permissions: func.permissions
      };

      const stub: RPCStub = {
        functionName: func.name,
        clientCode: this.generateClientStub(metadata),
        serverEndpoint: this.generateServerEndpoint(metadata),
        metadata
      };

      stubs.push(stub);
    }

    return stubs;
  }

  /**
   * Generate client-side RPC stub code
   */
  generateClientStub(metadata: RPCCallMetadata): string {
    this.indentLevel = 0;
    const lines: string[] = [];

    // Generate function signature
    const paramString = this.generateParameterString(metadata.parameters);
    const asyncKeyword = metadata.isAsync ? 'async ' : '';

    lines.push(`${asyncKeyword}function ${metadata.functionName}(${paramString}) {`);
    this.indentLevel++;

    // Add parameter validation if needed
    if (metadata.parameters.length > 0) {
      lines.push(this.indent('// Parameter validation'));
      for (const param of metadata.parameters) {
        if (!param.isOptional) {
          lines.push(this.indent(`if (${param.name} === undefined || ${param.name} === null) {`));
          this.indentLevel++;
          lines.push(this.indent(`throw new Error('Required parameter "${param.name}" is missing');`));
          this.indentLevel--;
          lines.push(this.indent('}'));
        }
      }
      lines.push('');
    }

    // Generate request payload
    lines.push(this.indent('// Prepare request payload'));
    const payloadParams = metadata.parameters.map(p => p.name).join(', ');
    lines.push(this.indent(`const payload = { ${payloadParams} };`));
    lines.push('');

    // Generate fetch configuration
    lines.push(this.indent('// Configure request'));
    lines.push(this.indent('const requestConfig = {'));
    this.indentLevel++;
    lines.push(this.indent('method: "POST",'));
    lines.push(this.indent('headers: {'));
    this.indentLevel++;
    lines.push(this.indent('"Content-Type": "application/json",'));

    if (this.options.authentication) {
      lines.push(this.indent('"Authorization": `Bearer ${getAuthToken()}`,'));
    }

    if (this.options.compression) {
      lines.push(this.indent('"Accept-Encoding": "gzip, deflate, br",'));
    }

    this.indentLevel--;
    lines.push(this.indent('},'));
    lines.push(this.indent('body: JSON.stringify(payload)'));
    this.indentLevel--;
    lines.push(this.indent('};'));
    lines.push('');

    // Add timeout if specified
    if (this.options.timeout && this.options.timeout > 0) {
      lines.push(this.indent('// Add timeout'));
      lines.push(this.indent('const controller = new AbortController();'));
      lines.push(this.indent(`const timeoutId = setTimeout(() => controller.abort(), ${this.options.timeout});`));
      lines.push(this.indent('requestConfig.signal = controller.signal;'));
      lines.push('');
    }

    // Generate retry logic
    if (this.options.retries && this.options.retries > 0) {
      lines.push(this.indent('// Retry logic'));
      lines.push(this.indent(`let retries = ${this.options.retries};`));
      lines.push(this.indent('let lastError;'));
      lines.push('');
      lines.push(this.indent('while (retries >= 0) {'));
      this.indentLevel++;
      lines.push(this.indent('try {'));
      this.indentLevel++;
    }

    // Generate the actual fetch call
    const endpoint = `${this.options.endpoint}/${metadata.componentName}/${metadata.functionName}`;
    lines.push(this.indent(`const response = await fetch("${endpoint}", requestConfig);`));

    if (this.options.timeout && this.options.timeout > 0) {
      lines.push(this.indent('clearTimeout(timeoutId);'));
    }

    // Handle response
    lines.push('');
    lines.push(this.indent('// Handle response'));
    lines.push(this.indent('if (!response.ok) {'));
    this.indentLevel++;

    if (this.options.errorHandling === 'throw') {
      lines.push(this.indent('const errorText = await response.text();'));
      lines.push(this.indent('throw new Error(`RPC call failed: ${response.status} ${response.statusText}. ${errorText}`);'));
    } else if (this.options.errorHandling === 'return-null') {
      lines.push(this.indent('console.error(`RPC call failed: ${response.status} ${response.statusText}`);'));
      lines.push(this.indent('return null;'));
    } else if (this.options.errorHandling === 'return-error') {
      lines.push(this.indent('const errorText = await response.text();'));
      lines.push(this.indent('return { error: true, status: response.status, message: errorText };'));
    }

    this.indentLevel--;
    lines.push(this.indent('}'));
    lines.push('');

    // Parse and return response
    lines.push(this.indent('// Parse response'));
    lines.push(this.indent('const result = await response.json();'));
    lines.push(this.indent('return result;'));

    // Close retry logic if enabled
    if (this.options.retries && this.options.retries > 0) {
      this.indentLevel--;
      lines.push(this.indent('} catch (error) {'));
      this.indentLevel++;
      lines.push(this.indent('lastError = error;'));
      lines.push(this.indent('retries--;'));
      lines.push(this.indent('if (retries < 0) throw lastError;'));
      lines.push(this.indent('// Wait before retry'));
      lines.push(this.indent('await new Promise(resolve => setTimeout(resolve, 1000 * (3 - retries)));'));
      this.indentLevel--;
      lines.push(this.indent('}'));
      this.indentLevel--;
      lines.push(this.indent('}'));
    }

    this.indentLevel--;
    lines.push('}');

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

  /**
   * Generate server endpoint path
   */
  generateServerEndpoint(metadata: RPCCallMetadata): string {
    return `${this.options.endpoint}/${metadata.componentName}/${metadata.functionName}`;
  }

  /**
   * Generate Express.js route handler for server function
   */
  generateServerRouteHandler(metadata: RPCCallMetadata): string {
    this.indentLevel = 0;
    const lines: string[] = [];

    const endpoint = this.generateServerEndpoint(metadata);

    lines.push(`// RPC endpoint for ${metadata.componentName}.${metadata.functionName}`);
    lines.push(`app.post('${endpoint}', async (req, res) => {`);
    this.indentLevel++;

    // Add middleware checks
    if (metadata.middleware.length > 0) {
      lines.push(this.indent('// Apply middleware'));
      for (const middleware of metadata.middleware) {
        lines.push(this.indent(`await ${middleware}(req, res);`));
      }
      lines.push('');
    }

    // Add permission checks
    if (metadata.permissions.length > 0) {
      lines.push(this.indent('// Check permissions'));
      lines.push(this.indent('const userPermissions = req.user?.permissions || [];'));
      lines.push(this.indent(`const requiredPermissions = ${JSON.stringify(metadata.permissions)};`));
      lines.push(this.indent('const hasPermission = requiredPermissions.every(perm => userPermissions.includes(perm));'));
      lines.push(this.indent('if (!hasPermission) {'));
      this.indentLevel++;
      lines.push(this.indent('return res.status(403).json({ error: "Insufficient permissions" });'));
      this.indentLevel--;
      lines.push(this.indent('}'));
      lines.push('');
    }

    // Extract parameters from request body
    lines.push(this.indent('try {'));
    this.indentLevel++;

    if (metadata.parameters.length > 0) {
      lines.push(this.indent('// Extract parameters'));
      const paramNames = metadata.parameters.map(p => p.name);
      lines.push(this.indent(`const { ${paramNames.join(', ')} } = req.body;`));
      lines.push('');

      // Validate required parameters
      for (const param of metadata.parameters) {
        if (!param.isOptional) {
          lines.push(this.indent(`if (${param.name} === undefined || ${param.name} === null) {`));
          this.indentLevel++;
          lines.push(this.indent(`return res.status(400).json({ error: "Missing required parameter: ${param.name}" });`));
          this.indentLevel--;
          lines.push(this.indent('}'));
        }
      }
      lines.push('');
    }

    // Call the actual server function
    lines.push(this.indent('// Call server function'));
    const paramString = metadata.parameters.map(p => p.name).join(', ');
    const awaitKeyword = metadata.isAsync ? 'await ' : '';
    lines.push(this.indent(`const result = ${awaitKeyword}${metadata.componentName}Server.${metadata.functionName}(${paramString});`));
    lines.push('');

    // Return result
    lines.push(this.indent('// Return result'));
    lines.push(this.indent('res.json(result);'));

    // Error handling
    this.indentLevel--;
    lines.push(this.indent('} catch (error) {'));
    this.indentLevel++;
    lines.push(this.indent('console.error(`RPC call error in ${metadata.componentName}.${metadata.functionName}:`, error);'));
    lines.push(this.indent('res.status(500).json({ error: "Internal server error", message: error.message });'));
    this.indentLevel--;
    lines.push(this.indent('}'));

    this.indentLevel--;
    lines.push('});');

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

  /**
   * Generate complete RPC client module
   */
  generateRPCClientModule(stubs: RPCStub[], componentName: string): string {
    this.indentLevel = 0;
    const lines: string[] = [];

    lines.push(`// Auto-generated RPC client for ${componentName}`);
    lines.push(`// Generated at: ${new Date().toISOString()}`);
    lines.push('');

    // Add authentication helper if needed
    if (this.options.authentication) {
      lines.push('// Authentication helper');
      lines.push('function getAuthToken() {');
      this.indentLevel++;
      lines.push(this.indent('// Implement your authentication token retrieval logic here'));
      lines.push(this.indent('return localStorage.getItem("authToken") || sessionStorage.getItem("authToken");'));
      this.indentLevel--;
      lines.push('}');
      lines.push('');
    }

    // Generate RPC client class
    lines.push(`export class ${componentName}RPCClient {`);
    this.indentLevel++;

    // Add constructor
    lines.push(this.indent('constructor(options = {}) {'));
    this.indentLevel++;
    lines.push(this.indent('this.options = { ...options };'));
    this.indentLevel--;
    lines.push(this.indent('}'));
    lines.push('');

    // Add each RPC stub as a method
    for (const stub of stubs) {
      lines.push(this.indent(`// ${stub.metadata.functionName}`));
      const stubLines = stub.clientCode.split('\n');
      for (const stubLine of stubLines) {
        lines.push(this.indent(stubLine));
      }
      lines.push('');
    }

    this.indentLevel--;
    lines.push('}');
    lines.push('');

    // Export default instance
    lines.push(`export default new ${componentName}RPCClient();`);

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

  /**
   * Generate complete server routes module
   */
  generateServerRoutesModule(stubs: RPCStub[], componentName: string): string {
    this.indentLevel = 0;
    const lines: string[] = [];

    lines.push(`// Auto-generated RPC server routes for ${componentName}`);
    lines.push(`// Generated at: ${new Date().toISOString()}`);
    lines.push('');
    lines.push(`const ${componentName}Server = require('./${componentName.toLowerCase()}-server');`);
    lines.push('');
    lines.push('module.exports = function(app) {');
    this.indentLevel++;

    for (const stub of stubs) {
      const routeHandler = this.generateServerRouteHandler(stub.metadata);
      const handlerLines = routeHandler.split('\n');
      for (const handlerLine of handlerLines) {
        lines.push(this.indent(handlerLine));
      }
      lines.push('');
    }

    this.indentLevel--;
    lines.push('};');

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

  /**
   * Generate parameter string for function signature
   */
  private generateParameterString(parameters: ParameterNode[]): string {
    return parameters.map(param => {
      let result = param.name;
      if (param.isOptional) {
        result += '?';
      }
      if (param.defaultValue) {
        result += ` = ${this.generateDefaultValue(param.defaultValue)}`;
      }
      return result;
    }).join(', ');
  }

  /**
   * Generate default value for parameter
   */
  private generateDefaultValue(defaultValue: any): string {
    if (typeof defaultValue === 'string') {
      return `"${defaultValue}"`;
    } else if (typeof defaultValue === 'object' && defaultValue.expressionType === 'LITERAL') {
      if (typeof defaultValue.value === 'string') {
        return `"${defaultValue.value}"`;
      }
      return String(defaultValue.value);
    }
    return 'undefined';
  }

  /**
   * Helper to generate indentation
   */
  private indent(text: string): string {
    return '  '.repeat(this.indentLevel) + text;
  }
}
