package service

import (
	"context"
	"fmt"
	"time"

	caasmmodel "security-center-backend/internal/modules/caasm/model"
	"security-center-backend/internal/modules/caasm/repository"
	"security-center-backend/internal/pkg/logger"
)

type SyncJobTimeouts struct {
	RunningTimeout time.Duration
	QueuedTimeout  time.Duration
}

type SyncJobTimeoutProvider func(context.Context) (SyncJobTimeouts, error)

type ZombieReaper struct {
	jobs            repository.SyncJobRepository
	timeoutProvider SyncJobTimeoutProvider
	logger          *logger.Logger
}

func DefaultSyncJobTimeouts() SyncJobTimeouts {
	return SyncJobTimeouts{
		RunningTimeout: time.Duration(caasmmodel.DefaultSyncJobRunningTimeoutMinutes) * time.Minute,
		QueuedTimeout:  time.Duration(caasmmodel.DefaultSyncJobQueuedTimeoutMinutes) * time.Minute,
	}
}

func SyncJobTimeoutsFromSettings(settings caasmmodel.SyncSettings) SyncJobTimeouts {
	timeouts := DefaultSyncJobTimeouts()
	if settings.RunningTimeoutMinutes > 0 {
		timeouts.RunningTimeout = time.Duration(settings.RunningTimeoutMinutes) * time.Minute
	}
	if settings.QueuedTimeoutMinutes > 0 {
		timeouts.QueuedTimeout = time.Duration(settings.QueuedTimeoutMinutes) * time.Minute
	}
	return timeouts
}

func NewZombieReaper(jobs repository.SyncJobRepository, timeoutProviders ...SyncJobTimeoutProvider) *ZombieReaper {
	var timeoutProvider SyncJobTimeoutProvider
	if len(timeoutProviders) > 0 {
		timeoutProvider = timeoutProviders[0]
	}
	return &ZombieReaper{jobs: jobs, timeoutProvider: timeoutProvider}
}

func (r *ZombieReaper) SetLogger(log *logger.Logger) {
	r.logger = log
}

func (r *ZombieReaper) resolveTimeouts(ctx context.Context) SyncJobTimeouts {
	if r.timeoutProvider == nil {
		return DefaultSyncJobTimeouts()
	}
	timeouts, err := r.timeoutProvider(ctx)
	if err != nil {
		r.logError("failed to resolve configured sync job timeouts; using defaults", "error", err)
		return DefaultSyncJobTimeouts()
	}
	if timeouts.RunningTimeout <= 0 || timeouts.QueuedTimeout <= 0 {
		return DefaultSyncJobTimeouts()
	}
	return timeouts
}

func formatTimeoutForMessage(d time.Duration) string {
	if d%time.Minute == 0 {
		minutes := int(d / time.Minute)
		if minutes == 1 {
			return "1 minute"
		}
		return fmt.Sprintf("%d minutes", minutes)
	}
	return d.String()
}

// Start begins the background reaper process.
// It should be run in a goroutine.
func (r *ZombieReaper) Start(ctx context.Context) {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	r.logInfo("caasm zombie reaper started")

	for {
		select {
		case <-ctx.Done():
			r.logInfo("caasm zombie reaper stopped")
			return
		case <-ticker.C:
			r.reap(ctx)
		}
	}
}

func (r *ZombieReaper) reap(ctx context.Context) {
	timeouts := r.resolveTimeouts(ctx)
	runningOlderThan := time.Now().Add(-timeouts.RunningTimeout)
	queuedOlderThan := time.Now().Add(-timeouts.QueuedTimeout)

	// Check Running jobs
	runningJobs, err := r.jobs.FindStaleJobs(ctx, caasmmodel.SyncJobRunning, runningOlderThan)
	if err != nil {
		r.logError("failed to query stale running sync jobs", "error", err, "status", caasmmodel.SyncJobRunning)
	} else {
		for _, job := range runningJobs {
			r.failJob(ctx, job, fmt.Sprintf("Job timed out (exceeded %s)", formatTimeoutForMessage(timeouts.RunningTimeout)))
		}
	}

	// Check Queued jobs (if they stay queued too long without being picked up)
	queuedJobs, err := r.jobs.FindStaleJobs(ctx, caasmmodel.SyncJobQueued, queuedOlderThan)
	if err != nil {
		r.logError("failed to query stale queued sync jobs", "error", err, "status", caasmmodel.SyncJobQueued)
	} else {
		for _, job := range queuedJobs {
			r.failJob(ctx, job, fmt.Sprintf("Job timed out in queue (exceeded %s)", formatTimeoutForMessage(timeouts.QueuedTimeout)))
		}
	}
}

func (r *ZombieReaper) failJob(ctx context.Context, job caasmmodel.SyncJob, reason string) {
	r.logInfo("marking stale sync job failed", "job_id", job.ID, "reason", reason)

	writeCtx := syncJobDurableContext(ctx)
	if latest, err := r.jobs.FindByID(writeCtx, job.ID); err != nil || latest == nil || isSyncJobTerminalStatus(latest.Status) {
		if err != nil {
			r.logError("failed to load stale sync job before terminal update", "job_id", job.ID, "error", err)
		}
		return
	}

	job.Status = caasmmodel.SyncJobFailed
	job.ErrorMessage = reason
	finishedAt := time.Now()
	job.FinishedAt = &finishedAt

	// Use UpdateFields to ensure status is persisted
	err := r.jobs.UpdateFields(writeCtx, job.ID, map[string]interface{}{
		"status":        caasmmodel.SyncJobFailed,
		"error_message": reason,
		"finished_at":   finishedAt,
	})

	if err != nil {
		r.logError("failed to mark stale sync job failed", "job_id", job.ID, "error", err)
	}
}

func (r *ZombieReaper) logInfo(message string, args ...any) {
	if r.logger != nil {
		r.logger.Info(message, args...)
	}
}

func (r *ZombieReaper) logError(message string, args ...any) {
	if r.logger != nil {
		r.logger.Error(message, args...)
	}
}
