const fs = require('fs');
const path = require('path');

/**
 * Security Report Generator (Node.js)
 * Better, faster, and more maintainable than Bash.
 */

class SecurityReport {
    constructor() {
        this.results = {
            codeql: { status: 'PASS', findings: [], alertCount: 0, rulesCount: 0 },
            snyk: { status: 'PASS', findings: [], vulnCount: 0 },
            gitleaks: { status: 'PASS', findings: [], leaksCount: 0 },
            trivy: { status: 'PASS', findings: [], misconfigCount: 0 },
            coverage: { actions: 0, js: 0, ts: 0 },
            artifactUris: []
        };
        this.auditTime = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
        this.runId = process.env.GITHUB_RUN_ID || '0';
        this.repository = process.env.GITHUB_REPOSITORY || 'unknown/repo';
        this.runUrl = `https://github.com/${this.repository}/actions/runs/${this.runId}`;

        this.locales = {
            zh: {
                filename: 'security-report-cn.md',
                switcher: '[English](security-report.md) | 中文',
                title: '🛡️ 安全审计与透明度报告',
                grade: '安全评级',
                important: '> [!IMPORTANT]\n> 本报告由 **GitHub Actions** 自动生成。为确保数据主权的绝对透明度，所有核心模块的安全扫描结果均实时公开。',
                auditTime: '📅 审计时间',
                runId: '📝 运行 ID',
                env: '🛠️ 环境',
                dashboard: '📉 实时安全仪表盘',
                tool: '工具',
                status: '状态',
                findings: '发现项',
                leaks: '泄露',
                vulns: '漏洞',
                alerts: '告警',
                coverageTitle: '🔍 扫描覆盖范围',
                module: '模块',
                auditedFiles: '已审计文件',
                coverage: '覆盖率',
                detailedFindings: '🔍 详细发现项',
                gitleaksTitle: '🔑 凭据泄露检查 (Gitleaks)',
                gitleaksDesc: '`检测代码历史记录中硬编码的 API 密钥、密码或其他敏感令牌。`',
                gitleaksSafe: '✅ **安全**：未发现硬编码的敏感凭据。',
                gitleaksScope: '`扫描范围：所有代码更改和 Git 历史记录 (Gitleaks 全量扫描)`',
                snykTitle: '📦 第三方依赖',
                snykSafe: '✅ **安全**：在依赖项中未发现已知漏洞。',
                package: '软件包',
                severity: '严重程度',
                description: '描述',
                fixPlan: '修复方案',
                codeqlTitle: '💻 代码质量与安全 (CodeQL)',
                codeqlSummary: '#### 摘要',
                rulesChecked: '已检查规则',
                totalAlerts: '告警总数',
                codeqlSafe: '✅ **安全**：CodeQL 扫描清洁，未检测到问题。',
                ruleId: '规则 ID',
                level: '级别',
                location: '位置',
                auditedList: '📂 已审计文件列表',
                guideTitle: '⚠️ 操作指南',
                guideDesc: '如果您看到 **FAIL** 状态或严重的代码问题：',
                guideStep1: '1. **开发人员**：使用上方表格中的 **位置** 列找到确切的文件和行号。',
                guideStep2: '2. **纠正**：遵循为每个规则提供的文档链接以提交修复。',
                guideStep3: '3. **可追溯性**：完整的原始 `.sarif` 数据已附加到此分支。下载并将其导入您的 IDE（例如 VS Code SARIF 查看器）进行本地分析。',
                footer: '💡 *由 Antigravity AI 安全引擎生成。透明度是我们的承诺。*',
                auditedIcon: '✅ **已审计**',
                noFiles: '未检索到文件。',
                trivyTitle: '🛡️ 容器配置安全 (Trivy)',
                trivyDesc: '`检测 Dockerfile 和容器配置中的安全风险与最佳实践。`',
                trivySafe: '✅ **安全**：未发现容器配置缺陷。'
            },
            en: {
                filename: 'security-report.md',
                switcher: 'English | [中文](security-report-cn.md)',
                title: '🛡️ Security Audit & Transparency Report',
                grade: 'Security Grade',
                important: '> [!IMPORTANT]\n> This report is automatically generated by **GitHub Actions**. To ensure absolute transparency of data sovereignty, all core module security scan results are made public in real-time.',
                auditTime: '📅 Audit Time',
                runId: '📝 Run ID',
                env: '🛠️ Environment',
                dashboard: '📉 Real-time Security Dashboard',
                tool: 'Tool',
                status: 'Status',
                findings: 'Findings',
                leaks: 'Leaks',
                vulns: 'Vulns',
                alerts: 'Alerts',
                coverageTitle: '🔍 Scan Coverage',
                module: 'Module',
                auditedFiles: 'Audited Files',
                coverage: 'Coverage',
                detailedFindings: '🔍 Detailed Findings',
                gitleaksTitle: '🔑 Credential Leak Check (Gitleaks)',
                gitleaksDesc: '`This section detects hardcoded API Keys, passwords, or other sensitive tokens in the code history.`',
                gitleaksSafe: '✅ **SAFE**: No hardcoded sensitive credentials found.',
                gitleaksScope: '`Scan Scope: All code changes and Git history (Gitleaks Full Scan)`',
                snykTitle: '📦 Third-party Dependencies',
                snykSafe: '✅ **SAFE**: No known vulnerabilities found in dependencies.',
                package: 'Package',
                severity: 'Severity',
                description: 'Description',
                fixPlan: 'Fix Plan',
                codeqlTitle: '💻 Code Quality & Safety (CodeQL)',
                codeqlSummary: '#### Summary',
                rulesChecked: 'Rules Checked',
                totalAlerts: 'Total Alerts',
                codeqlSafe: '✅ **SAFE**: CodeQL clean. No issues detected.',
                ruleId: 'Rule ID',
                level: 'Level',
                location: 'Location',
                auditedList: '📂 Audited File List',
                guideTitle: '⚠️ Action Guide',
                guideDesc: 'If you see a **FAIL** status or serious code issues:',
                guideStep1: '1. **Developers**: Use the **Location** column in the tables above to find the exact file and line number.',
                guideStep2: '2. **Remediate**: Follow the documentation links provided for each rule to submit a fix.',
                guideStep3: '3. **Traceability**: Full raw `.sarif` data is attached to this branch. Download and import it into your IDE (e.g., VS Code SARIF Viewer) for local analysis.',
                footer: '💡 *Generated by Antigravity AI Security Engine. Transparency is our commitment.*',
                auditedIcon: '✅ **Audited**',
                noFiles: 'No files found.',
                trivyTitle: '🛡️ Container Config Security (Trivy)',
                trivyDesc: '`This section detects security risks and best practices in Dockerfile and container configurations.`',
                trivySafe: '✅ **SAFE**: No container configuration defects found.'
            }
        };
    }

    // --- Data Parsers ---

    async parseCodeQL() {
        const sarifPath = 'sarif-results';
        if (!fs.existsSync(sarifPath)) return;

        const files = this.globFiles(sarifPath, '.sarif');
        let totalAlerts = 0;
        let rulesSet = new Set();
        let findings = [];
        let artifactUris = new Set();

        for (const file of files) {
            const data = JSON.parse(fs.readFileSync(file, 'utf8'));
            for (const run of data.runs || []) {
                // Collect Rules
                (run.tool.driver.rules || []).forEach(r => rulesSet.add(r.id));
                (run.tool.extensions || []).forEach(ext => {
                    (ext.rules || []).forEach(r => rulesSet.add(r.id));
                });

                // Collect Results
                for (const res of run.results || []) {
                    totalAlerts++;
                    const loc = (res.locations && res.locations[0]?.physicalLocation) || {};
                    findings.push({
                        id: res.ruleId,
                        level: res.level || 'warning',
                        path: loc.artifactLocation?.uri || 'Global',
                        line: loc.region?.startLine || '-',
                        message: res.message?.text || 'No description'
                    });
                }

                // Track Coverage (Deduplicated)
                (run.artifacts || []).forEach(art => {
                    const uri = art.location?.uri || '';
                    if (uri) artifactUris.add(uri);
                });
            }
        }

        this.results.artifactUris = Array.from(artifactUris).sort();
        this.results.coverage.actions = this.results.artifactUris.filter(u => u.startsWith('.github/workflows/')).length;
        this.results.coverage.js = this.results.artifactUris.filter(u => u.endsWith('.js')).length;
        this.results.coverage.ts = this.results.artifactUris.filter(u => u.endsWith('.ts')).length;

        this.results.codeql.alertCount = totalAlerts;
        this.results.codeql.rulesCount = rulesSet.size;
        this.results.codeql.findings = findings;
        if (totalAlerts > 0) this.results.codeql.status = 'INFO';
    }

    async parseSnyk() {
        const jsonPath = 'snyk_result.json';
        if (!fs.existsSync(jsonPath)) return;

        try {
            const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
            const projects = Array.isArray(data) ? data : [data];
            let vulnTotal = 0;
            let findings = [];

            for (const proj of projects) {
                const vulns = proj.vulnerabilities || [];
                vulnTotal += vulns.length;
                vulns.forEach(v => {
                    findings.push({
                        pkg: `${v.packageName}@${v.version}`,
                        severity: v.severity,
                        title: v.title,
                        url: v.url,
                        fixedIn: Array.isArray(v.fixedIn) ? v.fixedIn.join(', ') : (v.fixedIn || 'N/A')
                    });
                });
            }

            this.results.snyk.vulnCount = vulnTotal;
            this.results.snyk.findings = findings;
            if (vulnTotal > 0) this.results.snyk.status = 'WARN';
        } catch (e) {
            console.error('Error parsing Snyk JSON:', e.message);
        }
    }

    async parseGitleaks() {
        const files = this.globFiles('.', 'results.sarif');
        if (files.length === 0) return;

        try {
            const data = JSON.parse(fs.readFileSync(files[0], 'utf8'));
            let leaks = 0;
            let findings = [];
            for (const run of data.runs || []) {
                for (const res of run.results || []) {
                    leaks++;
                    findings.push({
                        id: res.ruleId,
                        message: res.message.text,
                        path: res.locations[0]?.physicalLocation?.artifactLocation?.uri || 'Unknown'
                    });
                }
            }

            this.results.gitleaks.leaksCount = leaks;
            this.results.gitleaks.findings = findings;
            if (leaks > 0) this.results.gitleaks.status = 'FAIL';
        } catch (e) {
            console.error('Error parsing Gitleaks SARIF:', e.message);
        }
    }

    async parseTrivy() {
        const jsonPath = 'trivy_result.json';
        if (!fs.existsSync(jsonPath)) return;

        try {
            const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
            let misconfigs = 0;
            let findings = [];

            (data.Results || []).forEach(res => {
                (res.Misconfigurations || []).forEach(m => {
                    misconfigs++;
                    findings.push({
                        id: m.ID,
                        severity: m.Severity,
                        title: m.Title,
                        message: m.Message,
                        status: m.Status,
                        target: res.Target
                    });
                });
            });

            this.results.trivy.misconfigCount = misconfigs;
            this.results.trivy.findings = findings;
            if (misconfigs > 0) this.results.trivy.status = 'WARN';
        } catch (e) {
            console.error('Error parsing Trivy JSON:', e.message);
        }
    }

    generateTable(type, t) {
        let files = [];
        if (type === 'actions') files = this.results.artifactUris.filter(u => u.startsWith('.github/workflows/'));
        else if (type === 'js') files = this.results.artifactUris.filter(u => u.endsWith('.js'));
        else if (type === 'ts') files = this.results.artifactUris.filter(u => u.endsWith('.ts'));

        if (files.length === 0) return `> ${t.noFiles}\n`;

        let table = `| ${t.module} | ${t.location} | ${t.status} |\n| :--- | :--- | :--- |\n`;
        files.forEach(f => {
            const filename = path.basename(f);
            table += `| \`${filename}\` | \`${f}\` | ${t.auditedIcon} |\n`;
        });
        return table;
    }

    // --- Renderers ---

    generateMarkdown(localeKey) {
        const { codeql, snyk, gitleaks, coverage } = this.results;
        const t = this.locales[localeKey];

        // Calculate Grade
        let grade = 'A+';
        let gradeColor = 'success';
        if (gitleaks.status === 'FAIL') { grade = 'D'; gradeColor = 'red'; }
        else if (snyk.vulnCount > 10 || this.results.trivy.misconfigCount > 5) { grade = 'C'; gradeColor = 'orange'; }
        else if (snyk.vulnCount > 0 || codeql.alertCount > 0 || this.results.trivy.misconfigCount > 0) { grade = 'B'; gradeColor = 'blue'; }

        const badge = (label, value, color) => `![${label}](https://img.shields.io/badge/${label.replace(/ /g, '_')}-${value}-${color}?style=for-the-badge)`;

        let md = `# ${t.title}\n\n`;
        md += `${t.switcher}\n\n`;
        md += `${badge(t.grade.replace(/ /g, '_'), grade, gradeColor)}\n\n`;
        md += `${t.important}\n\n`;

        md += `| ${t.auditTime} | ${t.runId} | ${t.env} |\n`;
        md += `| :--- | :--- | :--- |\n`;
        md += `| \`${this.auditTime}\` | [#${this.runId}](${this.runUrl}) | \`GitHub CI/CD\` |\n\n`;

        md += `---\n\n## ${t.dashboard}\n\n`;
        md += `| ${t.tool} | ${t.status} | ${t.findings} |\n`;
        md += `| :--- | :--- | :--- |\n`;
        md += `| **Credential Leak (Gitleaks)** | ${this.getBadge(gitleaks.status)} | \`${gitleaks.leaksCount}\` ${t.leaks} |\n`;
        md += `| **Dependency Scan (Snyk)** | ${this.getBadge(snyk.status)} | \`${snyk.vulnCount}\` ${t.vulns} |\n`;
        md += `| **Static Analysis (CodeQL)** | ${this.getBadge(codeql.status)} | \`${codeql.alertCount}\` ${t.alerts} |\n`;
        md += `| **Container Scan (Trivy)** | ${this.getBadge(this.results.trivy.status)} | \`${this.results.trivy.misconfigCount}\` ${t.findings} |\n\n`;

        md += `---\n\n## ${t.coverageTitle}\n\n`;
        md += `| ${t.module} | ${t.auditedFiles} | ${t.coverage} |\n`;
        md += `| :--- | :---: | :---: |\n`;
        md += `| **GitHub Actions** | \`${coverage.actions}\` | ✨ **100%** |\n`;
        md += `| **JavaScript (Frontend)** | \`${coverage.js}\` | ✨ **100%** |\n`;
        md += `| **TypeScript (Backend)** | \`${coverage.ts}\` | ✨ **100%** |\n\n`;

        md += `---\n\n## ${t.detailedFindings}\n\n`;

        // Gitleaks Section
        md += `### ${t.gitleaksTitle}\n`;
        md += `${t.gitleaksDesc} ${t.gitleaksScope}\n\n`;
        if (gitleaks.findings.length > 0) {
            md += `| ${t.ruleId} | ${t.location} | ${t.description} |\n`;
            md += `| :--- | :--- | :--- |\n`;
            gitleaks.findings.forEach(f => {
                md += `| \`${f.id}\` | \`${f.path}\` | ${f.message} |\n`;
            });
        } else {
            md += `${t.gitleaksSafe}\n`;
        }

        // Trivy Section
        md += `\n### ${t.trivyTitle}\n`;
        md += `${t.trivyDesc}\n\n`;
        if (this.results.trivy.findings.length > 0) {
            md += `| ${t.ruleId} | ${t.severity} | ${t.location} | ${t.description} |\n`;
            md += `| :--- | :---: | :--- | :--- |\n`;
            this.results.trivy.findings.forEach(f => {
                const icon = f.severity === 'CRITICAL' ? '🔴' : (f.severity === 'HIGH' ? '🟠' : '🟡');
                md += `| \`${f.id}\` | ${icon} ${f.severity} | \`${f.target}\` | ${f.title}: ${f.message} |\n`;
            });
        } else {
            md += `${t.trivySafe}\n`;
        }

        // Snyk Section
        md += `\n### ${t.snykTitle}\n`;
        if (snyk.findings.length > 0) {
            md += `| ${t.package} | ${t.severity} | ${t.description} | ${t.fixPlan} |\n`;
            md += `| :--- | :---: | :--- | :--- |\n`;
            snyk.findings.forEach(f => {
                const icon = f.severity === 'critical' ? '🔴' : (f.severity === 'high' ? '🟠' : '🟡');
                md += `| \`${f.pkg}\` | ${icon} ${f.severity} | [${f.title}](${f.url}) | ${f.fixedIn === 'N/A' ? 'No fix' : `Upgrade to \`${f.fixedIn}\``} |\n`;
            });
        } else {
            md += `${t.snykSafe}\n`;
        }

        // CodeQL Section
        md += `\n### ${t.codeqlTitle}\n`;
        if (codeql.findings.length > 0) {
            md += `${t.codeqlSummary}\n- **${t.rulesChecked}**: \`${codeql.rulesCount}\`\n- **${t.totalAlerts}**: \`${codeql.alertCount}\`\n\n`;
            md += `| ${t.ruleId} | ${t.level} | ${t.location} | ${t.description} |\n`;
            md += `| :--- | :---: | :--- | :--- |\n`;
            codeql.findings.forEach(f => {
                const icon = f.level === 'error' ? '🔴' : (f.level === 'warning' ? '🟠' : '🔵');
                const prefix = f.id.split('/')[0];
                const langMap = {
                    'js': 'javascript',
                    'actions': 'github-actions',
                    'cpp': 'cpp',
                    'cs': 'csharp',
                    'go': 'go',
                    'java': 'java',
                    'py': 'python',
                    'rb': 'ruby',
                    'swift': 'swift'
                };
                const langPath = langMap[prefix] || 'javascript';
                md += `| [${f.id}](https://codeql.github.com/codeql-query-help/${langPath}/${f.id.replace(/\//g, '-')}/) | ${icon} ${f.level} | \`${f.path}:${f.line}\` | ${f.message} |\n`;
            });
        } else {
            md += `${t.codeqlSafe}\n`;
        }

        // Audited Files List
        md += `\n### ${t.auditedList}\n`;
        md += `<details>\n<summary><b>GitHub Actions (${this.results.coverage.actions})</b></summary>\n\n`;
        md += this.generateTable('actions', t);
        md += `\n</details>\n\n`;

        md += `<details>\n<summary><b>JavaScript (${this.results.coverage.js})</b></summary>\n\n`;
        md += this.generateTable('js', t);
        md += `\n</details>\n\n`;

        md += `<details>\n<summary><b>TypeScript (${this.results.coverage.ts})</b></summary>\n\n`;
        md += this.generateTable('ts', t);
        md += `\n</details>\n\n`;

        // Action Guide
        md += `--- \n\n## ${t.guideTitle}\n\n`;
        md += `${t.guideDesc}\n`;
        md += `${t.guideStep1}\n`;
        md += `${t.guideStep2}\n`;
        md += `${t.guideStep3}\n\n`;

        md += `--- \n\n${t.footer}`;

        return md;
    }

    // --- Helpers ---

    getBadge(status) {
        if (status === 'PASS') return '![Pass](https://img.shields.io/badge/Status-PASS-success?style=for-the-badge)';
        if (status === 'WARN' || status === 'INFO') return '![Warning](https://img.shields.io/badge/Status-NOTICE-orange?style=for-the-badge)';
        return '![Fail](https://img.shields.io/badge/Status-FAIL-red?style=for-the-badge)';
    }

    globFiles(dir, ext) {
        let results = [];
        const list = fs.readdirSync(dir);
        for (const file of list) {
            const fullPath = path.join(dir, file);
            const stat = fs.statSync(fullPath);
            if (stat && stat.isDirectory()) {
                results = results.concat(this.globFiles(fullPath, ext));
            } else if (file.endsWith(ext)) {
                results.push(fullPath);
            }
        }
        return results;
    }

    async run() {
        console.log('--- Security Report Generation Started ---');
        await this.parseCodeQL();
        await this.parseSnyk();
        await this.parseGitleaks();
        await this.parseTrivy();

        for (const localeKey of Object.keys(this.locales)) {
            const locale = this.locales[localeKey];
            const markdown = this.generateMarkdown(localeKey);
            fs.writeFileSync(locale.filename, markdown);
            console.log(`Report generated successfully at ${locale.filename}`);
        }
    }
}

new SecurityReport().run().catch(err => {
    console.error('Report generation failed:', err);
    process.exit(1);
});
