package service

import (
	"context"
	"sync"
	"time"

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

type SchedulerStatus struct {
	IsRunning   bool      `json:"is_running"`
	LastRunAt   time.Time `json:"last_run_at,omitempty"`
	NextRunAt   time.Time `json:"next_run_at,omitempty"`
	ActiveTasks int       `json:"active_tasks"`
}

type CollectorScheduler struct {
	cloudAcctRepo repository.CloudAccountRepository
	cloudAcctSvc  *CloudAccountService
	jobQueue      queue.JobQueueInterface
	logger        *logger.Logger
	stopChan      chan struct{}
	wg            sync.WaitGroup
	ticker        *time.Ticker
	staleTTL      time.Duration
	jobs          repository.SyncJobRepository
	capProvider   func(context.Context) (CoordinationCap, error)

	// Status tracking
	mu        sync.RWMutex
	isRunning bool
	lastRun   time.Time
}

func NewCollectorScheduler(
	cloudAcctRepo repository.CloudAccountRepository,
	cloudAcctSvc *CloudAccountService,
	jobs repository.SyncJobRepository,
	jobQueue queue.JobQueueInterface,
	log *logger.Logger,
	staleTTL time.Duration,
	capProvider func(context.Context) (CoordinationCap, error),
) *CollectorScheduler {
	if staleTTL <= 0 {
		staleTTL = 6 * time.Hour
	}
	return &CollectorScheduler{
		cloudAcctRepo: cloudAcctRepo,
		cloudAcctSvc:  cloudAcctSvc,
		jobs:          jobs,
		jobQueue:      jobQueue,
		logger:        log,
		stopChan:      make(chan struct{}),
		staleTTL:      staleTTL,
		capProvider:   capProvider,
	}
}

func (s *CollectorScheduler) Start() {
	s.mu.Lock()
	if s.isRunning {
		s.mu.Unlock()
		return
	}
	s.isRunning = true
	// Reset stop channel to allow restart after a previous stop.
	if s.stopChan == nil {
		s.stopChan = make(chan struct{})
	} else {
		select {
		case <-s.stopChan:
			s.stopChan = make(chan struct{})
		default:
		}
	}
	s.mu.Unlock()

	s.logger.Info("starting collector scheduler")
	s.ticker = time.NewTicker(1 * time.Minute)
	s.wg.Add(1)
	go s.scheduleLoop()
}

func (s *CollectorScheduler) Stop() {
	s.mu.Lock()
	if !s.isRunning {
		s.mu.Unlock()
		return
	}
	s.isRunning = false
	s.mu.Unlock()

	s.logger.Info("stopping collector scheduler")
	if s.ticker != nil {
		s.ticker.Stop()
	}
	close(s.stopChan)
	s.wg.Wait()
}

func (s *CollectorScheduler) GetStatus() SchedulerStatus {
	s.mu.RLock()
	defer s.mu.RUnlock()

	status := SchedulerStatus{
		IsRunning: s.isRunning,
		LastRunAt: s.lastRun,
	}

	if s.isRunning && !s.lastRun.IsZero() {
		status.NextRunAt = s.lastRun.Add(1 * time.Minute)
	} else if s.isRunning {
		status.NextRunAt = time.Now().Add(1 * time.Minute)
	}

	return status
}

func (s *CollectorScheduler) scheduleLoop() {
	defer s.wg.Done()

	for {
		select {
		case <-s.stopChan:
			return
		case <-s.ticker.C:
			s.runSync()
			s.cleanupStaleAssets()
		}
	}
}

func (s *CollectorScheduler) runSync() {
	s.mu.Lock()
	s.lastRun = time.Now()
	s.mu.Unlock()

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()
	if s.cloudAcctSvc != nil {
		enabled, err := s.cloudAcctSvc.isKillSwitchEnabled(ctx)
		if err != nil {
			s.logger.Error("failed to resolve kill switch before scheduled sync", "error", err)
			return
		}
		if enabled {
			s.logger.Info("skipping scheduled sync; kill switch is enabled")
			return
		}
	}

	accounts, err := s.cloudAcctRepo.FindDueForSync(ctx)
	if err != nil {
		s.logger.Error("failed to find accounts due for sync", "error", err)
		return
	}

	runtimeCap := s.runtimePressureCap(ctx)

	for _, acct := range accounts {
		if s.cloudAcctSvc != nil {
			enabled, err := s.cloudAcctSvc.isKillSwitchEnabled(ctx)
			if err != nil {
				s.logger.Error("failed to resolve kill switch during scheduled sync", "error", err)
				return
			}
			if enabled {
				s.logger.Info("stopping scheduled sync loop; kill switch is enabled")
				return
			}
		}
		if runtimeCap > 0 && s.activeJobCount(ctx) >= int64(runtimeCap) {
			s.logger.Info("skipping remaining scheduled syncs; runtime capacity reached",
				"active_jobs", s.activeJobCount(ctx),
				"runtime_cap", runtimeCap,
			)
			return
		}
		s.logger.Info("triggering scheduled sync", "account_id", acct.ID, "provider", acct.Provider)

		// Let the service own kill-switch, backoff, and active-job coordination.
		job, err := s.cloudAcctSvc.TriggerSync(ctx, acct.ID, nil)
		if err != nil {
			s.logger.Error("failed to trigger scheduled sync", "account_id", acct.ID, "error", err)
			continue
		}
		if job != nil {
			s.logger.Info("scheduled sync delegated to service coordination", "account_id", acct.ID, "job_id", job.ID, "job_status", job.Status)
		}
	}
}

func (s *CollectorScheduler) runtimePressureCap(ctx context.Context) int {
	if s.capProvider != nil {
		if result, err := s.capProvider(ctx); err == nil && result.Config.MaxConcurrency > 0 {
			return result.Config.MaxConcurrency
		}
	}
	return 0
}

func (s *CollectorScheduler) activeJobCount(ctx context.Context) int64 {
	if s.jobs == nil {
		return 0
	}
	count, err := s.jobs.CountActiveJobs(ctx)
	if err != nil {
		return 0
	}
	return count
}

// cleanupStaleAssets marks assets as inactive if they haven't been seen recently (no deletion).
func (s *CollectorScheduler) cleanupStaleAssets() {
	if s.cloudAcctRepo == nil || s.cloudAcctSvc == nil || s.cloudAcctSvc.assets == nil {
		return
	}
	cutoff := time.Now().Add(-s.staleTTL)
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	if enabled, err := s.cloudAcctSvc.isKillSwitchEnabled(ctx); err != nil {
		s.logger.Error("failed to resolve kill switch before stale cleanup", "error", err)
		return
	} else if enabled {
		s.logger.Info("skipping stale cleanup; kill switch is enabled")
		return
	}

	accounts, _, err := s.cloudAcctRepo.List(ctx, repository.CloudAccountFilter{}, 1, 1000)
	if err != nil {
		s.logger.Error("failed to list accounts for stale cleanup", "error", err)
		return
	}
	for _, acct := range accounts {
		if err := s.cloudAcctSvc.assets.SoftDeleteStale(ctx, acct.ID, cutoff); err != nil {
			s.logger.Error("failed to mark stale assets inactive", "account_id", acct.ID, "error", err)
		}
	}
}
