import fs from 'fs-extra';
import path from 'path';

/**
 * Interface representing a parsed entity field
 */
export interface EntityField {
  name: string;
  type: string;
  optional: boolean;
  description?: string;
}

/**
 * Interface representing a parsed entity
 */
export interface ParsedEntity {
  name: string;
  fields: EntityField[];
  filePath: string;
}

/**
 * Interface representing a parsed method from ports/adapters
 */
export interface ParsedMethod {
  name: string;
  returnType: string;
  parameters: Array<{
    name: string;
    type: string;
    optional: boolean;
  }>;
  isAsync: boolean;
  httpMethod?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  endpoint?: string;
}

/**
 * Interface representing parsed adapter information
 */
export interface ParsedAdapter {
  name: string;
  methods: ParsedMethod[];
  baseUrl?: string;
  filePath: string;
}

/**
 * Interface representing parsed ports information
 */
export interface ParsedPorts {
  name: string;
  methods: ParsedMethod[];
  filePath: string;
}

/**
 * Complete domain analysis result
 */
export interface DomainAnalysis {
  domainName: string;
  entities: ParsedEntity[];
  ports: ParsedPorts[];
  adapters: ParsedAdapter[];
  primaryEntity?: ParsedEntity;
}

/**
 * Parse TypeScript interface or type definition
 */
const parseInterface = (content: string, interfaceName: string): EntityField[] => {
  const fields: EntityField[] = [];
  
  try {
    // Regex to find interface definition
    const interfacePattern = `(?:interface|type)\\s+${interfaceName}\\s*(?:extends[^{]*)?\\s*{([^}]+)}`;
    const interfaceRegex = new RegExp(interfacePattern, 'gs');
    
    const match = interfaceRegex.exec(content);
    if (!match || !match[1]) return fields;
    
    const interfaceBody = match[1];
    
    // Parse each field
    const fieldRegex = /^\s*(\w+)(\?)?\s*:\s*([^;,\n]+)(?:\s*;|\s*,|\s*$)/gm;
    let fieldMatch;
    
    while ((fieldMatch = fieldRegex.exec(interfaceBody)) !== null) {
      const [, name, optional, type] = fieldMatch;
      if (name && type) {
        fields.push({
          name: name.trim(),
          type: type.trim(),
          optional: !!optional,
        });
      }
    }
  } catch (error) {
    // Ignore parsing errors
  }
  
  return fields;
};

/**
 * Parse method signatures from class or interface
 */
const parseMethods = (content: string, className?: string): ParsedMethod[] => {
  const methods: ParsedMethod[] = [];
  
  try {
    // If className is provided, extract only methods from that class/interface
    let targetContent = content;
    if (className) {
      const classPattern = `(?:class|interface)\\s+${className}[^{]*{([^}]+(?:{[^}]*}[^}]*)*)}`;
      const classRegex = new RegExp(classPattern, 'gs');
      const classMatch = classRegex.exec(content);
      if (classMatch && classMatch[1]) {
        targetContent = classMatch[1];
      }
    }
    
    // Parse method signatures - improved regex to handle multiline methods
    const methodRegex = /^\s*(?:async\s+)?(\w+)\s*\(\s*([^)]*)\s*\)\s*:\s*([^{;,\n]+)/gm;
    let methodMatch;
    
    while ((methodMatch = methodRegex.exec(targetContent)) !== null) {
      const [fullMatch, methodName, params, returnType] = methodMatch;
      if (!methodName || typeof params !== 'string' || !returnType) continue;
      
      const isAsync = fullMatch.includes('async') || returnType.includes('Promise');
      
      // Parse parameters
      const parameters = params
        .split(',')
        .map(param => {
          const trimmed = param.trim();
          if (!trimmed) return null;
          
          const paramMatch = trimmed.match(/^\s*(\w+)(\?)?\s*:\s*(.+)$/);
          if (!paramMatch || !paramMatch[1] || !paramMatch[3]) return null;
          
          return {
            name: paramMatch[1],
            type: paramMatch[3].trim(),
            optional: !!paramMatch[2],
          };
        })
        .filter(Boolean) as Array<{name: string; type: string; optional: boolean}>;
      
      // Try to infer HTTP method and endpoint from method name
      const httpMethod = inferHttpMethod(methodName);
      const endpoint = inferEndpoint(methodName);
      
      methods.push({
        name: methodName,
        returnType: returnType.trim(),
        parameters,
        isAsync,
        httpMethod,
        endpoint,
      });
    }
  } catch (error) {
    // Ignore parsing errors
  }
  
  return methods;
};

/**
 * Infer HTTP method from method name
 */
const inferHttpMethod = (methodName: string): 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | undefined => {
  const name = methodName.toLowerCase();
  
  if (name.startsWith('get') || name.startsWith('find') || name.startsWith('list') || name.startsWith('fetch')) {
    return 'GET';
  }
  if (name.startsWith('create') || name.startsWith('add') || name.startsWith('post')) {
    return 'POST';
  }
  if (name.startsWith('update') || name.startsWith('put') || name.startsWith('edit')) {
    return 'PUT';
  }
  if (name.startsWith('delete') || name.startsWith('remove')) {
    return 'DELETE';
  }
  if (name.startsWith('patch') || name.startsWith('modify')) {
    return 'PATCH';
  }
  
  return undefined;
};

/**
 * Infer API endpoint from method name
 */
const inferEndpoint = (methodName: string): string | undefined => {
  const name = methodName.toLowerCase();
  
  // Common patterns
  if (name.includes('byid') || name.includes('by_id')) {
    return '/:id';
  }
  if (name.includes('list') || name.includes('all') || name === 'get') {
    return '';
  }
  if (name.includes('create') || name.includes('add')) {
    return '';
  }
  if (name.includes('update') || name.includes('edit')) {
    return '/:id';
  }
  if (name.includes('delete') || name.includes('remove')) {
    return '/:id';
  }
  
  return undefined;
};

/**
 * Extract base URL from adapter content
 */
const extractBaseUrl = (content: string, domainName: string): string => {
  // Look for baseUrl assignments
  const baseUrlRegex = /baseUrl\s*[:=]\s*['"`]([^'"`]+)['"`]/;
  const match = baseUrlRegex.exec(content);
  
  if (match && match[1]) {
    return match[1];
  }
  
  // Default to /api/{domain}
  return `/api/${domainName}`;
};

/**
 * Analyze entities files in a domain
 */
export const analyzeEntities = async (domainPath: string): Promise<ParsedEntity[]> => {
  const entities: ParsedEntity[] = [];
  const entitiesPath = path.join(domainPath, 'entities');
  
  if (!await fs.pathExists(entitiesPath)) {
    return entities;
  }
  
  const files = await fs.readdir(entitiesPath);
  const tsFiles = files.filter(file => file.endsWith('.ts') && !file.endsWith('.test.ts'));
  
  for (const file of tsFiles) {
    const filePath = path.join(entitiesPath, file);
    const content = await fs.readFile(filePath, 'utf8');
    
    const entityName = path.basename(file, '.ts');
    const fields = parseInterface(content, entityName);
    
    if (fields.length > 0) {
      entities.push({
        name: entityName,
        fields,
        filePath,
      });
    }
  }
  
  return entities;
};

/**
 * Analyze ports files in a domain
 */
export const analyzePorts = async (domainPath: string): Promise<ParsedPorts[]> => {
  const ports: ParsedPorts[] = [];
  const portsPath = path.join(domainPath, 'ports');
  
  if (!await fs.pathExists(portsPath)) {
    return ports;
  }
  
  const files = await fs.readdir(portsPath);
  const tsFiles = files.filter(file => file.endsWith('.ts') && !file.endsWith('.test.ts'));
  
  for (const file of tsFiles) {
    const filePath = path.join(portsPath, file);
    const content = await fs.readFile(filePath, 'utf8');
    
    const portsName = path.basename(file, '.ts');
    const methods = parseMethods(content, portsName);
    
    if (methods.length > 0) {
      ports.push({
        name: portsName,
        methods,
        filePath,
      });
    }
  }
  
  return ports;
};

/**
 * Analyze adapters files in a domain
 */
export const analyzeAdapters = async (domainPath: string, domainName: string): Promise<ParsedAdapter[]> => {
  const adapters: ParsedAdapter[] = [];
  const adaptersPath = path.join(domainPath, 'adapters');
  
  if (!await fs.pathExists(adaptersPath)) {
    return adapters;
  }
  
  const files = await fs.readdir(adaptersPath);
  const tsFiles = files.filter(file => file.endsWith('.ts') && !file.endsWith('.test.ts'));
  
  for (const file of tsFiles) {
    const filePath = path.join(adaptersPath, file);
    const content = await fs.readFile(filePath, 'utf8');
    
    const adapterName = path.basename(file, '.ts');
    const methods = parseMethods(content, adapterName);
    const baseUrl = extractBaseUrl(content, domainName);
    
    if (methods.length > 0) {
      adapters.push({
        name: adapterName,
        methods,
        baseUrl,
        filePath,
      });
    }
  }
  
  return adapters;
};

/**
 * Perform complete analysis of a domain
 */
export const analyzeDomain = async (domainPath: string, domainName: string): Promise<DomainAnalysis> => {
  const entities = await analyzeEntities(domainPath);
  const ports = await analyzePorts(domainPath);
  const adapters = await analyzeAdapters(domainPath, domainName);
  
  // Try to identify the primary entity (usually matches domain name)
  const primaryEntity = entities.find(e => 
    e.name.toLowerCase() === domainName.toLowerCase() ||
    e.name.toLowerCase() === domainName.toLowerCase().slice(0, -1) // Remove potential 's'
  ) || entities[0];
  
  return {
    domainName,
    entities,
    ports,
    adapters,
    primaryEntity,
  };
}; 