#!/usr/bin/env tsx
import fs from 'fs/promises';
import path from 'path';
import { glob } from 'glob';

// Comprehensive OpenAPI spec generator that scans all DTO files
async function generateComprehensiveOpenAPISpec() {
  console.log('🔄 Generating comprehensive OpenAPI spec from all DTO files...');

  const schemas: Record<string, any> = {};

  // Find all DTO files in the API
  const dtoFiles = await glob('apps/api/src/domain/*/dtos/**/*.ts', {
    cwd: path.join(__dirname, '..')
  });

  console.log(`📁 Found ${dtoFiles.length} DTO files to process`);

  for (const dtoFile of dtoFiles) {
    const filePath = path.join(__dirname, '..', dtoFile);
    await processDTOFile(filePath, schemas);
  }

  // Also check for DTOs in other locations
  const additionalDtoFiles = await glob('apps/api/src/domain/*/*.ts', {
    cwd: path.join(__dirname, '..')
  });

  for (const dtoFile of additionalDtoFiles) {
    if (dtoFile.includes('dto') || dtoFile.includes('DTO')) {
      const filePath = path.join(__dirname, '..', dtoFile);
      await processDTOFile(filePath, schemas);
    }
  }

  const spec = {
    openapi: '3.0.0',
    info: {
      title: 'GridPilot API',
      description: 'GridPilot API documentation',
      version: '1.0.0'
    },
    paths: {},
    components: {
      schemas
    }
  };

  const outputPath = path.join(__dirname, '../apps/api/openapi.json');
  await fs.writeFile(outputPath, JSON.stringify(spec, null, 2));

  console.log(`✅ Comprehensive OpenAPI spec generated with ${Object.keys(schemas).length} schemas at: ${outputPath}`);
}

async function processDTOFile(filePath: string, schemas: Record<string, any>) {
  try {
    const content = await fs.readFile(filePath, 'utf-8');

    // Extract all class and interface definitions
    const classMatches = content.match(/export (?:class|interface) (\w+(?:DTO|Dto))/g);
    if (!classMatches) {
      // Debug: check if file has any export statements
      if (content.includes('export')) {
        console.log(`📄 ${filePath} has exports but no DTO classes`);
      }
      return;
    }

    console.log(`📄 Processing ${filePath} - found ${classMatches.length} DTO classes`);

    for (const classMatch of classMatches) {
      const classNameMatch = classMatch.match(/export (?:class|interface) (\w+(?:DTO|Dto))/);
      if (classNameMatch) {
        const className = classNameMatch[1];
        console.log(`  🔍 Extracting schema for ${className}`);
        const schema = extractSchemaFromClass(content, className);
        if (schema && Object.keys(schema.properties || {}).length > 0) {
          schemas[className] = schema;
          console.log(`  ✅ Added schema for ${className} with ${Object.keys(schema.properties).length} properties`);
        } else {
          console.log(`  ⚠️ No schema generated for ${className}`);
        }
      }
    }
  } catch (error) {
    // File can't be read, continue
    console.warn(`⚠️ Could not process ${filePath}:`, error.message);
  }
}

function extractSchemaFromClass(content: string, className: string): any | null {
  const properties: Record<string, any> = {};
  const required: string[] = [];

  // Extract @ApiProperty decorated properties from NestJS DTOs
  // Pattern: @ApiProperty(...) followed by property declaration
  const lines = content.split('\n');
  let i = 0;

  while (i < lines.length) {
    const line = lines[i].trim();

    // Look for @ApiProperty decorator
    if (line.startsWith('@ApiProperty(')) {
      const decoratorMatch = line.match(/@ApiProperty\(([^)]*)\)/);
      if (decoratorMatch) {
        const decoratorContent = decoratorMatch[1];

        // Find the property declaration (could be on next line)
        let propertyLine = line;
        if (!line.includes(';')) {
          i++;
          if (i < lines.length) {
            propertyLine = lines[i].trim();
          }
        }

        // Extract property name and type from declaration
        const propertyMatch = propertyLine.match(/(\w+)\s*\??:\s*([^;]+);/);
        if (propertyMatch) {
          const propertyName = propertyMatch[1];
          const propertyType = propertyMatch[2].trim();

          // Check if property is required
          const isOptional = propertyName.includes('?') ||
                            decoratorContent.includes('required: false') ||
                            decoratorContent.includes('nullable: true') ||
                            propertyLine.includes('@IsOptional()');

          if (!isOptional) {
            required.push(propertyName);
          }

          // Try to extract type from decorator first, then fall back to property type
          let schemaType = extractTypeFromDecorator(decoratorContent);
          if (!schemaType) {
            schemaType = mapTypeToSchema(propertyType);
          }

          properties[propertyName] = schemaType;
        }
      }
    }

    i++;
  }

  // Also extract interface properties (for existing interfaces)
  const interfacePropertyRegex = /(\w+)\s*\??:\s*([^;]+);/g;
  let match;
  while ((match = interfacePropertyRegex.exec(content)) !== null) {
    const propertyName = match[1];
    const propertyType = match[2].trim();

    if (!properties[propertyName]) {
      // Check if property is required
      if (!propertyName.includes('?')) {
        required.push(propertyName);
      }

      properties[propertyName] = mapTypeToSchema(propertyType);
    }
  }

  if (Object.keys(properties).length === 0) {
    return null;
  }

  const schema: any = {
    type: 'object',
    properties
  };

  if (required.length > 0) {
    schema.required = required;
  }

  return schema;
}

function extractTypeFromDecorator(decoratorContent: string): any | null {
  // Extract type information from @ApiProperty decorator
  // Examples:
  // @ApiProperty({ type: String })
  // @ApiProperty({ type: [SomeDTO] })
  // @ApiProperty({ type: () => SomeDTO })

  if (decoratorContent.includes('type:')) {
    // Simple type extraction - this is a simplified version
    // In a real implementation, you'd want proper AST parsing

    if (decoratorContent.includes('[String]') || decoratorContent.includes('[string]')) {
      return { type: 'array', items: { type: 'string' } };
    }

    if (decoratorContent.includes('[Number]') || decoratorContent.includes('[number]')) {
      return { type: 'array', items: { type: 'number' } };
    }

    if (decoratorContent.includes('String') || decoratorContent.includes('string')) {
      return { type: 'string' };
    }

    if (decoratorContent.includes('Number') || decoratorContent.includes('number')) {
      return { type: 'number' };
    }

    if (decoratorContent.includes('Boolean') || decoratorContent.includes('boolean')) {
      return { type: 'boolean' };
    }

    // For complex types with references
    const refMatch = decoratorContent.match(/type:\s*\[?(\w+DTO)\]?/);
    if (refMatch) {
      const refType = refMatch[1];
      if (decoratorContent.includes('[')) {
        return { type: 'array', items: { $ref: `#/components/schemas/${refType}` } };
      } else {
        return { $ref: `#/components/schemas/${refType}` };
      }
    }

    // For function types like type: () => SomeDTO
    const funcMatch = decoratorContent.match(/type:\s*\(\)\s*=>\s*(\w+DTO)/);
    if (funcMatch) {
      return { $ref: `#/components/schemas/${funcMatch[1]}` };
    }
  }

  return null;
}

function mapTypeToSchema(type: string): any {
  // Clean up the type
  type = type.replace(/[|;]/g, '').trim();

  // Handle array types
  if (type.endsWith('[]')) {
    const itemType = type.slice(0, -2);
    return {
      type: 'array',
      items: mapTypeToSchema(itemType)
    };
  }

  // Handle union types (simplified)
  if (type.includes('|')) {
    const types = type.split('|').map(t => t.trim());
    if (types.length === 2 && types.includes('null')) {
      // Nullable type
      const nonNullType = types.find(t => t !== 'null');
      return mapTypeToSchema(nonNullType!);
    }
    return {
      oneOf: types.map(t => mapTypeToSchema(t))
    };
  }

  // Handle basic types
  switch (type.toLowerCase()) {
    case 'string':
      return { type: 'string' };
    case 'number':
    case 'bigint':
      return { type: 'number' };
    case 'boolean':
      return { type: 'boolean' };
    case 'date':
      return { type: 'string', format: 'date-time' };
    case 'any':
    case 'unknown':
      return {};
    default:
      // For complex types, assume they're other DTOs
      if (type.includes('DTO') || type.includes('Dto')) {
        return { $ref: `#/components/schemas/${type}` };
      }
      // For other types, use string as fallback
      return { type: 'string' };
  }
}

generateComprehensiveOpenAPISpec().catch(console.error);