import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Define the paths
const STORAGE_CONNECTORS_DIR = path.join(__dirname, '../../src/subsystems/IO/Storage.service/connectors');
const VECTORDB_CONNECTORS_DIR = path.join(__dirname, '../../src/subsystems/IO/VectorDB.service/connectors');
const LLM_CONNECTORS_DIR = path.join(__dirname, '../../src/subsystems/LLMManager/LLM.service/connectors');
const TYPES_OUTPUT_DIR = path.join(__dirname, '../../src/sdk/types/generated');
const STORAGE_TEMPLATE_PATH = path.join(__dirname, './templates/Storage.type.ts.tpl');
const VECTORDB_TEMPLATE_PATH = path.join(__dirname, './templates/VectorDB.type.ts.tpl');
const LLM_TEMPLATE_PATH = path.join(__dirname, './templates/LLM.type.ts.tpl');

/**
 * Simple template engine that replaces {{variable}} with values
 */
function renderTemplate(templateContent, variables) {
    const warning = '//!!! DO NOT EDIT THIS FILE, IT IS AUTO-GENERATED !!!//';
    let result = templateContent;

    for (const [key, value] of Object.entries(variables)) {
        const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
        result = result.replace(placeholder, value);
    }

    result = warning + '\n\n' + result;

    return result;
}

/**
 * Load template file
 */
function loadTemplate(templatePath) {
    try {
        return fs.readFileSync(templatePath, 'utf-8');
    } catch (error) {
        console.error(`Error loading template from ${templatePath}:`, error);
        throw error;
    }
}

/**
 * Extract provider registrations from service index.ts
 */
function extractProviderRegistrations(serviceName, serviceType) {
    // Define service path mappings
    const servicePathMap = {
        Storage: 'src/subsystems/IO/Storage.service',
        VectorDB: 'src/subsystems/IO/VectorDB.service',
        LLM: 'src/subsystems/LLMManager/LLM.service',
    };

    const servicePath = servicePathMap[serviceName];
    if (!servicePath) {
        console.error(`Unknown service: ${serviceName}`);
        return {};
    }

    const indexPath = path.join(__dirname, `../../${servicePath}/index.ts`);

    try {
        const content = fs.readFileSync(indexPath, 'utf-8');
        const classToProviderMap = {};

        // Match ConnectorService.register(TConnectorService.SERVICE_TYPE, 'PROVIDER_NAME', CLASS_NAME);
        const registerPattern = new RegExp(
            `ConnectorService\\.register\\(TConnectorService\\.${serviceType},\\s*['"]([^'"]+)['"],\\s*(\\w+)\\)`,
            'g',
        );

        let match;
        while ((match = registerPattern.exec(content)) !== null) {
            const providerName = match[1];
            const className = match[2];
            classToProviderMap[className] = providerName;
        }

        return classToProviderMap;
    } catch (error) {
        console.error(`Error reading ${serviceName} service index.ts: ${error.message}`);
        return {};
    }
}

/**
 * Map storage class names to provider names based on registrations in index.ts
 */
function getProviderNameFromClassName(className, classToProviderMap) {
    return classToProviderMap[className] || className;
}

/**
 * Extract connector information from connector files
 */
function extractConnectorInfo(filePath, classToProviderMap, serviceName, connectorBaseClass) {
    try {
        const content = fs.readFileSync(filePath, 'utf-8');
        const fileName = path.basename(filePath, '.class.ts');

        // Extract class name from the file
        const classMatch = content.match(new RegExp(`export\\s+class\\s+(\\w+)\\s+extends\\s+${connectorBaseClass}`));
        if (!classMatch) {
            return null;
        }

        const className = classMatch[1];
        const providerName = getProviderNameFromClassName(className, classToProviderMap);

        // Skip if this class is not registered as a provider
        if (!classToProviderMap[className]) {
            console.warn(`Class ${className} is not registered in ${serviceName} service index.ts`);
            return null;
        }

        // Extract constructor parameter type
        const constructorMatch = content.match(/constructor\s*\([^)]*:\s*([^)]+)\s*\)/);
        if (!constructorMatch) {
            console.warn(`No constructor type found for ${className}`);
            return null;
        }

        // Parse complex constructor parameter types (e.g., "SmythConfigs & OAuthConfig & { openaiApiKey?: string }")
        let configType = constructorMatch[1].trim();

        // Clean up complex types by removing extra spaces and newlines
        configType = configType.replace(/\s+/g, ' ').trim();

        // Handle complex types - for now, let's create a simplified type name
        if (configType.includes('&') || configType.includes('{')) {
            // For complex types, we'll use a convention
            configType = `T${className}Config`;
        }

        // Handle cases where configType might have extra characters due to parsing
        if (configType.includes('}')) {
            configType = `T${className}Config`;
        }

        // Extract import path for the config type
        const importMatch = content.match(
            new RegExp(`export\\s+(type|interface)\\s+${configType.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*[=\\{]`, 'm'),
        );
        let importPath = '';

        if (importMatch) {
            // Type is defined in the same file
            importPath = `@sre/IO/${serviceName}.service/connectors/${fileName}.class`;
        } else {
            // Look for import statement
            const importRegex = new RegExp(`import\\s+.*${configType.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*from\\s+['"]([^'"]+)['"]`, 'm');
            const importStatementMatch = content.match(importRegex);
            if (importStatementMatch) {
                importPath = importStatementMatch[1];
            } else {
                // For complex types, we might need to import from types file
                if (configType.startsWith('T') && configType.endsWith('Config')) {
                    importPath = `@sre/types/${serviceName}.types`;
                }
            }
        }

        return {
            className,
            providerName,
            configType,
            importPath,
            fileName,
        };
    } catch (error) {
        console.error(`Error extracting connector info from ${filePath}:`, error);
        return null;
    }
}

/**
 * Find all connector files in a directory
 */
function findConnectorFiles(connectorsDir) {
    try {
        const files = fs.readdirSync(connectorsDir);
        return files.filter((file) => file.endsWith('.class.ts')).map((file) => path.join(connectorsDir, file));
    } catch (error) {
        console.error(`Error reading connectors directory: ${error.message}`);
        return [];
    }
}

/**
 * Generate service types
 */
function generateServiceTypes(connectors, template) {
    // Generate imports
    const imports = connectors
        .filter((connector) => connector.importPath)
        .map((connector) => `import { ${connector.configType} } from '${connector.importPath}';`)
        .join('\n');

    // Generate builtin providers mapping
    const builtinProviders = connectors.map((connector) => `    ${connector.providerName}: '${connector.providerName}',`).join('\n');

    // Generate type mapping
    const typeMapping = connectors.map((connector) => `    ${connector.providerName}: ${connector.configType};`).join('\n');

    return renderTemplate(template, {
        imports,
        builtinProviders,
        typeMapping,
    });
}

/**
 * Generate LLM provider types (simplified - only provider constants)
 */
function generateLLMProviderTypes(template) {
    // Read models.ts to extract unique providers
    const modelsPath = path.join(__dirname, '../../src/subsystems/LLMManager/models.ts');

    if (!fs.existsSync(modelsPath)) {
        console.error(`Models file not found: ${modelsPath}`);
        return renderTemplate(template, { builtinProviders: '' });
    }

    const modelsContent = fs.readFileSync(modelsPath, 'utf-8');

    // Extract unique providers from the models object
    const providerMatches = modelsContent.matchAll(/provider:\s*['"`]([^'"`]+)['"`]/g);

    const providersSet = new Set();
    for (const match of providerMatches) {
        const [, provider] = match;
        providersSet.add(provider);
    }

    // Convert to sorted array for consistent output
    const providers = Array.from(providersSet).sort();

    console.log(`✅ Extracted ${providers.length} unique providers from models.ts: ${providers.join(', ')}`);

    // Generate builtin providers mapping
    const builtinProviders = providers.map((provider) => `    ${provider}: '${provider}',`).join('\n');

    return renderTemplate(template, {
        builtinProviders,
    });
}

/**
 * Generate types for a specific service
 */
async function generateTypesForService(
    serviceName,
    serviceType,
    connectorsDir,
    templatePath,
    connectorBaseClass,
    outputFileName,
    isSimplified = false,
) {
    console.log(`\n📋 Processing ${serviceName} service...`);

    // Load template
    const template = loadTemplate(templatePath);

    // Extract provider registrations from index.ts
    console.log(`📋 Reading provider registrations from ${serviceName} service index.ts...`);
    const classToProviderMap = extractProviderRegistrations(serviceName, serviceType);
    console.log(
        `✅ Found ${Object.keys(classToProviderMap).length} registered providers: ${Object.entries(classToProviderMap)
            .map(([cls, prov]) => `${cls}→${prov}`)
            .join(', ')}`,
    );

    // For simplified generation (LLM), we only need provider constants
    // if (isSimplified) {
    //     try {
    //         const typesCode = generateLLMProviderTypes(template);
    //         const outputPath = path.join(TYPES_OUTPUT_DIR, outputFileName);

    //         fs.writeFileSync(outputPath, typesCode);
    //         console.log(`✅ Generated ${outputFileName} (simplified)`);
    //         return true;
    //     } catch (error) {
    //         console.error(`❌ Error generating ${serviceName} types:`, error);
    //         return false;
    //     }
    // }

    // Find all connector files
    const connectorFiles = findConnectorFiles(connectorsDir);
    console.log(`📁 Found ${connectorFiles.length} connector files`);

    // Extract connector information
    const connectors = [];
    process.stdout.write(`📝 Processing ${serviceName} connectors `);

    for (const filePath of connectorFiles) {
        const connectorInfo = extractConnectorInfo(filePath, classToProviderMap, serviceName, connectorBaseClass);
        if (connectorInfo) {
            connectors.push(connectorInfo);
            process.stdout.write('.');
        } else {
            process.stdout.write('!');
        }
    }

    process.stdout.write('\n');

    if (connectors.length === 0) {
        console.log(`⚠️ No valid ${serviceName} connectors found`);
        return false;
    }

    console.log(`✅ Processed ${connectors.length} ${serviceName} connectors: ${connectors.map((c) => c.className).join(', ')}`);

    // Generate types
    try {
        const typesCode = generateServiceTypes(connectors, template);
        const outputPath = path.join(TYPES_OUTPUT_DIR, outputFileName);

        fs.writeFileSync(outputPath, typesCode);
        console.log(`✅ Generated ${outputFileName}`);
        return true;
    } catch (error) {
        console.error(`❌ Error generating ${serviceName} types:`, error);
        return false;
    }
}

/**
 * Main generation function
 */
async function generateTypes() {
    console.log('🚀 Starting SDK types generation...');

    // Ensure output directory exists
    if (!fs.existsSync(TYPES_OUTPUT_DIR)) {
        fs.mkdirSync(TYPES_OUTPUT_DIR, { recursive: true });
    }

    const services = [
        {
            name: 'Storage',
            type: 'Storage',
            connectorsDir: STORAGE_CONNECTORS_DIR,
            templatePath: STORAGE_TEMPLATE_PATH,
            connectorBaseClass: 'StorageConnector',
            outputFileName: 'Storage.types.ts',
        },
        {
            name: 'VectorDB',
            type: 'VectorDB',
            connectorsDir: VECTORDB_CONNECTORS_DIR,
            templatePath: VECTORDB_TEMPLATE_PATH,
            connectorBaseClass: 'VectorDBConnector',
            outputFileName: 'VectorDB.types.ts',
        },
        {
            name: 'LLM',
            type: 'LLM',
            connectorsDir: LLM_CONNECTORS_DIR,
            templatePath: LLM_TEMPLATE_PATH,
            connectorBaseClass: 'LLMConnector',
            outputFileName: 'LLM.types.ts',
            isSimplified: true, // LLM only needs provider constants, not config mappings
        },
    ];

    let successCount = 0;
    const generatedFiles = [];

    for (const service of services) {
        const success = await generateTypesForService(
            service.name,
            service.type,
            service.connectorsDir,
            service.templatePath,
            service.connectorBaseClass,
            service.outputFileName,
            service.isSimplified,
        );

        if (success) {
            successCount++;
            generatedFiles.push(`src/sdk/types/generated/${service.outputFileName}`);
        }
    }

    console.log(`\n🎉 Types generation completed!`);
    console.log(`✅ Generated ${successCount}/${services.length} type files:`);
    generatedFiles.forEach((file) => console.log(`   • ${file}`));
}

// Run the generator
generateTypes().catch(console.error);
