/**
 * TS2614: Module has no exported member (import/export mismatch) fixer
 * Handles cases where imports use wrong syntax (named vs default)
 */

import { CodeIssue } from '../../sandbox/sandboxTypes';
import { FixerContext, FixResult, FixedIssue, UnfixableIssue, FileObject } from '../types';
import { generateCode, traverseAST } from '../utils/ast';
import { findImportAtLocation, getFileAST, getFileExports } from '../utils/imports';
import { findModuleFile } from '../utils/paths';
import { createObjectLogger } from '../../../logger';
import * as t from '@babel/types';
import { handleFixerError } from '../utils/helpers';

const logger = createObjectLogger({ name: 'TS2614Fixer' }, 'TS2614Fixer');

/**
 * Fix TS2614 "Module has no exported member" errors (import/export mismatch)
 * Corrects import statements to match actual export types
 */
export function fixImportExportTypeMismatch(
    context: FixerContext,
    issues: CodeIssue[]
): FixResult {
    logger.info(`Starting TS2614 fixer with ${issues.length} issues`);
    
    const fixedIssues: FixedIssue[] = [];
    const unfixableIssues: UnfixableIssue[] = [];
    const modifiedFiles: FileObject[] = [];
    const newFiles: FileObject[] = [];

    for (const issue of issues) {
        logger.info(`Processing TS2614 issue: ${issue.message} at ${issue.filePath}:${issue.line}`);
        
        try {
            // Get AST for the file with the import issue
            logger.info(`Getting AST for source file: ${issue.filePath}`);
            const sourceAST = getFileAST(issue.filePath, context.files);
            if (!sourceAST) {
                logger.warn(`Failed to get AST for ${issue.filePath}`);
                unfixableIssues.push({
                    issueCode: 'TS2614',
                    filePath: issue.filePath,
                    line: issue.line,
                    column: issue.column,
                    originalMessage: issue.message,
                    reason: 'Failed to parse source file AST'
                });
                continue;
            }

            // Find the import at the error location
            logger.info(`Finding import at line ${issue.line} in ${issue.filePath}`);
            const importInfo = findImportAtLocation(sourceAST, issue.line);
            if (!importInfo) {
                logger.warn(`No import found at line ${issue.line} in ${issue.filePath}`);
                unfixableIssues.push({
                    issueCode: 'TS2614',
                    filePath: issue.filePath,
                    line: issue.line,
                    column: issue.column,
                    originalMessage: issue.message,
                    reason: 'No import found at specified location'
                });
                continue;
            }
            
            logger.info(`Found import: ${importInfo.moduleSpecifier}, default: ${importInfo.defaultImport}, named: [${importInfo.namedImports.join(', ')}]`);

            // Find the target file
            logger.info(`Searching for target file: ${importInfo.moduleSpecifier}`);
            const targetFile = findModuleFile(
                importInfo.moduleSpecifier, 
                issue.filePath, 
                context.files
            );
            
            if (!targetFile) {
                logger.warn(`Target file not found for module: ${importInfo.moduleSpecifier}`);
                unfixableIssues.push({
                    issueCode: 'TS2614',
                    filePath: issue.filePath,
                    line: issue.line,
                    column: issue.column,
                    originalMessage: issue.message,
                    reason: `Target file not found for module: ${importInfo.moduleSpecifier}`
                });
                continue;
            }
            logger.info(`Found target file: ${targetFile}`);

            // Get AST for target file to analyze actual exports
            logger.info(`Getting AST for target file: ${targetFile}`);
            const targetAST = getFileAST(targetFile, context.files);
            if (!targetAST) {
                logger.warn(`Failed to parse target file: ${targetFile}`);
                unfixableIssues.push({
                    issueCode: 'TS2614',
                    filePath: issue.filePath,
                    line: issue.line,
                    column: issue.column,
                    originalMessage: issue.message,
                    reason: `Failed to parse target file: ${targetFile}`
                });
                continue;
            }

            // Analyze target file's exports
            logger.info(`Analyzing exports in target file: ${targetFile}`);
            const targetExports = getFileExports(targetAST);
            logger.info(`Found exports - defaultExport: ${targetExports.defaultExport || 'none'}, named: [${targetExports.namedExports.join(', ')}]`);

            // Determine the mismatch type and fix it
            const mismatchAnalysis = analyzeMismatch(importInfo, targetExports);
            if (!mismatchAnalysis) {
                logger.warn(`Could not determine mismatch type for ${importInfo.moduleSpecifier}`);
                unfixableIssues.push({
                    issueCode: 'TS2614',
                    filePath: issue.filePath,
                    line: issue.line,
                    column: issue.column,
                    originalMessage: issue.message,
                    reason: 'Could not determine import/export mismatch type'
                });
                continue;
            }

            logger.info(`Mismatch analysis: ${mismatchAnalysis.type} - ${mismatchAnalysis.description}`);

            // Apply the fix to the import statement
            const fixedAST = fixImportStatement(sourceAST, importInfo, mismatchAnalysis);
            const generatedCode = generateCode(fixedAST);
            
            modifiedFiles.push({
                filePath: issue.filePath,
                fileContents: generatedCode.code,
            });
            
            fixedIssues.push({
                issueCode: 'TS2614',
                filePath: issue.filePath,
                line: issue.line,
                column: issue.column,
                originalMessage: issue.message,
                fixApplied: mismatchAnalysis.description,
                fixType: 'import_fix',
            });
            logger.info(`Successfully fixed import/export mismatch in ${issue.filePath}`);
            
        } catch (error) {
            logger.error(`Failed to fix TS2614 issue at ${issue.filePath}:${issue.line}: ${error instanceof Error ? error.message : 'Unknown error'}`, error);
            unfixableIssues.push(handleFixerError(issue, error as Error, 'TS2614Fixer'));
        }
    }

    logger.info(`TS2614 fixer completed: ${fixedIssues.length} fixed, ${unfixableIssues.length} unfixable, ${modifiedFiles.length} modified files, ${newFiles.length} new files`);
    
    return {
        fixedIssues,
        unfixableIssues,
        modifiedFiles,
        newFiles
    };
}

interface MismatchAnalysis {
    type: 'named-to-default' | 'default-to-named' | 'partial-match' | 'complex-partial' | 'default-to-named-typo';
    description: string;
    targetName?: string;
    sourceNames?: string[];
    additionalData?: {
        defaultConversions?: string[];
        typoCorrections?: Array<{ invalid: string; correct: string } | null>;
    };
}

/**
 * Calculate Levenshtein distance between two strings
 * Used for detecting typos in import names
 */
function levenshteinDistance(str1: string, str2: string): number {
    const matrix: number[][] = [];
    
    for (let i = 0; i <= str2.length; i++) {
        matrix[i] = [i];
    }
    
    for (let j = 0; j <= str1.length; j++) {
        matrix[0][j] = j;
    }
    
    for (let i = 1; i <= str2.length; i++) {
        for (let j = 1; j <= str1.length; j++) {
            if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
                matrix[i][j] = matrix[i - 1][j - 1];
            } else {
                matrix[i][j] = Math.min(
                    matrix[i - 1][j - 1] + 1, // substitution
                    matrix[i][j - 1] + 1,     // insertion
                    matrix[i - 1][j] + 1      // deletion
                );
            }
        }
    }
    
    return matrix[str2.length][str1.length];
}

/**
 * Analyze import/export mismatch and determine fix strategy
 * Enhanced to handle complex partial matches
 */
function analyzeMismatch(importInfo: { defaultImport?: string; namedImports: string[] }, targetExports: { defaultExport?: string; namedExports: string[] }): MismatchAnalysis | null {
    
    // Case 1: Trying to import something as named when it's actually default export
    if (importInfo.namedImports.length > 0 && !importInfo.defaultImport && targetExports.defaultExport) {
        // Check if any named import matches the default export name
        const matchingNamedImport = importInfo.namedImports.find(name => 
            name === targetExports.defaultExport || 
            name.toLowerCase() === targetExports.defaultExport?.toLowerCase()
        );
        
        if (matchingNamedImport) {
            return {
                type: 'named-to-default',
                description: `Changed import from named '${matchingNamedImport}' to default import`,
                targetName: matchingNamedImport
            };
        }
    }
    
    // Case 2: Trying to import as default when it's actually a named export
    if (importInfo.defaultImport && !targetExports.defaultExport && targetExports.namedExports.length > 0) {
        // Check if the default import name matches any named export
        const matchingNamedExport = targetExports.namedExports.find(name => 
            name === importInfo.defaultImport || 
            name.toLowerCase() === importInfo.defaultImport?.toLowerCase()
        );
        
        if (matchingNamedExport) {
            return {
                type: 'default-to-named',
                description: `Changed import from default '${importInfo.defaultImport}' to named import '${matchingNamedExport}'`,
                targetName: matchingNamedExport
            };
        }
    }
    
    // Case 3: Mixed imports - some correct, some need conversion
    if (importInfo.namedImports.length > 0) {
        const validNamedImports = importInfo.namedImports.filter(name => 
            targetExports.namedExports.includes(name)
        );
        const invalidNamedImports = importInfo.namedImports.filter(name => 
            !targetExports.namedExports.includes(name)
        );
        
        // Check if any invalid named imports match the default export
        const needsDefaultConversion = invalidNamedImports.filter(name =>
            targetExports.defaultExport && (
                name === targetExports.defaultExport || 
                name.toLowerCase() === targetExports.defaultExport?.toLowerCase()
            )
        );
        
        // Check if any invalid named imports have similar names in exports (typos)
        const possibleTypos = invalidNamedImports.map(invalidName => {
            const similar = targetExports.namedExports.find(exportName => {
                const distance = levenshteinDistance(invalidName.toLowerCase(), exportName.toLowerCase());
                return distance <= 2; // Allow up to 2 character differences
            });
            return similar ? { invalid: invalidName, correct: similar } : null;
        }).filter(Boolean);
        
        if (needsDefaultConversion.length > 0 || validNamedImports.length > 0 || possibleTypos.length > 0) {
            return {
                type: 'complex-partial',
                description: `Complex import fix: ${needsDefaultConversion.length} to default, ${validNamedImports.length} valid named, ${possibleTypos.length} typo fixes`,
                targetName: needsDefaultConversion[0], // Primary default conversion
                sourceNames: validNamedImports,
                additionalData: {
                    defaultConversions: needsDefaultConversion,
                    typoCorrections: possibleTypos
                }
            };
        }
    }
    
    // Case 4: Check if default import matches a named export (opposite of case 1)
    if (importInfo.defaultImport && targetExports.namedExports.length > 0) {
        const matchingNamedExport = targetExports.namedExports.find(name =>
            name === importInfo.defaultImport ||
            name.toLowerCase() === importInfo.defaultImport?.toLowerCase()
        );
        
        if (matchingNamedExport) {
            return {
                type: 'default-to-named',
                description: `Changed import from default '${importInfo.defaultImport}' to named import '${matchingNamedExport}'`,
                targetName: matchingNamedExport
            };
        }
        
        // Check if default import name is close to any named export (typo)
        const similarNamed = targetExports.namedExports.find(name => {
            const distance = levenshteinDistance(importInfo.defaultImport!.toLowerCase(), name.toLowerCase());
            return distance <= 2;
        });
        
        if (similarNamed) {
            return {
                type: 'default-to-named-typo',
                description: `Changed default import '${importInfo.defaultImport}' to named import '${similarNamed}' (possible typo)`,
                targetName: similarNamed
            };
        }
    }
    
    return null;
}

/**
 * Fix the import statement based on mismatch analysis
 */
function fixImportStatement(
    ast: t.File, 
    importInfo: { moduleSpecifier: string; defaultImport?: string; namedImports: string[] },
    mismatchAnalysis: MismatchAnalysis
): t.File {
    
    traverseAST(ast, {
        ImportDeclaration(path) {
            if (t.isStringLiteral(path.node.source) && path.node.source.value === importInfo.moduleSpecifier) {
                
                switch (mismatchAnalysis.type) {
                    case 'named-to-default':
                        // Convert named import to default import
                        if (mismatchAnalysis.targetName) {
                            // Find the original specifier to preserve local alias if any
                            const orig = path.node.specifiers.find(s => 
                                t.isImportSpecifier(s) && 
                                t.isIdentifier(s.imported) && 
                                s.imported.name === mismatchAnalysis.targetName
                            ) as t.ImportSpecifier | undefined;
                            const localName = orig && t.isIdentifier(orig.local) ? orig.local.name : mismatchAnalysis.targetName;
                            path.node.specifiers = [
                                t.importDefaultSpecifier(t.identifier(localName))
                            ];
                        }
                        break;
                        
                    case 'default-to-named':
                        // Convert default import to named import
                        if (mismatchAnalysis.targetName && importInfo.defaultImport) {
                            path.node.specifiers = [
                                t.importSpecifier(
                                    t.identifier(importInfo.defaultImport),
                                    t.identifier(mismatchAnalysis.targetName)
                                )
                            ];
                        }
                        break;
                        
                    case 'partial-match':
                    case 'complex-partial':
                        // Handle complex partial matches with multiple conversions
                        if (mismatchAnalysis.additionalData) {
                            const newSpecifiers: (t.ImportDefaultSpecifier | t.ImportSpecifier)[] = [];
                            const specifiers = path.node.specifiers;
                            
                            // Add default import for names that need conversion to default
                            const defaultConversions = mismatchAnalysis.additionalData.defaultConversions || [];
                            if (defaultConversions.length > 0) {
                                const firstDefault = defaultConversions[0];
                                const defaultSpec = specifiers.find(s => 
                                    t.isImportSpecifier(s) && 
                                    t.isIdentifier(s.imported) && 
                                    s.imported.name === firstDefault
                                ) as t.ImportSpecifier | undefined;
                                const defaultLocal = defaultSpec && t.isIdentifier(defaultSpec.local) ? defaultSpec.local.name : firstDefault;
                                newSpecifiers.push(t.importDefaultSpecifier(t.identifier(defaultLocal)));
                            }
                            
                            // Keep valid named imports
                            if (mismatchAnalysis.sourceNames) {
                                for (const validName of mismatchAnalysis.sourceNames) {
                                    const orig = specifiers.find(s => 
                                        t.isImportSpecifier(s) && 
                                        t.isIdentifier(s.imported) && 
                                        s.imported.name === validName
                                    ) as t.ImportSpecifier | undefined;
                                    const local = orig && t.isIdentifier(orig.local) ? orig.local.name : validName;
                                    newSpecifiers.push(
                                        t.importSpecifier(t.identifier(local), t.identifier(validName))
                                    );
                                }
                            }
                            
                            // Fix typos in named imports
                            const typoCorrections = mismatchAnalysis.additionalData.typoCorrections || [];
                            for (const correction of typoCorrections) {
                                if (correction) {
                                    const orig = specifiers.find(s => 
                                        t.isImportSpecifier(s) && 
                                        t.isIdentifier(s.imported) && 
                                        s.imported.name === correction.invalid
                                    ) as t.ImportSpecifier | undefined;
                                    const local = orig && t.isIdentifier(orig.local) ? orig.local.name : correction.correct;
                                    newSpecifiers.push(
                                        t.importSpecifier(t.identifier(local), t.identifier(correction.correct))
                                    );
                                }
                            }
                            
                            path.node.specifiers = newSpecifiers;
                        } else if (mismatchAnalysis.targetName && mismatchAnalysis.sourceNames) {
                            // Fallback to simple partial match handling
                            const newSpecifiers: (t.ImportDefaultSpecifier | t.ImportSpecifier)[] = [];
                            const specifiers = path.node.specifiers;

                            // Add default import for the converted name
                            const invalidSpec = specifiers.find(s => 
                                t.isImportSpecifier(s) && 
                                t.isIdentifier(s.imported) && 
                                s.imported.name === mismatchAnalysis.targetName
                            ) as t.ImportSpecifier | undefined;
                            const defaultLocal = invalidSpec && t.isIdentifier(invalidSpec.local) ? invalidSpec.local.name : mismatchAnalysis.targetName;
                            newSpecifiers.push(t.importDefaultSpecifier(t.identifier(defaultLocal)));

                            // Keep valid named imports
                            for (const validName of mismatchAnalysis.sourceNames) {
                                const orig = specifiers.find(s => 
                                    t.isImportSpecifier(s) && 
                                    t.isIdentifier(s.imported) && 
                                    s.imported.name === validName
                                ) as t.ImportSpecifier | undefined;
                                const local = orig && t.isIdentifier(orig.local) ? orig.local.name : validName;
                                newSpecifiers.push(
                                    t.importSpecifier(t.identifier(local), t.identifier(validName))
                                );
                            }
                            
                            path.node.specifiers = newSpecifiers;
                        }
                        break;
                    
                    case 'default-to-named-typo':
                        // Convert default import to named import with typo correction
                        if (mismatchAnalysis.targetName && importInfo.defaultImport) {
                            path.node.specifiers = [
                                t.importSpecifier(
                                    t.identifier(importInfo.defaultImport),
                                    t.identifier(mismatchAnalysis.targetName)
                                )
                            ];
                        }
                        break;
                }
            }
        }
    });
    
    return ast;
}

