import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = __dirname;
const SCAN_ROOTS = ['src', 'scripts', 'schema', 'docs', '.github', 'package.json', 'tsconfig.json', '.gitleaks.toml'] as const;

const TEXT_FILE_EXTENSIONS = new Set([
    '.ts', '.tsx', '.js', '.mjs', '.cjs', '.json', '.md', '.yml', '.yaml', '.toml', '.sql', '.txt'
]);

const EXCLUDED_PATH_SEGMENTS = ['node_modules', 'dist', '.git', '.vite'];
const PLACEHOLDER_HINTS = ['your_', 'your-', 'example', 'changeme', 'placeholder', '<redacted>', 'xxxx'];

const SECRET_ASSIGNMENT_PATTERNS: Array<{ name: string; regex: RegExp }> = [
    {
        name: 'env_secret_assignment',
        regex: /\b(?:SUPABASE_(?:KEY|SERVICE_ROLE_KEY)|ALPACA_API_KEY|ALPACA_SECRET_KEY|REAL_ALPACA_API_KEY|REAL_ALPACA_SECRET_KEY|OPENAI_API_KEY|GEMINI_API_KEY|PERPLEXITY_API_KEY)\s*=\s*([^\s#'"]{12,})/gi
    },
    {
        name: 'openai_like_secret',
        regex: /\bsk-[a-z0-9]{20,}\b/gi
    },
    {
        name: 'aws_access_key_like',
        regex: /\bAKIA[0-9A-Z]{16}\b/g
    }
];

function shouldScan(filePath: string): boolean {
    const normalized = filePath.replace(/\\/g, '/');
    if (EXCLUDED_PATH_SEGMENTS.some((segment) => normalized.includes(`/${segment}/`) || normalized.startsWith(`${segment}/`))) {
        return false;
    }

    const ext = path.extname(filePath).toLowerCase();
    if (TEXT_FILE_EXTENSIONS.has(ext)) return true;
    const base = path.basename(filePath).toLowerCase();
    return base.startsWith('.env');
}

function isLikelyPlaceholder(value: string): boolean {
    const normalized = value.trim().toLowerCase();
    return PLACEHOLDER_HINTS.some((hint) => normalized.includes(hint));
}

function main() {
    const files: string[] = [];
    const walk = (dirPath: string) => {
        const entries = fs.readdirSync(dirPath, { withFileTypes: true });
        for (const entry of entries) {
            const absolute = path.join(dirPath, entry.name);
            const relative = path.relative(repoRoot, absolute);
            if (!relative) continue;
            if (!shouldScan(relative) && entry.isFile()) continue;
            if (entry.isDirectory()) {
                if (EXCLUDED_PATH_SEGMENTS.includes(entry.name)) continue;
                walk(absolute);
            } else {
                if (shouldScan(relative)) {
                    files.push(relative);
                }
            }
        }
    };
    for (const rootEntry of SCAN_ROOTS) {
        const absolutePath = path.join(repoRoot, rootEntry);
        if (!fs.existsSync(absolutePath)) continue;
        const stat = fs.statSync(absolutePath);
        if (stat.isDirectory()) {
            walk(absolutePath);
        } else {
            files.push(path.relative(repoRoot, absolutePath));
        }
    }

    const findings: string[] = [];

    for (const relativePath of files) {
        const absolutePath = path.join(repoRoot, relativePath);
        let content = '';
        try {
            content = fs.readFileSync(absolutePath, 'utf8');
        } catch {
            continue;
        }

        for (const pattern of SECRET_ASSIGNMENT_PATTERNS) {
            let match: RegExpExecArray | null = null;
            while ((match = pattern.regex.exec(content)) !== null) {
                const rawValue = match[1] || match[0];
                if (isLikelyPlaceholder(rawValue)) continue;
                findings.push(`[${pattern.name}] ${relativePath}: ${match[0].slice(0, 120)}`);
            }
            pattern.regex.lastIndex = 0;
        }
    }

    assert.equal(
        findings.length,
        0,
        `Potential plaintext secrets detected:\n${findings.join('\n')}\n\nRotate credentials and scrub artifacts before release.`
    );

    console.log(`[secret-hygiene] OK: scanned ${files.length} tracked text files, no plaintext secret patterns found`);
}

main();
