package service

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/client"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/config"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/domain"
	gitpkg "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/git"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/purge"
)

var (
	// ErrForceSyncBusy indicates another sync workflow is already in progress.
	ErrForceSyncBusy = errors.New("a sync is already in progress")
	// ErrForceSyncNotAllowed indicates force sync is not permitted for the repository state.
	ErrForceSyncNotAllowed = errors.New("force sync is only allowed for indexing or failed repositories")
)

// ForceSyncAccepted summarizes an accepted force sync request.
type ForceSyncAccepted struct {
	RepoID string
	Ref    string
	JobID  string
	Mode   string
	Purged purge.Result
}

// ForceSyncDeps configures purge and side-effect clients for force sync.
type ForceSyncDeps struct {
	Config    config.Config
	Purger    *purge.PipelinePurger
	Embedding *client.EmbeddingEngineClient
}

// ConfigureForceSync attaches force sync dependencies to the service.
func (s *SyncService) ConfigureForceSync(deps ForceSyncDeps) {
	s.forceSyncCfg = deps.Config
	s.purger = deps.Purger
	s.embedding = deps.Embedding
}

// TriggerForceSync purges pipeline state, removes the git clone, and starts a fresh sync.
func (s *SyncService) TriggerForceSync(repoID, ref string, forceOverride bool) (ForceSyncAccepted, error) {
	repoID = strings.TrimSpace(repoID)
	ref = strings.TrimSpace(ref)
	if ref == "" {
		ref = DefaultRef
	}

	if s.ActiveSyncCount() > 0 {
		return ForceSyncAccepted{}, ErrForceSyncBusy
	}
	if !s.forceSyncAllowed(repoID, forceOverride) {
		return ForceSyncAccepted{}, ErrForceSyncNotAllowed
	}
	if s.purger == nil {
		return ForceSyncAccepted{}, fmt.Errorf("force sync purge is not configured")
	}

	jobID, err := newJobID()
	if err != nil {
		return ForceSyncAccepted{}, err
	}

	startedAt := time.Now().UTC()
	job := domain.SyncJob{
		JobID:     jobID,
		RepoID:    repoID,
		Ref:       ref,
		Status:    domain.SyncStatusRunning,
		StartedAt: startedAt,
	}
	ctx := context.Background()
	if err := s.metadata.CreateSyncJob(ctx, job); err != nil {
		return ForceSyncAccepted{}, fmt.Errorf("create sync job: %w", err)
	}

	s.beginSync()

	purged, err := s.purger.PurgeRepo(ctx, repoID)
	if err != nil {
		s.failForceSyncJob(ctx, job, err)
		s.endSync(false)
		return ForceSyncAccepted{}, fmt.Errorf("purge pipeline data: %w", err)
	}

	if s.embedding != nil {
		if err := s.embedding.DeleteRepoVectors(ctx, repoID); err != nil {
			s.failForceSyncJob(ctx, job, err)
			s.endSync(false)
			return ForceSyncAccepted{}, fmt.Errorf("purge qdrant vectors: %w", err)
		}
	}

	if err := gitpkg.RemoveWorkspace(s.forceSyncCfg, repoID); err != nil {
		s.failForceSyncJob(ctx, job, err)
		s.endSync(false)
		return ForceSyncAccepted{}, fmt.Errorf("remove workspace: %w", err)
	}

	s.logger.Info("force sync purge completed",
		"repo_id", repoID,
		"ref", ref,
		"job_id", jobID,
		"purged", purged,
	)

	go func() {
		if err := s.runSync(context.Background(), repoID, ref, job); err != nil {
			s.logger.Error("force sync failed", "repo_id", repoID, "ref", ref, "job_id", jobID, "error", err)
		}
	}()

	return ForceSyncAccepted{
		RepoID: repoID,
		Ref:    ref,
		JobID:  jobID,
		Mode:   domain.IndexingRunModeFresh,
		Purged: purged,
	}, nil
}

func (s *SyncService) forceSyncAllowed(repoID string, forceOverride bool) bool {
	if forceOverride {
		return true
	}

	run, ok := s.GetLatestIndexingRun(repoID)
	if !ok {
		return true
	}

	switch run.Status {
	case domain.IndexingRunStatusRunning,
		domain.IndexingRunStatusFailed,
		domain.IndexingRunStatusPending,
		domain.IndexingRunStatusDegraded:
		return true
	default:
		return false
	}
}

// ForceSyncAllowedForTest exposes gate logic for unit tests.
func (s *SyncService) ForceSyncAllowedForTest(repoID string, forceOverride bool) bool {
	return s.forceSyncAllowed(repoID, forceOverride)
}

func (s *SyncService) failForceSyncJob(ctx context.Context, job domain.SyncJob, cause error) {
	completedAt := time.Now().UTC()
	job.Status = domain.SyncStatusFailed
	job.Error = cause.Error()
	job.CompletedAt = &completedAt
	if updateErr := s.metadata.UpdateSyncJob(ctx, job); updateErr != nil {
		s.logger.Error("failed to update force sync job", "job_id", job.JobID, "error", updateErr)
	}
	s.recordError(cause)
}
