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

const bundle = process.argv[2];
if (!bundle) {
  console.error("Please specify a bundle name (e.g., node scripts/lint.js dev)");
  process.exit(1);
}

const bundleRoot = process.env.IRIS_BUNDLE_ROOT;
if (!bundleRoot) {
  console.error("Error: Environment variable IRIS_BUNDLE_ROOT is not defined.");
  process.exit(1);
}
const bundleDir = path.join(bundleRoot, bundle);
const inboxDir = path.join(bundleDir, 'raw_inbox');
const reportFile = path.join(bundleDir, '.bundle/lint.md');

if (!fs.existsSync(inboxDir)) {
  console.error(`Inbox directory not found: ${inboxDir}`);
  process.exit(1);
}

// Ensure .bundle directory exists
const bundleMetaDir = path.join(bundleDir, '.bundle');
if (!fs.existsSync(bundleMetaDir)) {
  fs.mkdirSync(bundleMetaDir, { recursive: true });
}

function getAllFiles(dir, fileList = []) {
  if (!fs.existsSync(dir)) return fileList;
  const files = fs.readdirSync(dir);
  files.forEach(file => {
    const filePath = path.join(dir, file);
    if (fs.statSync(filePath).isDirectory()) {
      getAllFiles(filePath, fileList);
    } else {
      fileList.push(filePath);
    }
  });
  return fileList;
}

const files = getAllFiles(inboxDir);
const violations = [];

files.forEach(file => {
  const relativePath = path.relative(inboxDir, file);
  const fileExt = path.extname(file);
  const baseName = path.basename(file, fileExt);
  const dirName = path.dirname(relativePath);

  let content = '';
  let hasEncodingError = false;

  // L-1: Encoding Check
  try {
    const buffer = fs.readFileSync(file);
    content = buffer.toString('utf8');
    // Simple UTF-8 validation check (check for replacement character)
    if (content.includes('\uFFFD')) {
      hasEncodingError = true;
    }
  } catch (err) {
    hasEncodingError = true;
  }

  if (hasEncodingError) {
    violations.push({
      file: relativePath,
      rule: 'L-1',
      description: 'ファイルが UTF-8 で正しくデコードできません（非UTF-8、またはバイナリの可能性があります）。',
      location: 'ファイル全体',
      suggestion: 'ファイルを UTF-8 (BOMなし) エンコーディングで再保存してください。'
    });
  }

  // L-2: Naming Convention Check
  // Check directory parts
  const dirParts = dirName === '.' ? [] : dirName.split(path.sep);
  let namingViolation = false;
  let invalidParts = [];

  dirParts.forEach(part => {
    if (!/^[a-z0-9-]+$/.test(part)) {
      namingViolation = true;
      invalidParts.push(`ディレクトリ名 "${part}"`);
    }
  });

  if (!/^[a-z0-9-]+$/.test(baseName)) {
    namingViolation = true;
    invalidParts.push(`ファイル名 "${baseName}"`);
  }

  if (baseName.startsWith('_')) {
    namingViolation = true;
    invalidParts.push(`先頭のアンダースコア "_"`);
  }

  if (namingViolation) {
    violations.push({
      file: relativePath,
      rule: 'L-2',
      description: `命名規則違反: kebab-case（小文字英数字とハイフンのみ）に準拠していません。`,
      location: invalidParts.join(', '),
      suggestion: 'ファイル名およびディレクトリ名をすべて小文字の英数字とハイフンのみ（kebab-case）に変更してください。先頭のアンダースコアは使用できません。'
    });
  }

  // L-7: Private / Local File Check
  const fullFileName = path.basename(file);
  if (fullFileName.includes('.local') || fullFileName.includes('.private')) {
    violations.push({
      file: relativePath,
      rule: 'L-7',
      description: 'プライベート・ローカルファイルの混入: ファイル名に ".local" または ".private" が含まれています。',
      location: `ファイル名 "${fullFileName}"`,
      suggestion: 'ファイル名から ".local" または ".private" を削除するか、インボックスからファイルを除外してください。'
    });
  }

  if (!hasEncodingError) {
    // L-3: Sensitive Information Check
    const sensitivePatterns = [
      { name: 'AWS_ACCESS_KEY', severity: 'critical', pattern: /AKIA[0-9A-Z]{16}/ },
      { name: 'AWS_SECRET_KEY', severity: 'critical', pattern: /aws_secret_access_key\s*[:=]\s*['"]?[A-Za-z0-9/+=]{40}/i },
      { name: 'GCP_SERVICE_ACCOUNT', severity: 'critical', pattern: /"type"\s*:\s*"service_account"/i },
      { name: 'GCP_API_KEY', severity: 'high', pattern: /AIza[0-9A-Za-z_-]{35}/ },
      { name: 'AZURE_CLIENT_SECRET', severity: 'critical', pattern: /azure[_-]?client[_-]?secret\s*[:=]\s*['"]?[A-Za-z0-9_~.-]{34,}/i },
      { name: 'GITHUB_PAT', severity: 'critical', pattern: /ghp_[0-9A-Za-z]{36}/ },
      { name: 'GITHUB_OAUTH', severity: 'critical', pattern: /gho_[0-9A-Za-z]{36}/ },
      { name: 'GITHUB_APP_TOKEN', severity: 'critical', pattern: /ghs_[0-9A-Za-z._-]{36,}/ },
      { name: 'GITHUB_REFRESH_TOKEN', severity: 'critical', pattern: /ghr_[0-9A-Za-z]{36}/ },
      { name: 'GITHUB_FINE_GRAINED_PAT', severity: 'critical', pattern: /github_pat_[0-9A-Za-z_]{82}/ },
      { name: 'PRIVATE_KEY', severity: 'critical', pattern: /-----BEGIN (RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/ },
      { name: 'PGP_PRIVATE_BLOCK', severity: 'critical', pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/ },
      { name: 'GENERIC_SECRET', severity: 'high', pattern: /(secret|token|password|passwd|pwd|api[_-]?key|apikey|access[_-]?key|auth[_-]?token|client[_-]?secret)\s*[:=]\s*['"]?[A-Za-z0-9_/+=~.-]{8,}/i },
      { name: 'CONNECTION_STRING', severity: 'high', pattern: /(mongodb(\+srv)?|postgres(ql)?|mysql|redis|amqp|mssql):\/\/[^\s'"]{10,}/i },
      { name: 'BEARER_TOKEN', severity: 'medium', pattern: /[Bb]earer\s+[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/ },
      { name: 'SLACK_TOKEN', severity: 'high', pattern: /xox[baprs]-[0-9]{10,}-[0-9A-Za-z-]+/ },
      { name: 'SLACK_WEBHOOK', severity: 'high', pattern: /https:\/\/hooks\.slack\.com\/services\/T[0-9A-Z]{8,}\/B[0-9A-Z]{8,}\/[0-9A-Za-z]{24}/ },
      { name: 'DISCORD_TOKEN', severity: 'high', pattern: /[MN][A-Za-z0-9]{23,}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27,}/ },
      { name: 'TWILIO_API_KEY', severity: 'high', pattern: /SK[0-9a-fA-F]{32}/ },
      { name: 'SENDGRID_API_KEY', severity: 'high', pattern: /SG\.[0-9A-Za-z_-]{22}\.[0-9A-Za-z_-]{43}/ },
      { name: 'STRIPE_SECRET_KEY', severity: 'critical', pattern: /sk_live_[0-9A-Za-z]{24,}/ },
      { name: 'STRIPE_RESTRICTED_KEY', severity: 'high', pattern: /rk_live_[0-9A-Za-z]{24,}/ },
      { name: 'NPM_TOKEN', severity: 'high', pattern: /npm_[0-9A-Za-z]{36}/ },
      { name: 'JWT_TOKEN', severity: 'medium', pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
      { name: 'INTERNAL_IP_PORT', severity: 'medium', pattern: /(?:^|[^.0-9])(10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|172\.(1[6-9]|2[0-9]|3[01])\.[0-9]{1,3}\.[0-9]{1,3}|192\.168\.[0-9]{1,3}\.[0-9]{1,3}):[0-9]{2,5}(?:[^0-9]|$)/ }
    ];

    sensitivePatterns.forEach(p => {
      const match = content.match(p.pattern);
      if (match) {
        violations.push({
          file: relativePath,
          rule: 'L-3',
          description: `機密情報の混入: ${p.name} (${p.severity})`,
          location: `一致箇所: "${match[0].substring(0, 40).replace(/\r?\n/g, ' ')}..."`,
          suggestion: 'APIキーや秘密鍵などの機密情報をソースファイルから削除またはマスキングしてください。'
        });
      }
    });

    // L-4: Content Integrity Check
    const textOnly = content.replace(/\s/g, '');
    if (textOnly.length < 50) {
      violations.push({
        file: relativePath,
        rule: 'L-4',
        description: 'コンテンツの健全性: 実質的なテキストコンテンツが不足しています。',
        location: `文字数: ${textOnly.length}文字 (基準: 50文字以上)`,
        suggestion: 'ファイルに十分な内容（ナレッジ）を記述してください。'
      });
    }



    // L-5: Security Check
    const injectionPatterns = [
      /ignore\s+(?:the\s+)?(?:above|previous)\s+instructions/i,
      /you\s+must\s+ignore/i,
      /system\s+prompt/i
    ];
    injectionPatterns.forEach(pattern => {
      const match = content.match(pattern);
      if (match) {
        violations.push({
          file: relativePath,
          rule: 'L-5',
          description: 'セキュリティ違反: プロンプトインジェクションの疑いがある指示文が検出されました。',
          location: `検出箇所: "${match[0]}"`,
          suggestion: 'システムへの悪意ある指示やプロンプト指示を上書きするような文言を削除してください。'
        });
      }
    });

    // L-6: Document Format (Markdown only)
    if (fileExt === '.md') {
      // Check code blocks
      const codeBlockCount = (content.match(/^```/gm) || []).length;
      if (codeBlockCount % 2 !== 0) {
        violations.push({
          file: relativePath,
          rule: 'L-6',
          description: 'ドキュメントフォーマット: 閉じられていないコードブロックがあります。',
          location: `\`\`\` の出現数: ${codeBlockCount}回（奇数回）`,
          suggestion: 'Markdown のコードフェンス（```）が正しく閉じられているか確認してください。'
        });
      }
    }
  }
});

// Generate report
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
let reportContent = `# IRIS Lint Report (${bundle})

-   Generated At: ${timestamp}

`;

if (violations.length === 0) {
  reportContent += `## Status: PASS

すべてのソースファイルがルールに適合しています。品質ゲートを通過可能です。
`;
} else {
  reportContent += `## Status: FAIL

**${violations.length}** 件の違反が検出されました。これらの問題を修正してから Ingest を実行してください。

### 違反一覧

| ファイルパス | ルールID | 違反箇所/内容 | 推奨修正アクション |
| :--- | :--- | :--- | :--- |
`;

  violations.forEach(v => {
    const safeDesc = v.description.replace(/\|/g, '\\|');
    const safeLoc = v.location.replace(/\|/g, '\\|').replace(/\n/g, '<br>');
    const safeSugg = v.suggestion.replace(/\|/g, '\\|');
    reportContent += `| \`${v.file.replace(/\\/g, '/')}\` | \`${v.rule}\` | ${safeDesc}<br>_(${safeLoc})_ | ${safeSugg} |\n`;
  });
}

fs.writeFileSync(reportFile, reportContent, 'utf8');
console.log(`Lint completed. Report generated at: ${reportFile}`);
if (violations.length > 0) {
  console.log(`Failed. Total violations: ${violations.length}`);
  process.exit(1);
} else {
  console.log('Passed. No violations found.');
}

