/**
 * VL AutoTest - Auto Fixer v2.0
 * Uses Claude API + VL 3.7 syntax knowledge to:
 * 1. Analyze test failures (test-level fixes)
 * 2. Generate VL source code fixes using VL syntax 2.8
 * 3. Apply fixes → repackage ZIP → recompile
 */

import { readFile } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

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

// ── VL 3.7 Syntax Reference (condensed for LLM context) ──────────────
export const VL_SYNTAX_REF = `
## VL 3.7 Syntax Quick Reference

### File Types
- .vx (App): Route management, Section orchestration. Sections: # SysConfig, # Frontend Global Vars, # Frontend Tree, # Frontend Event Handlers, # Frontend Internal Methods, # Frontend Pipeline Funcs
- .sc (Section): Business module, data interaction. Sections: # Frontend Public Props, # Frontend Public Events, # Frontend Public Methods, # Frontend Global Vars, # Frontend Derived Vars, # Frontend Tree, # Frontend Event Handlers, # Frontend Internal Methods, # Frontend Pipeline Funcs
- .cp (Component): Pure UI, no service calls. Same sections as Section but NO ServiceDomain.
- .vs (ServiceDomain): Backend services. Sections: # Backend Environment Vars, # Backend Tree, # Services, # Backend Event Handlers, # Transactions, # Backend Internal Methods, # Backend Pipeline Funcs
- .vdb (Database): Table definitions with Fields, Indexes, Relations.
- .vth (Theme): Design tokens, component variants, overrides.

### Cross-Reference Rules
- App → Section ✅, Component ✅, ServiceDomain ❌
- Section → ServiceDomain ✅, Component ✅, Section ❌
- Component → nothing (no ServiceDomain, no Section, no Component)

### Key Syntax Rules
1. Version: first line must be \`// VL_VERSION:3.7\`
2. Indentation: use \`-\` prefix (no spaces before), deeper = more dashes
3. Single-line rule: variable defs, method signatures, RETURN statements must be one line
4. No leading spaces before any content
5. No backslash escapes (\\) anywhere
6. No semicolons at end of lines (only in FOR loop controls and after METHOD/SERVICE signature before RETURN)
7. Variables: $globalVar(TYPE), _localVar(TYPE), loop vars _item0/_index0
8. Methods: METHOD camelCase(), METHOD_PUB PascalCase(), SERVICE PascalCase()
9. Events: EVENT @eventName(params)
10. Element syntax: <Category-Name "instanceId"> prop:value prop2:"value2"

### Variable Declaration
$name(STRING) = ""
$count(INT) = 0
$items([{id:INT,name:STRING}]) = []
$config({key:STRING,value:STRING}) = {key:"",value:""}
_localVar(STRING) = "temp"

### Frontend Tree (Section/Component)
<ServiceDomain-Name "instanceId">
-<Service-MethodName> params:(param1(TYPE),param2(TYPE)) returns:(field1(TYPE),field2(TYPE))

<Row-Layout> gap:"16px"
-<Col-Left> flex:"0 0 200px"
--<Text-Title "title"> value:$titleVar
-<Col-Right> flex:"1"
--<Button-Submit "submitBtn"> value:"Submit"
--<Input-Search "searchInput"> value:$searchKeyword placeholder:"Search..."

### Control Flow in Tree
<If-Condition> conditions:$boolVar
-<Text-Message> value:"Visible when true"
<For-Items> sourceArray:$items loopVar:[_item0,_index0]
-<Component-Card "card"> data:_item0

### Method Definition
METHOD loadData(); RETURN {success:BOOL}
-$isLoading = true
-<ServiceDomain-Name "svc">.MethodName($param1, $param2) -> _result
-$isLoading = false
-IF !_result.success
--RETURN {success:false}
-$dataList = _result.data
-RETURN {success:true}

### Event Handler
<Button-Submit "submitBtn">.@click()
-validateForm() -> _valid
-IF _valid.success
--submitData()

<Component-Card "card">.@cardClick(itemId)
-$selectedId = itemId

### Service Definition (.vs)
SERVICE GetItems(categoryId(INT),keyword(STRING));RETURN {success:BOOL,data:[{}],total:INT}
-<VirtualTable-Items "itemTable">.select({category:categoryId},[["_create","desc"]],[0,50]) -> _r
-RETURN {success:true,data:_r.dataArray,total:_r.totalCount}

### Common Errors to Fix
- Missing # section headers (must be in order)
- Wrong indentation (missing or extra -)
- Variable type mismatch
- Calling ServiceDomain from Component (not allowed)
- Nesting Section inside Section (not allowed)
- Using backslash escapes
- Multi-line variable declarations (must be single line)
- Missing closing tag (e.g., </Section-Name>)
- Wrong method naming (METHOD_PUB should be PascalCase, METHOD should be camelCase)
`.trim();

// ── Test Fix System Prompt ────────────────────────────────────
export const TEST_FIX_SYSTEM = `You are a VL application auto-fix expert. Your task is to analyze test failure causes and suggest fix strategies.

Analysis steps:
1. Understand the expected behavior of the test case
2. Analyze the failure cause (error messages, DOM snapshot comparison)
3. Determine whether the issue is with the test case itself or the VL source code
4. Generate fix suggestions

Output format (strict JSON, no markdown code fence):
{
  "analysis": {
    "failureType": "selector_not_found | assertion_failed | navigation_error | timeout | logic_error | data_error | vl_code_error",
    "rootCause": "Root cause analysis",
    "isTestIssue": true/false,
    "isCodeIssue": true/false,
    "affectedFiles": ["VL file paths that may need modification"]
  },
  "testFixes": [
    {
      "caseId": "TC_xxx",
      "description": "Test case fix description",
      "fixedSteps": [fixed steps array]
    }
  ],
  "codeFixes": [
    {
      "file": "VL file path",
      "description": "VL code fix description",
      "priority": "critical | important | minor",
      "original": "Original code snippet",
      "fixed": "Fixed code snippet",
      "confidence": 0.0-1.0
    }
  ],
  "retryAdvice": "yes | no | after_fix"
}

Output JSON only, no markdown code fence.`;

// ── VL Source Fix System Prompt ────────────────────────────────
export const VL_FIX_SYSTEM = `You are a VL (Visual Logic) 3.7 syntax expert. Your task is to fix errors in VL source code.

${VL_SYNTAX_REF}

You will receive:
1. Detailed test failure information (error type, failed steps, DOM snapshots)
2. Related VL source file contents
3. VL metadata (app structure, dependencies)

Your task:
1. Analyze whether the failure is caused by VL source code issues
2. If so, generate precise VL code fix patches
3. Ensure the fixed code conforms to VL 3.7 syntax specification

Output format (strict JSON, no markdown code fence):
{
  "diagnosis": "Problem diagnosis summary",
  "fixes": [
    {
      "file": "VL file path (e.g. Sections/KanbanBoard.sc)",
      "description": "Fix description",
      "searchReplace": [
        {
          "search": "Original code to find and replace (exact match)",
          "replace": "Replacement code"
        }
      ],
      "confidence": 0.8
    }
  ],
  "noFixNeeded": false,
  "reason": "If no fix is needed, explain why"
}

Important rules:
- search in searchReplace must exactly match content in the source file
- replace must conform to VL 3.7 syntax specification
- Do not modify unrelated code
- If the issue is not in VL source code (e.g. the test itself has problems), set noFixNeeded=true
Output JSON only, no markdown code fence.`;

function log(tag, msg, data) {
  const ts = new Date().toISOString().slice(11, 23);
  if (data !== undefined) {
    const d = typeof data === 'string' ? data.slice(0, 300) : (typeof data === 'object' ? JSON.stringify(data).slice(0, 300) : String(data));
    console.log(`[${ts}] [AutoFixer] [${tag}] ${msg} ${d}`);
  } else {
    console.log(`[${ts}] [AutoFixer] [${tag}] ${msg}`);
  }
}

export class AutoFixer {
  constructor(anthropicClient) {
    this.client = anthropicClient;
    this.model = 'claude-sonnet-4-6';
  }

  /**
   * Analyze a failed test case and suggest test-level fixes
   */
  async analyzeFail(testCase, runResult, evaluation, projectFiles, onProgress) {
    onProgress?.({
      phase: 'auto_fix',
      caseId: testCase.id,
      status: 'running',
      message: `Analyzing failed case ${testCase.id}...`,
    });

    const stepsInfo = runResult.steps?.map((s, i) =>
      `Step ${i + 1}: ${s.action} "${s.selector || ''}" - ${s.status}${s.error ? ` (${s.error})` : ''}`
    ).join('\n') || '[no step info]';

    const snapshotInfo = runResult.snapshots?.length > 0
      ? runResult.snapshots.map(s => `[${s.type}]:\n${s.snapshot?.slice(0, 3000)}`).join('\n\n')
      : '[no snapshots]';

    const userMessage = `Failed test case:
ID: ${testCase.id}
Name: ${testCase.name}
Page: ${testCase.page}
Expected result: ${testCase.expectedResult}

Step execution results:
${stepsInfo}

Error message: ${runResult.error || 'none'}

LLM evaluation result:
- Pass: ${evaluation?.pass ? 'yes' : 'no'}
- Reason: ${evaluation?.reason || 'none'}

Browser snapshots:
${snapshotInfo}`;

    log('AnalyzeFail', `Analyzing ${testCase.id}: ${testCase.name}`);

    try {
      const response = await this.client.messages.create({
        model: this.model,
        max_tokens: 4096,
        system: TEST_FIX_SYSTEM,
        messages: [{ role: 'user', content: userMessage }],
      });

      const text = response.content[0].text;
      const fixResult = this._parseJSON(text);

      log('AnalyzeFail', `${testCase.id}: type=${fixResult.analysis?.failureType}, testFixes=${fixResult.testFixes?.length || 0}, codeFixes=${fixResult.codeFixes?.length || 0}`);

      onProgress?.({
        phase: 'auto_fix',
        caseId: testCase.id,
        status: 'done',
        message: `Case ${testCase.id} analysis complete`,
        data: fixResult,
      });

      return fixResult;
    } catch (err) {
      log('AnalyzeFail', `${testCase.id} error: ${err.message}`);
      const fallback = {
        analysis: {
          failureType: 'unknown',
          rootCause: `Auto-analysis failed: ${err.message}`,
          isTestIssue: false,
          isCodeIssue: false,
          affectedFiles: [],
        },
        testFixes: [],
        codeFixes: [],
        retryAdvice: 'no',
      };

      onProgress?.({
        phase: 'auto_fix',
        caseId: testCase.id,
        status: 'error',
        message: `Case ${testCase.id} analysis failed: ${err.message}`,
        data: fallback,
      });

      return fallback;
    }
  }

  /**
   * Analyze all failed test cases
   */
  async analyzeAllFailures(testCases, runResults, evaluations, projectFiles, onProgress) {
    const failedCases = evaluations.filter(e => !e.evaluation?.pass);
    const fixResults = [];

    log('AnalyzeAll', `Analyzing ${failedCases.length} failed cases`);

    for (const failed of failedCases) {
      const tc = testCases.find(t => t.id === failed.caseId);
      const result = runResults.find(r => r.caseId === failed.caseId);
      if (!tc || !result) continue;

      const fix = await this.analyzeFail(tc, result, failed.evaluation, projectFiles, onProgress);
      fixResults.push({
        caseId: failed.caseId,
        name: failed.name || tc.name,
        fix,
      });
    }

    return fixResults;
  }

  /**
   * Generate VL source code fixes using VL 3.7 syntax knowledge.
   * This is the core of the "VL auto-fix" flow:
   * 1. Analyze failures + VL source → generate code patches
   * 2. Return patches that can be applied to vlFiles
   *
   * @param {Array} failedResults - Array of { caseId, name, fix } from analyzeAllFailures
   * @param {Object} vlFiles - VL source files { 'Sections/Name.sc': 'content', ... }
   * @param {Object} metadata - VL metadata from extractor
   * @param {Object} runResults - Test run results
   * @param {Object} testCases - Test case definitions
   * @param {Function} onProgress - Progress callback
   * @returns {Object} { patches: [...], modifiedFiles: { path: newContent, ... } }
   */
  async generateVLFixes(failedResults, vlFiles, metadata, runResults, testCases, onProgress) {
    onProgress?.({
      phase: 'vl_fix',
      status: 'running',
      message: 'Analyzing and generating source code fixes using VL 3.7 syntax...',
    });

    // Collect failure summaries
    const failureSummary = failedResults.map(fr => {
      const tc = testCases.find(t => t.id === fr.caseId);
      const result = runResults.find(r => r.caseId === fr.caseId);
      return {
        caseId: fr.caseId,
        name: fr.name || tc?.name,
        failureType: fr.fix?.analysis?.failureType,
        rootCause: fr.fix?.analysis?.rootCause,
        isCodeIssue: fr.fix?.analysis?.isCodeIssue,
        affectedFiles: fr.fix?.analysis?.affectedFiles || [],
        codeFixes: fr.fix?.codeFixes || [],
        steps: result?.steps?.map((s, i) => `${i + 1}. ${s.action} "${s.selector || ''}" → ${s.status}${s.error ? ': ' + s.error.slice(0, 100) : ''}`).join('\n') || '',
      };
    }).filter(f => f.isCodeIssue !== false); // Only code issues

    if (failureSummary.length === 0) {
      log('VLFix', 'No code issues identified, skipping VL fix');
      onProgress?.({
        phase: 'vl_fix',
        status: 'done',
        message: 'No VL source code issues identified, skipping fix',
      });
      return { patches: [], modifiedFiles: {} };
    }

    // Collect relevant VL files (affected files + all small files)
    const relevantFiles = {};
    const affectedSet = new Set(failureSummary.flatMap(f => f.affectedFiles));

    for (const [path, content] of Object.entries(vlFiles)) {
      if (affectedSet.has(path) || content.length < 5000) {
        relevantFiles[path] = content;
      }
    }
    // If no specific files identified, include all VL files (truncated)
    if (Object.keys(relevantFiles).length === 0) {
      for (const [path, content] of Object.entries(vlFiles)) {
        relevantFiles[path] = content.slice(0, 8000);
      }
    }

    log('VLFix', `Sending ${failureSummary.length} failures + ${Object.keys(relevantFiles).length} VL files to LLM`);

    const userMessage = `## Test Failure Summary

${failureSummary.map(f => `### ${f.caseId}: ${f.name}
- Failure type: ${f.failureType}
- Root cause: ${f.rootCause}
- Related files: ${f.affectedFiles.join(', ') || 'unknown'}
- Suggested fixes: ${f.codeFixes.map(cf => cf.description).join('; ') || 'none'}
- Execution steps:
${f.steps}`).join('\n\n')}

## VL Source Files

${Object.entries(relevantFiles).map(([path, content]) => `### ${path}\n\`\`\`vl\n${content}\n\`\`\``).join('\n\n')}

## Metadata Summary
- Apps: ${metadata?.apps?.map(a => a.name).join(', ') || 'N/A'}
- Sections: ${metadata?.sections?.map(s => s.name).join(', ') || 'N/A'}
- Services: ${metadata?.services?.map(s => s.name).join(', ') || 'N/A'}

Please analyze these test failures and generate VL 3.5 source code fix patches.`;

    try {
      const response = await this.client.messages.create({
        model: this.model,
        max_tokens: 8192,
        system: VL_FIX_SYSTEM,
        messages: [{ role: 'user', content: userMessage }],
      });

      const text = response.content[0].text;
      log('VLFix', `LLM returned ${text.length} chars, stop=${response.stop_reason}`);

      const result = this._parseJSON(text);

      if (result.noFixNeeded) {
        log('VLFix', `No fix needed: ${result.reason}`);
        onProgress?.({
          phase: 'vl_fix',
          status: 'done',
          message: `VL source code needs no fix: ${result.reason}`,
        });
        return { patches: [], modifiedFiles: {}, reason: result.reason };
      }

      // Apply patches to VL files
      const modifiedFiles = { ...vlFiles };
      const appliedPatches = [];

      for (const fix of (result.fixes || [])) {
        const filePath = fix.file;
        if (!modifiedFiles[filePath]) {
          log('VLFix', `File not found: ${filePath}, skipping`);
          continue;
        }

        let fileContent = modifiedFiles[filePath];
        let applied = false;

        for (const sr of (fix.searchReplace || [])) {
          if (fileContent.includes(sr.search)) {
            fileContent = fileContent.replace(sr.search, sr.replace);
            applied = true;
            log('VLFix', `Applied patch to ${filePath}: "${sr.search.slice(0, 50)}..." → "${sr.replace.slice(0, 50)}..."`);
          } else {
            log('VLFix', `Search string not found in ${filePath}: "${sr.search.slice(0, 80)}..."`);
          }
        }

        if (applied) {
          modifiedFiles[filePath] = fileContent;
          appliedPatches.push({
            file: filePath,
            description: fix.description,
            confidence: fix.confidence,
          });
        }
      }

      log('VLFix', `Applied ${appliedPatches.length} patches to ${new Set(appliedPatches.map(p => p.file)).size} files`);

      onProgress?.({
        phase: 'vl_fix',
        status: 'done',
        message: `VL source code fix complete: ${appliedPatches.length} patches applied to ${new Set(appliedPatches.map(p => p.file)).size} files`,
        data: { patches: appliedPatches, diagnosis: result.diagnosis },
      });

      return {
        patches: appliedPatches,
        modifiedFiles,
        diagnosis: result.diagnosis,
        rawFixes: result.fixes,
      };
    } catch (err) {
      log('VLFix', `VL fix generation failed: ${err.message}`);
      onProgress?.({
        phase: 'vl_fix',
        status: 'error',
        message: `VL source code fix failed: ${err.message}`,
      });
      return { patches: [], modifiedFiles: {}, error: err.message };
    }
  }

  /**
   * Apply a test fix to a test case (update test steps)
   */
  applyTestFix(testCase, testFix) {
    if (!testFix?.fixedSteps) return testCase;
    return {
      ...testCase,
      steps: typeof testFix.fixedSteps === 'string'
        ? JSON.parse(testFix.fixedSteps)
        : testFix.fixedSteps,
      _fixed: true,
      _fixDescription: testFix.description,
    };
  }

  /**
   * Find project files relevant to a test case
   */
  _findRelevantFiles(testCase, projectFiles) {
    const relevant = [];
    const pagePath = testCase.page || '';
    const tags = testCase.tags || [];

    for (const [name, content] of Object.entries(projectFiles)) {
      if (pagePath && content.includes(pagePath.replace(/^\//, ''))) {
        relevant.push([name, content]);
        continue;
      }
      if (tags.some(tag => name.toLowerCase().includes(tag.toLowerCase()))) {
        relevant.push([name, content]);
        continue;
      }
      if (name.match(/\.(vx|vs)$/)) {
        relevant.push([name, content]);
      }
    }
    return relevant.slice(0, 5);
  }

  _parseJSON(text) {
    // Strip markdown code fences
    let cleaned = text.replace(/^```(?:json)?\s*\n?/m, '').replace(/\n?```\s*$/m, '');

    try {
      return JSON.parse(cleaned);
    } catch (e) {
      // Try extracting JSON from text
      const firstBrace = cleaned.indexOf('{');
      if (firstBrace >= 0) {
        const jsonStr = cleaned.slice(firstBrace);
        // Find matching closing brace
        let depth = 0;
        let inStr = false;
        let esc = false;
        for (let i = 0; i < jsonStr.length; i++) {
          const ch = jsonStr[i];
          if (esc) { esc = false; continue; }
          if (ch === '\\' && inStr) { esc = true; continue; }
          if (ch === '"') { inStr = !inStr; continue; }
          if (inStr) continue;
          if (ch === '{') depth++;
          if (ch === '}') {
            depth--;
            if (depth === 0) {
              return JSON.parse(jsonStr.slice(0, i + 1));
            }
          }
        }
        // Try parse as-is (may work if it's valid)
        try { return JSON.parse(jsonStr); } catch {}
      }
      throw new Error(`Failed to parse fix JSON: ${cleaned.slice(0, 200)}`);
    }
  }
}
