package worker

import (
	"context"
	"fmt"
	"os"
	"sync"
	"time"

	"service/internal/infrastructure/config"
	"service/internal/infrastructure/database"
	"service/internal/interfaces/satusehat"
	"service/internal/worker/satusehat/allergyintolerance"
	"service/internal/worker/satusehat/careplan"
	"service/internal/worker/satusehat/clinicalimpression"
	"service/internal/worker/satusehat/composition"
	"service/internal/worker/satusehat/condition"
	"service/internal/worker/satusehat/diagnosticreport"
	"service/internal/worker/satusehat/encounter"
	"service/internal/worker/satusehat/episodeofcare"
	"service/internal/worker/satusehat/imagingstudy"
	"service/internal/worker/satusehat/immunization"
	"service/internal/worker/satusehat/medication"
	"service/internal/worker/satusehat/medicationdispense"
	"service/internal/worker/satusehat/medicationrequest"
	"service/internal/worker/satusehat/medicationstatement"
	"service/internal/worker/satusehat/observation"
	"service/internal/worker/satusehat/procedure"
	"service/internal/worker/satusehat/questionnaireresponse"
	"service/internal/worker/satusehat/servicerequest"
	"service/internal/worker/satusehat/specimen"
	"service/pkg/logger"
)

// Manager mengelola siklus hidup semua background workers
type Manager struct {
	cfg             *config.Config
	db              database.Service
	satusehatClient satusehat.SatuSehatClient
}

// NewManager membuat instance Manager baru
func NewManager(cfg *config.Config, db database.Service, satusehatClient satusehat.SatuSehatClient) *Manager {
	return &Manager{
		cfg:             cfg,
		db:              db,
		satusehatClient: satusehatClient,
	}
}

// Start menjalankan semua worker terdaftar dalam goroutine terpisah
func (m *Manager) Start(ctx context.Context) {
	logger.Default().Info("Starting background workers...")

	var wg sync.WaitGroup

	// Tentukan Organization ID (Bisa dari Environment Variable atau Config Struct)
	orgID := os.Getenv("SATUSEHAT_ORGANIZATION_ID")
	if orgID == "" {
		logger.Default().Warn("SATUSEHAT_ORGANIZATION_ID is not set in environment variables")
	}

	internalPort := 8080
	if m.cfg != nil && m.cfg.Server.REST.Port != 0 {
		internalPort = m.cfg.Server.REST.Port
	}
	internalBaseURL := fmt.Sprintf("http://localhost:%d/api/v1", internalPort)
	internalToken := os.Getenv("INTERNAL_API_TOKEN")

	// Jobs mendefinisikan urutan dan jeda waktu (delay) boot-up setiap worker.
	// Konfigurasi waktu ini mencegah perebutan resource di awal (thundering herd)
	// dan memastikan resource inti (seperti Encounter) mendapat "head start"
	// sebelum worker anak mencoba merujuk ke data tersebut.
	jobs := []struct {
		name  string
		delay time.Duration
		run   func(context.Context)
	}{
		{
			name:  "Medication (Master)",
			delay: 0, // Master data berjalan seketika
			run: func(c context.Context) {
				medication.NewWorker(medication.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Encounter",
			delay: 5 * time.Second, // Memberikan jeda 5 detik agar Encounter mendapat porsi jalan lebih dulu
			run: func(c context.Context) {
				encounter.NewWorker(encounter.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Condition",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				condition.NewWorker(condition.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Observation",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				observation.NewWorker(observation.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Procedure",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				procedure.NewWorker(procedure.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "AllergyIntolerance",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				allergyintolerance.NewWorker(allergyintolerance.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "ClinicalImpression",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				clinicalimpression.NewWorker(clinicalimpression.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Immunization",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				immunization.NewWorker(immunization.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "ServiceRequest",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				servicerequest.NewWorker(servicerequest.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Specimen",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				specimen.NewWorker(specimen.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "DiagnosticReport",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				diagnosticreport.NewWorker(diagnosticreport.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "ImagingStudy",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				imagingstudy.NewWorker(imagingstudy.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "Composition",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				composition.NewWorker(composition.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "CarePlan",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				careplan.NewWorker(careplan.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "EpisodeOfCare",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				episodeofcare.NewWorker(episodeofcare.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "MedicationRequest",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				medicationrequest.NewWorker(medicationrequest.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "MedicationDispense",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				medicationdispense.NewWorker(medicationdispense.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "MedicationStatement",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				medicationstatement.NewWorker(medicationstatement.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
		{
			name:  "QuestionnaireResponse",
			delay: 2 * time.Second,
			run: func(c context.Context) {
				questionnaireresponse.NewWorker(questionnaireresponse.Config{
					DBManager: m.db, InternalBaseURL: internalBaseURL, InternalToken: internalToken, OrganizationID: orgID,
				}).Run(c)
			},
		},
	}

	// Eksekusi semua background worker secara tertib
	go func() {
		for _, job := range jobs {
			// Jeda startup, dapat di-interrupt oleh Context Cancel (Shutdown System)
			if job.delay > 0 {
				select {
				case <-ctx.Done():
					return
				case <-time.After(job.delay):
				}
			}

			wg.Add(1)
			j := job // copy pointer untuk goroutine loop
			go func() {
				defer wg.Done()
				logger.Default().Info(fmt.Sprintf("[%s WORKER] Started", j.name))
				j.run(ctx)
				logger.Default().Info(fmt.Sprintf("[%s WORKER] Stopped", j.name))
			}()
		}

		// Waiter untuk Graceful Shutdown keseluruhan
		wg.Wait()
		logger.Default().Info("All background workers stopped gracefully")
	}()
}
