import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
import { createHash } from 'crypto';
import { resolveNexusStateDir } from './runtime-registry.js';
import { appendOrReplaceNexusPrPresenceBlock, buildNexusProofMetadata, ensureNexusCoauthorTrailer, NEXUS_PRIME_COAUTHOR_TRAILER, NEXUS_PRIME_WEBSITE, renderNexusDocumentSignature, renderNexusPrPresenceBlock, } from './nexus-signature.js';
import { getSharedTelemetry } from './telemetry-remote.js';
export class GithubBridge {
    defaultCwd;
    telemetry;
    constructor(defaultCwd = process.cwd(), telemetry) {
        this.defaultCwd = defaultCwd;
        this.telemetry = telemetry;
    }
    detect(cwd = this.defaultCwd) {
        try {
            execSync('gh auth status', { cwd, stdio: 'ignore', timeout: 2_500 });
            return { available: true, method: 'gh-cli' };
        }
        catch {
            // fall through
        }
        if (process.env.GITHUB_TOKEN) {
            return { available: true, method: 'token' };
        }
        return { available: false, method: 'none', reason: 'No authenticated gh CLI session or GITHUB_TOKEN found' };
    }
    async promoteToPR(input) {
        const cwd = input.cwd ?? this.defaultCwd;
        const detection = this.detect(cwd);
        const diffSummary = this.safeExec('git diff --stat', cwd);
        const diffPatch = this.safeExec('git diff --binary', cwd);
        if (!diffPatch.trim()) {
            return this.persistArtifact(input, detection.method, 'No working-tree diff available for promotion', diffSummary, diffPatch);
        }
        if (!detection.available) {
            return this.persistArtifact(input, 'none', detection.reason ?? 'GitHub bridge unavailable', diffSummary, diffPatch);
        }
        const repo = this.resolveRepo(cwd);
        if (!repo) {
            return this.persistArtifact(input, detection.method, 'No GitHub origin remote detected', diffSummary, diffPatch);
        }
        const branch = this.buildBranchName(input);
        const baseBranch = input.baseBranch ?? this.detectBaseBranch(cwd);
        try {
            this.safeExec(`git checkout -b ${this.shellQuote(branch)}`, cwd, true);
            this.safeExec('git add -A', cwd, true);
            const subject = `[NOVA] Promote Darwin cycle ${input.cycleId ?? 'candidate'} to the fleet`;
            const body = ensureNexusCoauthorTrailer(this.renderCommitBody(input), true);
            this.safeExec(`git commit -m ${this.shellQuote(subject)} -m ${this.shellQuote(body)}`, cwd, true);
            this.trackProductEvent('commit_attributed', {
                surface: 'git_commit',
                role: 'published',
                method: detection.method,
                attribution: 'coauthor_trailer',
            });
            this.safeExec(`git push -u origin ${this.shellQuote(branch)}`, cwd, true);
            if (detection.method === 'gh-cli') {
                const title = `Darwin: ${input.hypothesis}`.slice(0, 72);
                const prBody = this.renderPrBody(input, diffSummary, baseBranch);
                const url = this.safeExec(`gh pr create --base ${this.shellQuote(baseBranch)} --head ${this.shellQuote(branch)} --title ${this.shellQuote(title)} --body ${this.shellQuote(prBody)}`, cwd, true).trim();
                this.trackProductEvent('pr_presence_added', {
                    surface: 'pull_request',
                    role: 'published',
                    method: detection.method,
                    verification: 'local_diff_captured',
                });
                return { status: 'pr_created', method: detection.method, branch, url };
            }
            const url = await this.createPullRequestViaApi(repo.owner, repo.name, branch, baseBranch, input, diffSummary);
            this.trackProductEvent('pr_presence_added', {
                surface: 'pull_request',
                role: 'published',
                method: detection.method,
                verification: 'local_diff_captured',
            });
            return { status: 'pr_created', method: detection.method, branch, url };
        }
        catch (error) {
            return this.persistArtifact(input, detection.method, error?.message ?? 'GitHub promotion failed', diffSummary, diffPatch);
        }
    }
    async createPullRequestViaApi(owner, repo, branch, baseBranch, input, diffSummary) {
        const token = process.env.GITHUB_TOKEN;
        if (!token) {
            throw new Error('GITHUB_TOKEN missing for token-based PR creation');
        }
        const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls`, {
            method: 'POST',
            headers: {
                Accept: 'application/vnd.github+json',
                Authorization: `Bearer ${token}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                title: `Darwin: ${input.hypothesis}`.slice(0, 72),
                head: branch,
                base: baseBranch,
                body: this.renderPrBody(input, diffSummary, baseBranch),
            }),
            signal: AbortSignal.timeout(10_000),
        });
        if (!response.ok) {
            throw new Error(`GitHub API PR creation failed (${response.status})`);
        }
        const payload = await response.json();
        if (!payload.html_url) {
            throw new Error('GitHub API PR creation returned no html_url');
        }
        return payload.html_url;
    }
    persistArtifact(input, method, reason, diffSummary, diffPatch) {
        const stateDir = path.join(resolveNexusStateDir(), 'autonomy-artifacts');
        fs.mkdirSync(stateDir, { recursive: true });
        const slug = createHash('sha1')
            .update(`${Date.now()}:${input.hypothesis}:${input.cycleId ?? ''}`)
            .digest('hex')
            .slice(0, 10);
        const patchPath = path.join(stateDir, `darwin-${slug}.patch`);
        const artifactPath = path.join(stateDir, `darwin-${slug}.json`);
        const signatureInput = {
            source: 'github-bridge',
            role: 'observed',
            runId: input.cycleId,
            cycleId: input.cycleId,
            verificationState: diffPatch.trim() ? 'advisory' : 'not-run',
            hooksState: 'not-applicable',
            telemetryConsent: this.telemetryConsentLabel(),
            humanReviewState: 'required',
            coauthorState: 'Not claimed because no GitHub PR was created.',
        };
        const documentSignature = renderNexusDocumentSignature({
            ...signatureInput,
            artifactKind: 'GitHub promotion artifact',
        });
        fs.writeFileSync(patchPath, diffPatch, 'utf8');
        fs.writeFileSync(artifactPath, JSON.stringify({
            createdAt: new Date().toISOString(),
            method,
            reason,
            hypothesis: input.hypothesis,
            cycleId: input.cycleId,
            targetFile: input.targetFile,
            learnings: input.learnings ?? [],
            diffSummary,
            patchPath,
            nexusPrime: {
                website: NEXUS_PRIME_WEBSITE,
                coauthorTrailer: NEXUS_PRIME_COAUTHOR_TRAILER,
                prPresence: renderNexusPrPresenceBlock(signatureInput),
                documentSignature,
                metadata: buildNexusProofMetadata(signatureInput),
            },
        }, null, 2), 'utf8');
        this.trackProductEvent('document_signed', {
            surface: 'github_promotion_artifact',
            role: 'observed',
            method,
            status: 'artifact_saved',
        });
        return {
            status: 'artifact_saved',
            method,
            artifactPath,
            reason,
        };
    }
    resolveRepo(cwd) {
        const remote = this.safeExec('git remote get-url origin', cwd).trim();
        const match = remote.match(/github\.com[:/](.+?)\/(.+?)(?:\.git)?$/i);
        if (!match)
            return null;
        return { owner: match[1], name: match[2] };
    }
    detectBaseBranch(cwd) {
        const current = this.safeExec('git rev-parse --abbrev-ref HEAD', cwd).trim();
        if (current && current !== 'HEAD')
            return current;
        return 'main';
    }
    buildBranchName(input) {
        const seed = `${input.cycleId ?? 'darwin'}-${input.hypothesis}`
            .toLowerCase()
            .replace(/[^a-z0-9]+/g, '-')
            .replace(/^-+|-+$/g, '')
            .slice(0, 48);
        return `ad/${seed || 'darwin-candidate'}`;
    }
    renderCommitBody(input) {
        return [
            'The autonomous engine marked this bounded improvement worthy of review.',
            `Hypothesis: ${input.hypothesis}`,
            ...(input.learnings?.length ? [`Learnings: ${input.learnings.join('; ')}`] : []),
        ].join('\n\n');
    }
    renderPrBody(input, diffSummary, baseBranch) {
        const body = [
            '### Mission Briefing',
            'A bounded Darwin candidate completed local validation and now seeks human review through the standard GitHub path.',
            '',
            '### Candidate',
            `- Hypothesis: ${input.hypothesis}`,
            `- Cycle: ${input.cycleId ?? 'unknown'}`,
            `- Target file: ${input.targetFile ?? 'unspecified'}`,
            `- Base branch: ${baseBranch}`,
            '',
            '### Learnings',
            ...(input.learnings?.length ? input.learnings.map((learning) => `- ${learning}`) : ['- No additional learnings recorded']),
            '',
            '### Diff Summary',
            '```text',
            diffSummary.trim() || 'No diff summary available.',
            '```',
        ].join('\n');
        return appendOrReplaceNexusPrPresenceBlock(body, {
            source: 'github-bridge',
            role: 'published',
            runId: input.cycleId,
            cycleId: input.cycleId,
            verificationState: diffSummary.trim() ? 'advisory' : 'unknown',
            hooksState: 'unknown',
            telemetryConsent: this.telemetryConsentLabel(),
            humanReviewState: 'required',
            coauthorState: 'Required on the Nexus-generated promotion commit.',
        });
    }
    safeExec(command, cwd, throwOnError = false) {
        try {
            return execSync(command, {
                cwd,
                encoding: 'utf8',
                stdio: ['ignore', 'pipe', 'pipe'],
                timeout: 15_000,
            });
        }
        catch (error) {
            if (throwOnError) {
                const stderr = error?.stderr ? String(error.stderr) : '';
                const stdout = error?.stdout ? String(error.stdout) : '';
                throw new Error([stderr, stdout, error?.message].filter(Boolean).join('\n').trim());
            }
            return '';
        }
    }
    async shareAsGist(pattern) {
        const detection = this.detect();
        if (detection.method === 'gh-cli') {
            try {
                const filename = `nexus-skill-${pattern.id}.md`;
                const tmpPath = path.join(resolveNexusStateDir(), 'tmp', filename);
                fs.mkdirSync(path.dirname(tmpPath), { recursive: true });
                fs.writeFileSync(tmpPath, `# ${pattern.description}\n\n${pattern.content}`, 'utf8');
                const url = this.safeExec(`gh gist create ${this.shellQuote(tmpPath)} --desc ${this.shellQuote(pattern.description)} --public`, this.defaultCwd, true).trim();
                try {
                    fs.unlinkSync(tmpPath);
                }
                catch { /* best effort cleanup */ }
                return { url };
            }
            catch {
                // fall through to local save
            }
        }
        if (detection.method === 'token' && process.env.GITHUB_TOKEN) {
            try {
                const response = await fetch('https://api.github.com/gists', {
                    method: 'POST',
                    headers: {
                        Accept: 'application/vnd.github+json',
                        Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        description: pattern.description,
                        public: false,
                        files: { [`nexus-skill-${pattern.id}.md`]: { content: `# ${pattern.description}\n\n${pattern.content}` } },
                    }),
                    signal: AbortSignal.timeout(10_000),
                });
                if (response.ok) {
                    const payload = await response.json();
                    if (payload.html_url)
                        return { url: payload.html_url };
                }
            }
            catch {
                // fall through to local save
            }
        }
        // Graceful degradation: save locally
        const stateDir = path.join(resolveNexusStateDir(), 'shared-patterns');
        fs.mkdirSync(stateDir, { recursive: true });
        const localPath = path.join(stateDir, `nexus-skill-${pattern.id}.md`);
        fs.writeFileSync(localPath, `# ${pattern.description}\n\n${pattern.content}`, 'utf8');
        return { localPath };
    }
    shellQuote(value) {
        return `'${value.replace(/'/g, `'\\''`)}'`;
    }
    telemetryConsentLabel() {
        try {
            const telemetry = this.telemetry ?? getSharedTelemetry();
            return telemetry instanceof Object && 'isOptedIn' in telemetry && typeof telemetry.isOptedIn === 'function'
                ? (telemetry.isOptedIn() ? 'opted-in' : 'disabled')
                : 'unknown';
        }
        catch {
            return 'unknown';
        }
    }
    trackProductEvent(type, metadata) {
        try {
            const telemetry = this.telemetry ?? getSharedTelemetry();
            telemetry.trackProductEvent(type, metadata);
        }
        catch {
            // Telemetry must never block promotion or artifact persistence.
        }
    }
}
let _sharedGithubBridge = null;
export function getSharedGithubBridge(cwd) {
    if (!_sharedGithubBridge) {
        _sharedGithubBridge = new GithubBridge(cwd);
    }
    return _sharedGithubBridge;
}
