import type { Plugin } from 'vite';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import fs from 'fs';

const execAsync = promisify(exec);

/**
 * Git 版本管理 API 插件
 * 提供基于 Git 的版本控制功能，用于页面和元素的文件夹级别版本管理
 */
export function gitVersionApiPlugin(): Plugin {
  let gitAvailable = false;
  let gitCheckError: string | null = null;

  const resolveModuleFile = (basePath: string): string | null => {
    const fileCandidates = [basePath, `${basePath}.ts`, `${basePath}.tsx`, `${basePath}.js`, `${basePath}.jsx`];
    for (const candidate of fileCandidates) {
      try {
        if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
          return candidate;
        }
      } catch {
        // Ignore fs race/errors and continue probing other candidates.
      }
    }

    const indexCandidates = [
      path.join(basePath, 'index.ts'),
      path.join(basePath, 'index.tsx'),
      path.join(basePath, 'index.js'),
      path.join(basePath, 'index.jsx'),
    ];

    for (const candidate of indexCandidates) {
      try {
        if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
          return candidate;
        }
      } catch {
        // Ignore fs race/errors and continue probing other candidates.
      }
    }

    return null;
  };

  return {
    name: 'git-version-api',

    /**
     * 解决跨文件夹导入问题：
     * 当从 .git-versions/{id}/src/... 中的文件发起相对导入时，如果目标文件
     * 不在已提取的版本目录中（如 ../../components/side-menu），则回退到
     * 当前工作目录的 src/ 下去解析。
     */
    resolveId(source, importer) {
      if (!importer) return null;

      const projectRoot = process.cwd();
      const gitVersionsDir = path.join(projectRoot, '.git-versions');
      const normalizedImporter = path.normalize(importer);

      // 只处理来自 .git-versions 目录中的导入
      if (!normalizedImporter.startsWith(gitVersionsDir)) return null;

      // 解析相对路径导入
      const resolved = path.resolve(path.dirname(normalizedImporter), source);

      // 如果解析后的路径仍在 .git-versions 内且文件不存在，则回退到真实 src/
      if (!resolved.startsWith(gitVersionsDir)) return null;

      const versionResolvedPath = resolveModuleFile(resolved);
      if (versionResolvedPath) {
        return versionResolvedPath;
      }

      // 文件不存在 → 提取 .git-versions/{id}/ 之后的相对路径，映射到真实 src/
      const relativeTail = path.relative(gitVersionsDir, resolved);
      // relativeTail 形如 "67c09dc5/src/components/side-menu"
      const slashIdx = relativeTail.indexOf(path.sep);
      if (slashIdx < 0) return null;
      const pathAfterVersionId = relativeTail.substring(slashIdx + 1);
      // pathAfterVersionId 形如 "src/components/side-menu"

      const realPath = path.join(projectRoot, pathAfterVersionId);

      return resolveModuleFile(realPath);
    },

    configureServer(server) {
      const projectRoot = process.cwd();
      
      // 检查 Git 是否可用
      (async () => {
        try {
          await execAsync('git --version', { cwd: projectRoot });
          gitAvailable = true;
          console.log('[Git 版本管理] ✅ Git 已就绪');
        } catch (error: any) {
          gitAvailable = false;
          gitCheckError = error.message;
          console.warn('[Git 版本管理] ⚠️  Git 未安装或不可用');
          console.warn('[Git 版本管理] 💡 请安装 Git 以使用版本管理功能: https://git-scm.com/downloads');
        }
      })();
      
      // 服务器启动时清理临时版本文件
      const gitVersionsDir = path.join(projectRoot, '.git-versions');
      
      if (fs.existsSync(gitVersionsDir)) {
        try {
          console.log('[Git 版本管理] 清理临时版本文件...');
          fs.rmSync(gitVersionsDir, { recursive: true, force: true });
          console.log('[Git 版本管理] ✅ 临时版本文件已清理');
        } catch (error) {
          console.error('[Git 版本管理] ⚠️  清理临时文件失败:', error);
        }
      }
      
      // Helper function to parse JSON body
      const parseBody = (req: any): Promise<any> => {
        return new Promise((resolve, reject) => {
          let body = '';
          req.on('data', (chunk: any) => body += chunk);
          req.on('end', () => {
            try {
              resolve(body ? JSON.parse(body) : {});
            } catch (e) {
              reject(new Error('Invalid JSON in request body'));
            }
          });
          req.on('error', reject);
        });
      };

      // Helper function to send JSON response
      const sendJSON = (res: any, statusCode: number, data: any) => {
        res.statusCode = statusCode;
        res.setHeader('Content-Type', 'application/json; charset=utf-8');
        res.end(JSON.stringify(data));
      };

      // Helper function to check git availability and return error response if not available
      const checkGitAvailable = (res: any): boolean => {
        if (!gitAvailable) {
          sendJSON(res, 503, { 
            error: 'Git 未安装或不可用',
            message: '版本管理功能需要 Git 支持。请先安装 Git 后重启开发服务器。',
            details: gitCheckError || undefined
          });
          return false;
        }
        return true;
      };

      // Helper function to execute git command
      const execGit = async (command: string, cwd: string) => {
        try {
          const { stdout, stderr } = await execAsync(command, { cwd, maxBuffer: 1024 * 1024 * 10 });
          return { stdout: stdout.trim(), stderr: stderr.trim() };
        } catch (error: any) {
          // 提供更友好的错误信息
          if (error.message.includes('not a git repository')) {
            throw new Error('当前项目不是 Git 仓库。请先运行 "git init" 初始化仓库。');
          }
          throw new Error(`Git 命令执行失败: ${error.message}`);
        }
      };

      const normalizeTargetPath = (rawTargetPath: string, projectRoot: string) => {
        const sourceRoot = path.resolve(projectRoot, 'src');
        const trimmedPath = String(rawTargetPath || '').trim();

        if (!trimmedPath) {
          throw new Error('Missing path parameter');
        }

        let normalizedPath = trimmedPath.replace(/\\/g, '/');
        const normalizedProjectRoot = projectRoot.replace(/\\/g, '/').replace(/\/+$/, '');
        const normalizedSourceRoot = sourceRoot.replace(/\\/g, '/').replace(/\/+$/, '');

        if (normalizedPath.startsWith(normalizedSourceRoot + '/')) {
          normalizedPath = normalizedPath.slice(normalizedSourceRoot.length + 1);
        } else if (normalizedPath.startsWith(normalizedProjectRoot + '/src/')) {
          normalizedPath = normalizedPath.slice(normalizedProjectRoot.length + '/src/'.length);
        } else {
          const srcMarkerIndex = normalizedPath.lastIndexOf('/src/');
          if (srcMarkerIndex >= 0) {
            normalizedPath = normalizedPath.slice(srcMarkerIndex + '/src/'.length);
          } else if (normalizedPath.startsWith('src/')) {
            normalizedPath = normalizedPath.slice('src/'.length);
          }
        }

        normalizedPath = normalizedPath
          .replace(/^\/+/, '')
          .replace(/\/index\.(t|j)sx?$/i, '')
          .replace(/\/+$/, '');

        if (!normalizedPath) {
          throw new Error('Invalid path');
        }

        const segments = normalizedPath.split('/').filter(Boolean);
        if (segments.length === 0 || segments.some((segment) => segment === '.' || segment === '..')) {
          throw new Error('Invalid path');
        }

        const resolvedFolderPath = path.resolve(sourceRoot, normalizedPath);
        const relativeToSourceRoot = path.relative(sourceRoot, resolvedFolderPath);

        if (!relativeToSourceRoot || relativeToSourceRoot.startsWith('..') || path.isAbsolute(relativeToSourceRoot)) {
          throw new Error('Invalid path');
        }

        return {
          sourceRoot,
          targetPath: segments.join('/'),
          folderPath: resolvedFolderPath,
        };
      };

      // Main middleware for git version API
      server.middlewares.use(async (req: any, res: any, next: any) => {
        // Only handle /api/git/* routes
        if (!req.url.startsWith('/api/git')) {
          return next();
        }

        try {
          const url = new URL(req.url, `http://${req.headers.host}`);
          const pathname = url.pathname;

          // Route: GET /api/git/history - Get git history for a folder
          if (pathname === '/api/git/history' && req.method === 'GET') {
            if (!checkGitAvailable(res)) return;
            
            const rawTargetPath = url.searchParams.get('path'); // e.g., 'prototypes/home' or 'components/button'
            
            if (!rawTargetPath) {
              sendJSON(res, 400, { error: 'Missing path parameter' });
              return;
            }

            const projectRoot = process.cwd();
            let targetPath = '';
            let folderPath = '';

            try {
              ({ targetPath, folderPath } = normalizeTargetPath(rawTargetPath, projectRoot));
            } catch (error: any) {
              sendJSON(res, 403, { error: 'Invalid path' });
              return;
            }

            if (!fs.existsSync(folderPath)) {
              sendJSON(res, 404, { error: 'Folder not found' });
              return;
            }

            // Get git log for the folder (last 20 commits)
            const gitCommand = `git log -20 --pretty=format:'%H|%an|%ae|%at|%s' -- src/${targetPath}`;
            const { stdout } = await execGit(gitCommand, projectRoot);

            if (!stdout) {
              sendJSON(res, 200, { commits: [], hasUncommitted: false });
              return;
            }

            const commits = stdout.split('\n').map(line => {
              const [hash, author, email, timestamp, message] = line.split('|');
              return {
                hash,
                author,
                email,
                timestamp: parseInt(timestamp) * 1000, // Convert to milliseconds
                message,
                date: new Date(parseInt(timestamp) * 1000).toISOString()
              };
            });

            // Check for uncommitted changes in the folder
            const statusCommand = `git status --porcelain -- src/${targetPath}`;
            const { stdout: statusOutput } = await execGit(statusCommand, projectRoot);
            const hasUncommitted = statusOutput.length > 0;

            sendJSON(res, 200, { commits, hasUncommitted, uncommittedFiles: statusOutput });
            return;
          }

          // Route: POST /api/git/restore - Restore folder to a specific commit
          if (pathname === '/api/git/restore' && req.method === 'POST') {
            if (!checkGitAvailable(res)) return;
            
            const body = await parseBody(req);
            const { path: rawTargetPath, commitHash } = body;

            if (!rawTargetPath || !commitHash) {
              sendJSON(res, 400, { error: 'Missing path or commitHash parameter' });
              return;
            }

            const projectRoot = process.cwd();
            let targetPath = '';
            let folderPath = '';

            try {
              ({ targetPath, folderPath } = normalizeTargetPath(rawTargetPath, projectRoot));
            } catch (error: any) {
              sendJSON(res, 403, { error: 'Invalid path' });
              return;
            }

            if (!fs.existsSync(folderPath)) {
              sendJSON(res, 404, { error: 'Folder not found' });
              return;
            }

            // Verify commit exists
            const verifyCommand = `git cat-file -t ${commitHash}`;
            try {
              await execGit(verifyCommand, projectRoot);
            } catch (error) {
              sendJSON(res, 400, { error: 'Invalid commit hash' });
              return;
            }

            // Restore folder to specific commit
            const restoreCommand = `git checkout ${commitHash} -- src/${targetPath}`;
            await execGit(restoreCommand, projectRoot);

            sendJSON(res, 200, { success: true, message: 'Folder restored successfully' });
            return;
          }

          // Route: POST /api/git/commit - Commit changes for a folder
          if (pathname === '/api/git/commit' && req.method === 'POST') {
            if (!checkGitAvailable(res)) return;
            
            const body = await parseBody(req);
            const { path: rawTargetPath, message } = body;

            if (!rawTargetPath || !message) {
              sendJSON(res, 400, { error: 'Missing path or message parameter' });
              return;
            }

            const projectRoot = process.cwd();
            let targetPath = '';
            let folderPath = '';

            try {
              ({ targetPath, folderPath } = normalizeTargetPath(rawTargetPath, projectRoot));
            } catch (error: any) {
              sendJSON(res, 403, { error: 'Invalid path' });
              return;
            }

            if (!fs.existsSync(folderPath)) {
              sendJSON(res, 404, { error: 'Folder not found' });
              return;
            }

            // Check if there are changes to commit
            const statusCommand = `git status --porcelain -- src/${targetPath}`;
            const { stdout: statusOutput } = await execGit(statusCommand, projectRoot);

            if (!statusOutput) {
              sendJSON(res, 400, { error: 'No changes to commit' });
              return;
            }

            // Add and commit changes
            const addCommand = `git add src/${targetPath}`;
            await execGit(addCommand, projectRoot);

            const commitCommand = `git commit -m "${message.replace(/"/g, '\\"')}"`;
            const { stdout: commitOutput } = await execGit(commitCommand, projectRoot);

            sendJSON(res, 200, { success: true, message: 'Changes committed successfully', output: commitOutput });
            return;
          }

          // Route: GET /api/git/diff - Get diff for uncommitted changes
          if (pathname === '/api/git/diff' && req.method === 'GET') {
            if (!checkGitAvailable(res)) return;
            
            const rawTargetPath = url.searchParams.get('path');
            
            if (!rawTargetPath) {
              sendJSON(res, 400, { error: 'Missing path parameter' });
              return;
            }

            const projectRoot = process.cwd();
            let targetPath = '';
            let folderPath = '';

            try {
              ({ targetPath, folderPath } = normalizeTargetPath(rawTargetPath, projectRoot));
            } catch (error: any) {
              sendJSON(res, 403, { error: 'Invalid path' });
              return;
            }

            if (!fs.existsSync(folderPath)) {
              sendJSON(res, 404, { error: 'Folder not found' });
              return;
            }

            // Get diff for uncommitted changes
            const diffCommand = `git diff -- src/${targetPath}`;
            const { stdout: diffOutput } = await execGit(diffCommand, projectRoot);

            // Get list of changed files
            const statusCommand = `git status --porcelain -- src/${targetPath}`;
            const { stdout: statusOutput } = await execGit(statusCommand, projectRoot);

            const changedFiles = statusOutput.split('\n').filter(line => line.trim()).map(line => {
              const status = line.substring(0, 2).trim();
              const file = line.substring(3);
              return { status, file };
            });

            sendJSON(res, 200, { diff: diffOutput, changedFiles });
            return;
          }

          // Route: POST /api/git/build-version - Extract version files (no build)
          if (pathname === '/api/git/build-version' && req.method === 'POST') {
            if (!checkGitAvailable(res)) return;
            
            const body = await parseBody(req);
            const { path: rawTargetPath, commitHash } = body;

            if (!rawTargetPath || !commitHash) {
              sendJSON(res, 400, { error: 'Missing path or commitHash parameter' });
              return;
            }

            const projectRoot = process.cwd();
            let targetPath = '';
            let folderPath = '';

            try {
              ({ targetPath, folderPath } = normalizeTargetPath(rawTargetPath, projectRoot));
            } catch (error: any) {
              sendJSON(res, 403, { error: 'Invalid path' });
              return;
            }

            if (!fs.existsSync(folderPath)) {
              sendJSON(res, 404, { error: 'Folder not found' });
              return;
            }

            try {
              // Create temporary directory for version files
              const versionId = commitHash.substring(0, 8);
              const tempDir = path.join(projectRoot, '.git-versions', versionId);
              const srcPath = path.join(tempDir, 'src', targetPath);
              
              // Check if already extracted
              if (fs.existsSync(srcPath)) {
                console.log('[Git 版本管理] 使用已提取的版本:', versionId);
                const specPath = path.join(srcPath, 'spec.md');
                const indexPath = path.join(srcPath, 'index.tsx');
                const hasSpec = fs.existsSync(specPath);
                const hasPrototype = fs.existsSync(indexPath);
                
                sendJSON(res, 200, { 
                  success: true, 
                  versionId,
                  hasSpec,
                  hasPrototype,
                  specUrl: hasSpec ? `/${targetPath}/spec.html?ver=${versionId}` : null,
                  prototypeUrl: hasPrototype ? `/${targetPath}/index.html?ver=${versionId}` : null,
                  cached: true
                });
                return;
              }
              
              fs.mkdirSync(srcPath, { recursive: true });

              console.log('[Git 版本管理] 开始提取版本文件:', versionId, targetPath);

              // Get list of files in the commit
              const listCommand = `git ls-tree -r --name-only ${commitHash} src/${targetPath}`;
              const { stdout: fileList } = await execGit(listCommand, projectRoot);
              
              if (!fileList.trim()) {
                sendJSON(res, 404, { error: 'No files found in this version' });
                return;
              }

              // Extract each file
              const files = fileList.trim().split('\n');
              for (const file of files) {
                const targetFile = path.join(tempDir, file);
                const targetDir = path.dirname(targetFile);
                
                // Create directory if needed
                fs.mkdirSync(targetDir, { recursive: true });
                
                // Get file content from git
                const showCommand = `git show ${commitHash}:${file}`;
                const { stdout: content } = await execGit(showCommand, projectRoot);
                fs.writeFileSync(targetFile, content);
              }

              console.log('[Git 版本管理] ✅ 文件提取完成');

              // Check what files exist in the version
              const specPath = path.join(srcPath, 'spec.md');
              const indexPath = path.join(srcPath, 'index.tsx');
              const hasSpec = fs.existsSync(specPath);
              const hasPrototype = fs.existsSync(indexPath);

              // Generate URLs for accessing the version
              const specUrl = hasSpec ? `/${targetPath}/spec.html?ver=${versionId}` : null;
              const prototypeUrl = hasPrototype ? `/${targetPath}/index.html?ver=${versionId}` : null;

              sendJSON(res, 200, { 
                success: true, 
                versionId,
                hasSpec,
                hasPrototype,
                specUrl,
                prototypeUrl
              });
            } catch (error: any) {
              console.error('Extract version error:', error);
              sendJSON(res, 500, { error: error.message || 'Failed to extract version' });
            }
            return;
          }

          // Route: GET /api/git/version-file - Get file from specific version
          if (pathname.startsWith('/api/git/version-file/') && req.method === 'GET') {
            try {
              // Parse URL: /api/git/version-file/{versionId}/{path}/spec.md
              const parts = pathname.replace('/api/git/version-file/', '').split('/');
              if (parts.length < 3) {
                sendJSON(res, 400, { error: 'Invalid URL format' });
                return;
              }

              const versionId = parts[0];
              const fileName = parts[parts.length - 1];
              const targetPath = parts.slice(1, -1).join('/');

              // Validate
              if (targetPath.includes('..') || targetPath.startsWith('/')) {
                sendJSON(res, 403, { error: 'Invalid path' });
                return;
              }

              const projectRoot = process.cwd();
              const tempDir = path.join(projectRoot, '.git-versions', versionId);
              const filePath = path.join(tempDir, 'src', targetPath, fileName);

              if (!fs.existsSync(filePath)) {
                sendJSON(res, 404, { error: 'File not found in version' });
                return;
              }

              const content = fs.readFileSync(filePath, 'utf8');
              res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
              res.end(content);
            } catch (error: any) {
              console.error('Get version file error:', error);
              sendJSON(res, 500, { error: error.message });
            }
            return;
          }

          // Route: GET /api/git/status - Check git repository status
          if (pathname === '/api/git/status' && req.method === 'GET') {
            // 对于 status 接口，即使 Git 不可用也要返回状态信息
            if (!gitAvailable) {
              sendJSON(res, 200, { 
                initialized: false, 
                isGitRepo: false,
                gitAvailable: false,
                error: 'Git 未安装或不可用',
                message: '版本管理功能需要 Git 支持。请先安装 Git 后重启开发服务器。'
              });
              return;
            }

            const projectRoot = process.cwd();

            try {
              // Check if git is initialized
              const { stdout: branchOutput } = await execGit('git branch --show-current', projectRoot);
              const currentBranch = branchOutput || 'main';

              // Get repository status
              const { stdout: statusOutput } = await execGit('git status --porcelain', projectRoot);
              const hasChanges = statusOutput.length > 0;

              sendJSON(res, 200, { 
                initialized: true, 
                currentBranch,
                hasChanges,
                isGitRepo: true,
                gitAvailable: true
              });
            } catch (error: any) {
              sendJSON(res, 200, { 
                initialized: false, 
                isGitRepo: false,
                gitAvailable: true,
                error: 'Git 仓库未初始化',
                message: error.message.includes('not a git repository') 
                  ? '当前项目不是 Git 仓库。请先运行 "git init" 初始化仓库。'
                  : error.message
              });
            }
            return;
          }

          // No route matched
          sendJSON(res, 404, { error: 'API endpoint not found' });
          
        } catch (error: any) {
          console.error('Git version API error:', error);
          sendJSON(res, 500, { error: error.message || 'Internal server error' });
        }
      });
    }
  };
}
