import { logInfo, logError } from '../../../../../frameworks/logging/logBuffer.js';
import { enqueueReview, createJobId, updateJobProgress } from '../../../../../frameworks/queue/pQueueAdapter.js';
import { loadProjectConfig, getFollowupAgents } from '../../../../../config/projectConfig.js';
import { DEFAULT_FOLLOWUP_AGENTS } from '../../../../../modules/review-execution/entities/progress/agentDefinition.type.js';
import { invokeClaudeReview, sendNotification } from '../../../../../claude/invoker.js';
import { parseReviewOutput } from '../../../../../modules/statistics-insights/services/statsService.js';
import { parseThreadActions } from '../../../../../modules/review-execution/services/threadActionsParser.js';
import { executeThreadActions, defaultCommandExecutor } from '../../../../../modules/review-execution/services/threadActionsExecutor.js';
import { startWatchingReviewContext, stopWatchingReviewContext } from '../../../../../main/websocket.js';
function validateProjectPath(path) {
    if (!path) {
        return { valid: false, error: 'projectPath required' };
    }
    const trimmed = path.trim();
    if (!trimmed.startsWith('/') || trimmed.includes('..')) {
        return { valid: false, error: 'Invalid path' };
    }
    return { valid: true, path: trimmed };
}
export const mrTrackingAdvancedRoutes = async (fastify, opts) => {
    const { getRepositories, reviewRequestTrackingGateway, reviewContextGateway: contextGateway, threadFetchGatewayFactory, diffMetadataFetchGatewayFactory, diffStatsFetchGatewayFactory, createSyncThreadsUseCase, recordReviewCompletion, enforceBudget, broadcastBudgetExceeded, claudeInvokerDeps, gateClaudeInvocation, logger, } = opts;
    fastify.post('/api/mr-tracking/followup', async (request, reply) => {
        const body = request.body;
        const { mrId, projectPath } = body;
        if (!mrId) {
            reply.code(400);
            return { success: false, error: 'mrId required' };
        }
        const validation = validateProjectPath(projectPath);
        if (!validation.valid) {
            reply.code(400);
            return { success: false, error: validation.error };
        }
        const match = mrId.match(/^(gitlab|github)-(.+)-(\d+)$/);
        if (!match) {
            reply.code(400);
            return { success: false, error: 'Invalid mrId format' };
        }
        const [, platform, , mrNumberStr] = match;
        const mrNumber = Number.parseInt(mrNumberStr, 10);
        const platformLiteral = platform === 'github' ? 'github' : 'gitlab';
        const repo = getRepositories().find((r) => r.localPath === validation.path && r.enabled);
        if (!repo) {
            reply.code(404);
            return { success: false, error: 'Repository not configured' };
        }
        const trackedMr = reviewRequestTrackingGateway.getByNumber(validation.path, mrNumber, platformLiteral);
        if (!trackedMr) {
            reply.code(404);
            return { success: false, error: 'MR not tracked' };
        }
        const projectConfig = loadProjectConfig(validation.path);
        const skill = projectConfig?.reviewFollowupSkill || 'review-followup';
        const gitProjectPath = repo.remoteUrl
            .replace(/^https?:\/\/[^/]+\//, '')
            .replace(/\.git$/, '');
        const jobId = createJobId(`${platform}-followup`, gitProjectPath, mrNumber);
        const mrUrl = platform === 'gitlab'
            ? `${repo.remoteUrl.replace(/\.git$/, '')}/-/merge_requests/${mrNumber}`
            : `${repo.remoteUrl.replace(/\.git$/, '')}/pull/${mrNumber}`;
        const budgetDecision = await enforceBudget.execute({
            localPaths: getRepositories()
                .filter((repository) => repository.enabled)
                .map((repository) => repository.localPath),
        });
        if (!budgetDecision.accepted) {
            logger.warn({
                mrNumber,
                limitUsd: budgetDecision.status.limitUsd,
                consumedUsd: budgetDecision.status.consumedUsd,
            }, 'Budget exceeded, manual followup not enqueued');
            broadcastBudgetExceeded({
                mrNumber,
                platform: platformLiteral,
                projectPath: gitProjectPath,
                limitUsd: budgetDecision.status.limitUsd,
                consumedUsd: budgetDecision.status.consumedUsd,
            });
            reply.code(200);
            return { status: 'rejected', reason: 'budget-exceeded' };
        }
        const manualFollowupJob = {
            id: jobId,
            platform: platformLiteral,
            projectPath: gitProjectPath,
            localPath: repo.localPath,
            mrNumber,
            mrUrl,
            skill,
            sourceBranch: trackedMr.sourceBranch,
            targetBranch: trackedMr.targetBranch,
            jobType: 'followup',
        };
        const manualFollowupProcessor = async (job, signal) => {
            sendNotification('Review followup started', `MR !${job.mrNumber}`, logger);
            // Use injected gateways to fetch threads + diff metadata.
            const threadFetchGateway = threadFetchGatewayFactory(job.platform);
            const diffMetadataFetchGateway = diffMetadataFetchGatewayFactory(job.platform);
            try {
                const threads = threadFetchGateway.fetchThreads(job.projectPath, job.mrNumber);
                let diffMetadata;
                try {
                    diffMetadata = diffMetadataFetchGateway.fetchDiffMetadata(job.projectPath, job.mrNumber);
                }
                catch (error) {
                    logger.warn({ mrNumber: job.mrNumber, error: error instanceof Error ? error.message : String(error) }, 'Failed to fetch diff metadata for followup, inline comments will be skipped');
                }
                const followupAgentsList = getFollowupAgents(job.localPath) ?? DEFAULT_FOLLOWUP_AGENTS;
                contextGateway.create({
                    localPath: job.localPath,
                    mergeRequestId: mrId,
                    platform: job.platform,
                    projectPath: job.projectPath,
                    mergeRequestNumber: job.mrNumber,
                    threads,
                    agents: followupAgentsList,
                    diffMetadata,
                });
                logger.info({ mrNumber: job.mrNumber, threadsCount: threads.length, hasDiffMetadata: !!diffMetadata }, 'Review context file created with threads for manual followup');
                startWatchingReviewContext(job.id, job.localPath, mrId);
                logger.info({ mrNumber: job.mrNumber }, 'Started watching review context for live progress');
            }
            catch (error) {
                logger.warn({ mrNumber: job.mrNumber, error: error instanceof Error ? error.message : String(error) }, 'Failed to create review context file for manual followup, continuing without it');
            }
            const result = await invokeClaudeReview(job, logger, (progress, event) => {
                updateJobProgress(job.id, progress, event);
                // Also update the review context file for file-based progress tracking
                const runningAgent = progress.agents.find(a => a.status === 'running');
                const completedAgents = progress.agents
                    .filter(a => a.status === 'completed')
                    .map(a => a.name);
                contextGateway.updateProgress(job.localPath, mrId, {
                    phase: progress.currentPhase,
                    currentStep: runningAgent?.name ?? null,
                    stepsCompleted: completedAgents,
                });
            }, signal, claudeInvokerDeps);
            stopWatchingReviewContext(mrId);
            if (result.success) {
                // Parse review output for stats
                const parsed = parseReviewOutput(result.stdout);
                // Collect thread actions from both sources:
                // 1. MCP actions from review context file (new way)
                // 2. Text markers from stdout (legacy fallback)
                const reviewContext = contextGateway.read(job.localPath, mrId);
                const mcpActions = reviewContext?.actions ?? [];
                const markerActions = parseThreadActions(result.stdout);
                // Combine actions (MCP takes priority, markers as fallback)
                const threadActions = mcpActions.length > 0 ? mcpActions : markerActions;
                let threadResolveCount = 0;
                if (threadActions.length > 0) {
                    threadResolveCount = threadActions.filter(a => a.type === 'THREAD_RESOLVE').length;
                    logger.info({ mcpActionsCount: mcpActions.length, markerActionsCount: markerActions.length, mrNumber: job.mrNumber }, 'Executing thread actions');
                    const actionResult = await executeThreadActions(threadActions, {
                        platform: job.platform,
                        projectPath: job.projectPath,
                        mrNumber: job.mrNumber,
                        localPath: job.localPath,
                        diffMetadata: reviewContext?.diffMetadata,
                    }, logger, defaultCommandExecutor);
                    logger.info({ ...actionResult, threadResolveCount, mrNumber: job.mrNumber }, 'Thread actions executed for manual followup');
                }
                // Sync threads to get real state after followup resolves threads
                const syncUseCase = createSyncThreadsUseCase(job.platform);
                const updatedMr = syncUseCase.execute({ projectPath: job.localPath, mrId });
                let diffStats = null;
                try {
                    diffStats = diffStatsFetchGatewayFactory(job.platform).fetchDiffStats(job.projectPath, job.mrNumber);
                }
                catch {
                    logger.warn({ mrNumber: job.mrNumber }, 'Failed to fetch diff stats for manual followup');
                }
                recordReviewCompletion.execute({
                    projectPath: job.localPath,
                    mrId,
                    reviewData: {
                        type: 'followup',
                        durationMs: result.durationMs,
                        score: parsed.score,
                        blocking: parsed.blocking,
                        warnings: parsed.warnings,
                        suggestions: parsed.suggestions,
                        threadsOpened: 0,
                        threadsClosed: threadResolveCount,
                        diffStats,
                    },
                    qualityThreshold: loadProjectConfig(job.localPath)?.qualityThreshold ?? null,
                });
                logger.info({
                    mrNumber: job.mrNumber,
                    score: parsed.score,
                    blocking: parsed.blocking,
                    openThreads: updatedMr?.openThreads,
                    state: updatedMr?.state,
                }, 'Manual followup stats recorded and threads synced');
                sendNotification('Review followup completed', `MR !${job.mrNumber}`, logger);
            }
            else if (!result.cancelled) {
                sendNotification('Review followup failed', `MR !${job.mrNumber}`, logger);
            }
        };
        if (gateClaudeInvocation) {
            const gateResult = await gateClaudeInvocation.execute({
                job: manualFollowupJob,
                triggerSource: 'dashboard-manual',
                processor: manualFollowupProcessor,
            });
            if (gateResult.status === 'pending') {
                logInfo('Manual followup parked for human confirmation', { mrId, mrNumber, pendingId: gateResult.pendingId });
                return { success: true, status: 'pending-confirmation', pendingId: gateResult.pendingId };
            }
            if (gateResult.status === 'rejected') {
                return { success: false, error: 'Review already in progress or recently performed' };
            }
            logInfo('Followup triggered manually', { mrId, mrNumber, skill });
            return { success: true, jobId, message: 'Followup review in progress' };
        }
        const enqueued = await enqueueReview(manualFollowupJob, manualFollowupProcessor);
        if (!enqueued) {
            return { success: false, error: 'Review already in progress or recently performed' };
        }
        logInfo('Followup triggered manually', { mrId, mrNumber, skill });
        return { success: true, jobId, message: 'Followup review in progress' };
    });
    fastify.post('/api/mr-tracking/auto-followup', async (request, reply) => {
        const body = request.body;
        const { mrId, projectPath, enabled } = body;
        if (!mrId) {
            reply.code(400);
            return { success: false, error: 'mrId requis' };
        }
        if (typeof enabled !== 'boolean') {
            reply.code(400);
            return { success: false, error: 'enabled (boolean) requis' };
        }
        const validation = validateProjectPath(projectPath);
        if (!validation.valid) {
            reply.code(400);
            return { success: false, error: validation.error };
        }
        reviewRequestTrackingGateway.update(validation.path, mrId, { autoFollowup: enabled });
        const result = reviewRequestTrackingGateway.getById(validation.path, mrId);
        if (!result) {
            reply.code(404);
            return { success: false, error: 'MR non trouvée' };
        }
        logInfo('Auto-followup toggled', { mrId, enabled });
        return { success: true, mr: result };
    });
    fastify.post('/api/mr-tracking/followup-importants', async (request, reply) => {
        const body = request.body;
        let repos;
        if (body.projectPath) {
            const validation = validateProjectPath(body.projectPath);
            if (!validation.valid) {
                reply.code(400);
                return { success: false, error: validation.error };
            }
            const repo = getRepositories().find(r => r.localPath === validation.path && r.enabled);
            if (!repo) {
                reply.code(404);
                return { success: false, error: 'Repository not configured' };
            }
            repos = [repo];
        }
        else {
            repos = getRepositories().filter(r => r.enabled);
        }
        const candidates = [];
        for (const repo of repos) {
            const mrs = reviewRequestTrackingGateway.getByState(repo.localPath, 'pending-approval');
            for (const mr of mrs) {
                if (mr.totalWarnings > 0) {
                    candidates.push({ mr, projectPath: repo.localPath });
                }
            }
        }
        if (candidates.length === 0) {
            return { success: true, triggered: 0, candidates: [], failed: [] };
        }
        const failed = [];
        let triggered = 0;
        for (const { mr, projectPath } of candidates) {
            try {
                const internalResponse = await fastify.inject({
                    method: 'POST',
                    url: '/api/mr-tracking/followup',
                    payload: { mrId: mr.id, projectPath },
                });
                const data = JSON.parse(internalResponse.body);
                if (data.success) {
                    triggered++;
                }
                else {
                    failed.push({ mrId: mr.id, error: data.error });
                }
            }
            catch (error) {
                failed.push({ mrId: mr.id, error: error instanceof Error ? error.message : String(error) });
            }
        }
        logInfo('Followup importants batch triggered', { triggered, failed: failed.length, total: candidates.length });
        return {
            success: true,
            triggered,
            candidates: candidates.map(c => ({ mrId: c.mr.id, mrNumber: c.mr.mrNumber, title: c.mr.title })),
            failed,
        };
    });
    fastify.post('/api/mr-tracking/sync', async (request, reply) => {
        const body = request.body;
        const { projectPath, mrId } = body;
        const validation = validateProjectPath(projectPath);
        if (!validation.valid) {
            reply.code(400);
            return { success: false, error: validation.error };
        }
        try {
            if (mrId) {
                const mrData = reviewRequestTrackingGateway.getById(validation.path, mrId);
                if (!mrData) {
                    reply.code(404);
                    return { success: false, error: 'MR/PR not found' };
                }
                const syncUseCase = createSyncThreadsUseCase(mrData.platform);
                const mr = syncUseCase.execute({ projectPath: validation.path, mrId });
                if (mr) {
                    logInfo('MR/PR synced', { mrId, openThreads: mr.openThreads, state: mr.state });
                    return { success: true, mr };
                }
                reply.code(404);
                return { success: false, error: 'MR/PR not found' };
            }
            const activeMrs = reviewRequestTrackingGateway.getActiveMrs(validation.path);
            for (const activeMr of activeMrs) {
                try {
                    const syncUseCase = createSyncThreadsUseCase(activeMr.platform);
                    syncUseCase.execute({ projectPath: validation.path, mrId: activeMr.id });
                }
                catch {
                    // Ignore individual MR sync failures
                }
            }
            const data = reviewRequestTrackingGateway.loadTracking(validation.path);
            const mrs = data?.mrs ?? [];
            logInfo('All MRs/PRs synced', { count: mrs.length });
            return { success: true, mrs };
        }
        catch (err) {
            const error = err;
            logError('Sync failed', { error: error.message });
            reply.code(500);
            return { success: false, error: error.message };
        }
    });
};
//# sourceMappingURL=mrTrackingAdvanced.routes.js.map