package service

import (
	"context"
	"fmt"
	"sync"

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

type RuntimeStatus struct {
	WorkersRunning     bool   `json:"workers_running"`
	SchedulerRunning   bool   `json:"scheduler_running"`
	RuntimeConfigured  bool   `json:"runtime_configured"`
	QueueAvailable     bool   `json:"queue_available"`
	QueueDegraded      bool   `json:"queue_degraded"`
	QueueError         string `json:"queue_error,omitempty"`
	KillSwitchEnabled  bool   `json:"kill_switch_enabled"`
	WorkerLimit        int    `json:"worker_limit"`
	RuntimePressureCap int    `json:"runtime_pressure_cap"`
	ActiveJobCount     int64  `json:"active_job_count"`
	CoordinationMode   string `json:"coordination_mode,omitempty"`
	PressureState      string `json:"pressure_state,omitempty"`
	RuntimeLimitSource string `json:"runtime_limit_source,omitempty"`
}

type CoordinationCap struct {
	Config caasmmodel.ThrottlingConfig
	Source string
}

// RuntimeControl manages the lifecycle of the CAASM worker pool and scheduler.
// Startup is user-driven (via API/UI) rather than automatic on application boot.
type RuntimeControl struct {
	jobQueue  queue.JobQueueInterface
	executor  queue.SyncExecutor
	logger    *logger.Logger
	scheduler *CollectorScheduler
	jobs      repository.SyncJobRepository

	workerCount        int
	capProvider        func(context.Context) (CoordinationCap, error)
	killSwitchProvider func(context.Context) (bool, error)
	admission          queue.AdmissionController
	reporter           queue.JobEventReporter
	queueDegradedErr   error

	mu               sync.Mutex
	workerPool       *queue.WorkerPool
	workersRunning   bool
	schedulerRunning bool
}

func NewRuntimeControl(
	jobQueue queue.JobQueueInterface,
	workerCount int,
	executor queue.SyncExecutor,
	scheduler *CollectorScheduler,
	jobs repository.SyncJobRepository,
	capProvider func(context.Context) (CoordinationCap, error),
	log *logger.Logger,
) *RuntimeControl {
	return &RuntimeControl{
		jobQueue:    jobQueue,
		executor:    executor,
		workerCount: workerCount,
		scheduler:   scheduler,
		jobs:        jobs,
		capProvider: capProvider,
		logger:      log,
	}
}

func (rc *RuntimeControl) SetAdmissionController(controller queue.AdmissionController) {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	rc.admission = controller
	if rc.workerPool != nil {
		rc.workerPool.SetAdmissionController(controller)
	}
}

func (rc *RuntimeControl) SetKillSwitchProvider(provider func(context.Context) (bool, error)) {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	rc.killSwitchProvider = provider
}

func (rc *RuntimeControl) SetEventReporter(reporter queue.JobEventReporter) {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	rc.reporter = reporter
	if rc.workerPool != nil {
		rc.workerPool.SetEventReporter(reporter)
	}
}

func (rc *RuntimeControl) SetQueueDegraded(err error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	rc.queueDegradedErr = err
}

// Start spins up the worker pool and scheduler. It is idempotent and restartable
// after Stop has been called.
func (rc *RuntimeControl) Start() (RuntimeStatus, error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.jobQueue == nil && rc.scheduler == nil {
		return rc.statusLocked(), fmt.Errorf("runtime components are not configured")
	}
	killSwitchEnabled, err := rc.killSwitchStateLocked()
	if err != nil {
		return rc.statusLocked(), fmt.Errorf("resolve kill switch state: %w", err)
	}
	if killSwitchEnabled {
		return rc.statusLocked(), &KillSwitchError{Message: "CAASM kill switch is enabled; disable it before starting the runtime"}
	}

	rc.ensureWorkerPoolLocked()
	rc.startWorkersLocked()
	rc.startSchedulerLocked()

	if rc.logger != nil {
		rc.logger.Info("caasm runtime started",
			"workers_running", rc.workersRunning,
			"scheduler_running", rc.schedulerRunning,
		)
	}

	return rc.statusLocked(), nil
}

// StartScheduler enables scheduled sync execution without changing the worker pool state.
func (rc *RuntimeControl) StartScheduler() (RuntimeStatus, error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.scheduler == nil {
		return rc.statusLocked(), fmt.Errorf("scheduler is not configured")
	}
	killSwitchEnabled, err := rc.killSwitchStateLocked()
	if err != nil {
		return rc.statusLocked(), fmt.Errorf("resolve kill switch state: %w", err)
	}
	if killSwitchEnabled {
		return rc.statusLocked(), &KillSwitchError{Message: "CAASM kill switch is enabled; disable it before starting the scheduler"}
	}

	rc.startSchedulerLocked()

	if rc.logger != nil {
		rc.logger.Info("caasm scheduler started", "scheduler_running", rc.schedulerRunning)
	}

	return rc.statusLocked(), nil
}

// StartWorkers enables queued job processing without changing scheduler state.
func (rc *RuntimeControl) StartWorkers() (RuntimeStatus, error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.jobQueue == nil || rc.executor == nil {
		return rc.statusLocked(), fmt.Errorf("worker pool is not configured")
	}
	killSwitchEnabled, err := rc.killSwitchStateLocked()
	if err != nil {
		return rc.statusLocked(), fmt.Errorf("resolve kill switch state: %w", err)
	}
	if killSwitchEnabled {
		return rc.statusLocked(), &KillSwitchError{Message: "CAASM kill switch is enabled; disable it before starting workers"}
	}

	rc.ensureWorkerPoolLocked()
	rc.startWorkersLocked()

	if rc.logger != nil {
		rc.logger.Info("caasm workers started", "workers_running", rc.workersRunning)
	}

	return rc.statusLocked(), nil
}

// Stop halts the worker pool and scheduler. Workers are re-initialized so a
// subsequent Start call can restart processing cleanly.
func (rc *RuntimeControl) Stop() RuntimeStatus {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	rc.stopWorkersLocked()
	rc.stopSchedulerLocked()

	if rc.logger != nil {
		rc.logger.Info("caasm runtime stopped")
	}

	return rc.statusLocked()
}

// StopWorkers disables queued job processing while leaving scheduler state unchanged.
func (rc *RuntimeControl) StopWorkers() RuntimeStatus {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	rc.stopWorkersLocked()

	if rc.logger != nil {
		rc.logger.Info("caasm workers stopped", "workers_running", rc.workersRunning)
	}

	return rc.statusLocked()
}

// StopScheduler disables scheduled sync execution while leaving the worker pool unchanged.
func (rc *RuntimeControl) StopScheduler() RuntimeStatus {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	rc.stopSchedulerLocked()

	if rc.logger != nil {
		rc.logger.Info("caasm scheduler stopped", "scheduler_running", rc.schedulerRunning)
	}

	return rc.statusLocked()
}

func (rc *RuntimeControl) Status() RuntimeStatus {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	return rc.statusLocked()
}

func (rc *RuntimeControl) WorkerMetrics() *queue.QueueMetrics {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.workerPool == nil {
		return nil
	}
	return rc.workerPool.GetMetrics()
}

func (rc *RuntimeControl) CancelSyncJob(syncJobID uint64) bool {
	rc.mu.Lock()
	defer rc.mu.Unlock()
	if rc.workerPool == nil || syncJobID == 0 {
		return false
	}
	return rc.workerPool.CancelSyncJob(syncJobID)
}

func (rc *RuntimeControl) ensureWorkerPoolLocked() {
	effectiveWorkers := rc.effectiveWorkerLimitLocked()
	if rc.jobQueue == nil || rc.executor == nil {
		rc.workerPool = nil
		return
	}
	if rc.workerPool == nil || (!rc.workersRunning && rc.workerPool.WorkerCount() != effectiveWorkers) {
		rc.workerPool = queue.NewWorkerPool(rc.jobQueue, effectiveWorkers, rc.logger, rc.executor)
		rc.workerPool.SetAdmissionController(rc.admission)
		rc.workerPool.SetEventReporter(rc.reporter)
	}
}

func (rc *RuntimeControl) startWorkersLocked() {
	if rc.workerPool != nil && !rc.workersRunning {
		rc.workerPool.Start()
		rc.workersRunning = true
	}
}

func (rc *RuntimeControl) stopWorkersLocked() {
	if rc.workerPool != nil && rc.workersRunning {
		rc.workerPool.Stop()
		rc.workersRunning = false
	}
}

func (rc *RuntimeControl) startSchedulerLocked() {
	if rc.scheduler != nil && !rc.schedulerRunning {
		rc.scheduler.Start()
		rc.schedulerRunning = true
	}
}

func (rc *RuntimeControl) stopSchedulerLocked() {
	if rc.scheduler != nil && rc.schedulerRunning {
		rc.scheduler.Stop()
		rc.schedulerRunning = false
	}
}

func (rc *RuntimeControl) statusLocked() RuntimeStatus {
	cap, source := rc.runtimeCapLocked()
	activeJobs := int64(0)
	if rc.jobs != nil {
		if count, err := rc.jobs.CountActiveJobs(context.Background()); err == nil {
			activeJobs = count
		}
	}
	pressureState := "idle"
	if cap > 0 {
		switch {
		case activeJobs >= int64(cap):
			pressureState = "at_capacity"
		case activeJobs > 0:
			pressureState = "active"
		}
	}
	queueErr := ""
	if rc.queueDegradedErr != nil {
		queueErr = rc.queueDegradedErr.Error()
	}
	return RuntimeStatus{
		WorkersRunning:     rc.workersRunning,
		SchedulerRunning:   rc.schedulerRunning,
		RuntimeConfigured:  rc.jobQueue != nil || rc.scheduler != nil,
		QueueAvailable:     rc.jobQueue != nil,
		QueueDegraded:      rc.queueDegradedErr != nil,
		QueueError:         queueErr,
		KillSwitchEnabled:  rc.killSwitchEnabledLocked(),
		WorkerLimit:        rc.effectiveWorkerLimitLocked(),
		RuntimePressureCap: cap,
		ActiveJobCount:     activeJobs,
		CoordinationMode:   "db_backed",
		PressureState:      pressureState,
		RuntimeLimitSource: source,
	}
}

func (rc *RuntimeControl) killSwitchEnabledLocked() bool {
	enabled, err := rc.killSwitchStateLocked()
	if err != nil {
		return false
	}
	return enabled
}

func (rc *RuntimeControl) killSwitchStateLocked() (bool, error) {
	if rc.killSwitchProvider == nil {
		return false, nil
	}
	enabled, err := rc.killSwitchProvider(context.Background())
	if err != nil {
		return false, err
	}
	return enabled, nil
}

func (rc *RuntimeControl) runtimeCapLocked() (int, string) {
	cap := rc.workerCount
	source := "worker_pool_default"
	if rc.capProvider != nil {
		if result, err := rc.capProvider(context.Background()); err == nil {
			if result.Config.MaxConcurrency > 0 {
				cap = result.Config.MaxConcurrency
			}
			if result.Source != "" {
				source = result.Source
			}
		}
	}
	if cap <= 0 {
		cap = 1
	}
	return cap, source
}

func (rc *RuntimeControl) effectiveWorkerLimitLocked() int {
	cap, _ := rc.runtimeCapLocked()
	if rc.workerCount > 0 && rc.workerCount < cap {
		return rc.workerCount
	}
	return cap
}
