package parser

import (
	"context"
	"fmt"

	contractevents "bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/contracts/events"
)

// RecordCodeFileOutcome marks a code file as processed for fan-in without parsing.
// Used for skipped files and permanent stream failures so counters can advance.
func (s *Service) RecordCodeFileOutcome(ctx context.Context, event contractevents.FileChangedEvent) error {
	return s.recordCodeFileOutcome(ctx, event)
}

// RecordCodeFileFailure records a permanent code-parse failure and advances fan-in.
func (s *Service) RecordCodeFileFailure(ctx context.Context, event contractevents.FileChangedEvent) error {
	if s.finalize != nil {
		if err := s.finalize.IncrementFailedCodeFiles(ctx, event.RepoID, event.SnapshotID, event.FilePath); err != nil {
			return fmt.Errorf("increment failed_code_files: %w", err)
		}
	}
	return s.recordCodeFileOutcome(ctx, event)
}

func (s *Service) ensureCodeParseFanInSynced(ctx context.Context, repoID, snapshotID string) (bool, error) {
	if s.finalize == nil {
		return true, nil
	}

	run, err := s.finalize.GetIndexingRun(ctx, repoID, snapshotID)
	if err != nil {
		return false, fmt.Errorf("load indexing run: %w", err)
	}
	pathCount, err := s.finalize.CountProcessedCodeFilePaths(ctx, repoID, snapshotID)
	if err != nil {
		return false, err
	}
	snapshotCodeFiles, err := s.finalize.CountSnapshotCodeFiles(ctx, repoID, snapshotID)
	if err != nil {
		return false, err
	}

	if run.ExpectedCodeFiles <= 0 {
		// ExpectedCodeFiles not yet populated by repo-sync (race with run init) OR
		// the repo genuinely has no code files. If ANY code work is present or
		// expected (pathCount>0 or inventory>0), the run just isn't initialized
		// yet -> wait, so a repo.snapshot.ready trigger cannot seal a truncated
		// graph. Only complete when there is genuinely nothing.
		if pathCount > 0 || snapshotCodeFiles > 0 {
			return false, nil
		}
		if err := s.finalize.SyncCodeParseFanIn(ctx, repoID, snapshotID, s.cfg.CodeParseFailureThreshold); err != nil {
			return false, err
		}
		return true, nil
	}

	if !CodeParseFanInComplete(run.ExpectedCodeFiles, run.ProcessedCodeFiles, pathCount, snapshotCodeFiles) {
		return false, nil
	}

	// Reconcile the scalar counter to the distinct path count whenever fan-in is
	// complete — unconditionally. The counter can be over-counted (> expected),
	// so the old `processed < expected` guard skipped the correction exactly when
	// it was needed. SyncCodeParseFanIn now sets processed_code_files = pathCount.
	if err := s.finalize.SyncCodeParseFanIn(ctx, repoID, snapshotID, s.cfg.CodeParseFailureThreshold); err != nil {
		return false, err
	}
	return true, nil
}
