package service

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"time"

	gitcontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/git"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/events"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/domain"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/git"
)

type expectedCounts struct {
	codeFiles int
	docsFiles int
	commits   int
}

func computeExpectedCounts(result gitcontract.SyncResult) expectedCounts {
	var counts expectedCounts
	for _, change := range result.FileChanges {
		switch change.FileKind {
		case events.FileKindCode:
			counts.codeFiles++
		case events.FileKindDocs:
			if git.IsParseableDocPath(change.Path) {
				counts.docsFiles++
			}
		}
	}
	counts.commits = len(result.CommitChanges)
	return counts
}

func buildSnapshotFiles(result gitcontract.SyncResult, now time.Time) []domain.SnapshotFile {
	snap := result.Snapshot
	files := make([]domain.SnapshotFile, 0, len(result.FileChanges))
	for _, change := range result.FileChanges {
		files = append(files, domain.SnapshotFile{
			RepoID:     snap.RepoID,
			SnapshotID: snap.SnapshotID,
			CommitSHA:  snap.CommitSHA,
			FilePath:   change.Path,
			Language:   change.Language,
			FileKind:   string(change.FileKind),
			ChangeType: string(change.ChangeType),
			Status:     domain.SnapshotFileStatusPending,
			CreatedAt:  now,
		})
	}
	return files
}

func newIndexingRun(job domain.SyncJob, result gitcontract.SyncResult, counts expectedCounts, startedAt time.Time) domain.IndexingRun {
	snap := result.Snapshot
	mode := domain.IndexingRunModeReindex
	if result.IsFirstSync {
		mode = domain.IndexingRunModeFresh
	}
	return domain.IndexingRun{
		IndexingRunID:      "", // set by caller after ID generation
		JobID:              job.JobID,
		RepoID:             snap.RepoID,
		SnapshotID:         snap.SnapshotID,
		CommitSHA:          snap.CommitSHA,
		Ref:                snap.Ref,
		Mode:               mode,
		Status:             domain.IndexingRunStatusRunning,
		ExpectedCodeFiles:  counts.codeFiles,
		ExpectedDocsFiles:  counts.docsFiles,
		ExpectedCommits:    counts.commits,
		ProcessedCodeFiles: 0,
		ProcessedDocsFiles: 0,
		FailedFiles:        0,
		Stages: domain.IndexingRunStages{
			RepoSync:      domain.StageStatusRunning,
			DocsParse:     domain.StageStatusPending,
			CodeParse:     domain.StageStatusPending,
			GraphFinalize: domain.StageStatusPending,
			CommitIntel:   domain.StageStatusPending,
		},
		StartedAt: startedAt,
	}
}

func (s *SyncService) persistIndexingRun(ctx context.Context, job domain.SyncJob, result gitcontract.SyncResult) (domain.IndexingRun, error) {
	now := time.Now().UTC()
	counts := computeExpectedCounts(result)
	run := newIndexingRun(job, result, counts, now)

	runID, err := newIndexingRunID()
	if err != nil {
		return domain.IndexingRun{}, err
	}
	run.IndexingRunID = runID

	if err := s.metadata.CreateIndexingRun(ctx, run); err != nil {
		return domain.IndexingRun{}, fmt.Errorf("create indexing run: %w", err)
	}

	files := buildSnapshotFiles(result, now)
	if err := s.metadata.InsertSnapshotFiles(ctx, files); err != nil {
		return domain.IndexingRun{}, fmt.Errorf("insert snapshot files: %w", err)
	}
	return run, nil
}

func (s *SyncService) completeIndexingRun(ctx context.Context, run domain.IndexingRun) error {
	completedAt := time.Now().UTC()
	run.Status = domain.IndexingRunStatusCompleted
	run.Stages.RepoSync = domain.StageStatusCompleted
	run.CompletedAt = &completedAt
	run.Error = ""
	return s.metadata.UpdateIndexingRun(ctx, run)
}

func (s *SyncService) failIndexingRun(ctx context.Context, run domain.IndexingRun, cause error) {
	completedAt := time.Now().UTC()
	run.Status = domain.IndexingRunStatusFailed
	run.Stages.RepoSync = domain.StageStatusFailed
	run.Error = cause.Error()
	run.CompletedAt = &completedAt
	if err := s.metadata.UpdateIndexingRun(ctx, run); err != nil {
		s.logger.Error("failed to update indexing run", "indexing_run_id", run.IndexingRunID, "error", err)
	}
}

func newIndexingRunID() (string, error) {
	var b [8]byte
	if _, err := rand.Read(b[:]); err != nil {
		return "", fmt.Errorf("generate indexing run id: %w", err)
	}
	return "run_" + hex.EncodeToString(b[:]), nil
}
