package workerpool

import (
	"container/heap"
	"context"
	"fmt"
	"sync"
	"time"
)

type Gorev func() error

type GorevSonucu struct {
	GorevID   string
	GrupID    string
	Basarili  bool
	Hata      error
	Ilerleme  float64
	GecenSure time.Duration
}

type IsciHavuzu struct {
	isciSayisi    int
	gorevler      OncelikKuyrugu
	sonuclar      chan GorevSonucu
	eventler      chan interface{}
	gruplar       map[string]*Grup
	kilit         sync.RWMutex
	beklemeGrubu  sync.WaitGroup
	zamanAsimlari map[string]time.Duration
}

type GorevYapisi struct {
	GorevID       string
	GrupID        string
	Gorev         Gorev
	Oncelik       int
	Bagimliliklar []string
	Index         int // Oncelik kuyruğu için kullanılır
}

type Grup struct {
	GrupID   string
	Gorevler []*GorevYapisi
	Durum    string
}

type OncelikKuyrugu struct {
	gorevler []*GorevYapisi
	fifo     bool // true ise FIFO, false ise LIFO
}

func (pq *OncelikKuyrugu) Len() int {
	return len(pq.gorevler)
}

func (pq *OncelikKuyrugu) Less(i, j int) bool {
	// Return true if gorev i has a higher priority (oncelik) than gorev j
	// If FIFO, compare based on priority; if LIFO, you can reverse the comparison
	if pq.fifo {
		return pq.gorevler[i].Oncelik < pq.gorevler[j].Oncelik
	}
	return pq.gorevler[i].Oncelik > pq.gorevler[j].Oncelik
}

func (pq *OncelikKuyrugu) Swap(i, j int) {
	pq.gorevler[i], pq.gorevler[j] = pq.gorevler[j], pq.gorevler[i]
}

func (pq *OncelikKuyrugu) Uzunluk() int { return len(pq.gorevler) }

func (pq *OncelikKuyrugu) Push(gorev any) {
	pq.gorevler = append(pq.gorevler, gorev.(*GorevYapisi))
}

func (pq *OncelikKuyrugu) Ekle(gorev *GorevYapisi) {
	pq.gorevler = append(pq.gorevler, gorev)
}

func (pq *OncelikKuyrugu) Pop() any {
	if len(pq.gorevler) == 0 {
		return nil
	}
	var gorev *GorevYapisi
	if pq.fifo {
		// FIFO: İlk eklenen görevi çıkar
		gorev = pq.gorevler[0]
		pq.gorevler = pq.gorevler[1:]
	} else {
		// LIFO: Son eklenen görevi çıkar
		gorev = pq.gorevler[len(pq.gorevler)-1]
		pq.gorevler = pq.gorevler[:len(pq.gorevler)-1]
	}
	return gorev
}

func (pq *OncelikKuyrugu) Cikar() *GorevYapisi {
	if len(pq.gorevler) == 0 {
		return nil
	}
	var gorev *GorevYapisi
	if pq.fifo {
		// FIFO: İlk eklenen görevi çıkar
		gorev = pq.gorevler[0]
		pq.gorevler = pq.gorevler[1:]
	} else {
		// LIFO: Son eklenen görevi çıkar
		gorev = pq.gorevler[len(pq.gorevler)-1]
		pq.gorevler = pq.gorevler[:len(pq.gorevler)-1]
	}
	return gorev
}

func YeniOncelikKuyrugu(fifo bool) *OncelikKuyrugu {
	return &OncelikKuyrugu{
		gorevler: make([]*GorevYapisi, 0),
		fifo:     fifo,
	}
}

func YeniIsciHavuzu(isciSayisi int, fifo bool) *IsciHavuzu {
	return &IsciHavuzu{
		isciSayisi:    isciSayisi,
		gorevler:      *YeniOncelikKuyrugu(fifo),
		sonuclar:      make(chan GorevSonucu, 100),
		eventler:      make(chan interface{}, 100),
		gruplar:       make(map[string]*Grup),
		zamanAsimlari: make(map[string]time.Duration),
	}
}
func (ih *IsciHavuzu) Baslat() {
	for i := 0; i < ih.isciSayisi; i++ {
		go ih.isci()
	}
}

func (ih *IsciHavuzu) isci() {
	for {
		ih.kilit.Lock()
		if ih.gorevler.Uzunluk() == 0 {
			ih.kilit.Unlock()
			time.Sleep(10 * time.Millisecond)
			continue
		}
		gorev := heap.Pop(&ih.gorevler).(*GorevYapisi)
		ih.kilit.Unlock()

		if !ih.bagimlilikKontrol(gorev) {
			ih.eventler <- fmt.Sprintf("Görev %s bağımlılıkları karşılanmadı", gorev.GorevID)
			continue
		}

		baslangic := time.Now()
		ctx, iptal := context.WithTimeout(context.Background(), ih.zamanAsimlari[gorev.GorevID])
		tamamlandi := make(chan error)
		go func() {
			tamamlandi <- gorev.Gorev()
		}()

		var hata error
		select {
		case <-ctx.Done():
			hata = ctx.Err()
		case hata = <-tamamlandi:
		}
		iptal()

		sonuc := GorevSonucu{
			GorevID:   gorev.GorevID,
			GrupID:    gorev.GrupID,
			Basarili:  hata == nil,
			Hata:      hata,
			GecenSure: time.Since(baslangic),
		}
		ih.sonuclar <- sonuc
		ih.eventler <- sonuc
		ih.beklemeGrubu.Done()
	}
}

func (ih *IsciHavuzu) GorevEkle(grupID, gorevID string, gorev Gorev, oncelik int, zamanAsimi time.Duration, bagimliliklar []string) {
	ih.kilit.Lock()
	defer ih.kilit.Unlock()
	gorevYapisi := &GorevYapisi{
		GorevID:       gorevID,
		GrupID:        grupID,
		Gorev:         gorev,
		Oncelik:       oncelik,
		Bagimliliklar: bagimliliklar,
	}
	if _, varMi := ih.gruplar[grupID]; !varMi {
		ih.gruplar[grupID] = &Grup{GrupID: grupID, Durum: "beklemede"}
	}
	ih.gruplar[grupID].Gorevler = append(ih.gruplar[grupID].Gorevler, gorevYapisi)
	ih.zamanAsimlari[gorevID] = zamanAsimi
	ih.beklemeGrubu.Add(1)
	heap.Push(&ih.gorevler, gorevYapisi)
	ih.eventler <- fmt.Sprintf("Görev %s Grup %s'ye eklendi", gorevID, grupID)
}

func (ih *IsciHavuzu) Bekle() {
	ih.beklemeGrubu.Wait()
	close(ih.sonuclar)
	close(ih.eventler)
}

func (ih *IsciHavuzu) Izle(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		case sonuc := <-ih.sonuclar:
			fmt.Printf("Görev %s Grup %s içinde tamamlandı. Başarılı: %v, Hata: %v, Geçen Süre: %v\n", sonuc.GorevID, sonuc.GrupID, sonuc.Basarili, sonuc.Hata, sonuc.GecenSure)
		case event := <-ih.eventler:
			fmt.Printf("Etkinlik: %v\n", event)
		}
	}
}

func (ih *IsciHavuzu) GrupIptal(grupID string) {
	ih.kilit.Lock()
	defer ih.kilit.Unlock()
	if grup, varMi := ih.gruplar[grupID]; varMi {
		grup.Durum = "iptal edildi"
		for _, gorev := range grup.Gorevler {
			// Çalışan görevlerin iptal mantığı buraya eklenebilir
			ih.eventler <- fmt.Sprintf("Görev %s Grup %s içinde iptal edildi", gorev.GorevID, grupID)
		}
		ih.eventler <- fmt.Sprintf("Grup %s iptal edildi", grupID)
	}
}

func (ih *IsciHavuzu) bagimlilikKontrol(gorev *GorevYapisi) bool {
	for _, bagimli := range gorev.Bagimliliklar {
		if _, varMi := ih.zamanAsimlari[bagimli]; !varMi {
			return false
		}
	}
	return true
}

func (ih *IsciHavuzu) IsciSayisiAyarla(yeniSayi int) {
	ih.isciSayisi = yeniSayi
	ih.eventler <- fmt.Sprintf("İşçi sayısı %d olarak ayarlandı", yeniSayi)
}

func (ih *IsciHavuzu) GrupIlerleme(grupID string) float64 {
	ih.kilit.RLock()
	defer ih.kilit.RUnlock()
	if grup, varMi := ih.gruplar[grupID]; varMi {
		tamamlanan := 0
		for _, gorev := range grup.Gorevler {
			if gorev != nil {
				tamamlanan++
			}
		}
		ilerleme := float64(tamamlanan) / float64(len(grup.Gorevler)) * 100
		ih.eventler <- fmt.Sprintf("Grup %s ilerlemesi güncellendi: %.2f%%", grupID, ilerleme)
		return ilerleme
	}
	return 0
}

func main() {
	// İşçi havuzu oluşturuluyor, 3 işçi ile FIFO (ilk giren ilk çıkar) düzeninde çalışacak şekilde.
	isciHavuzu := YeniIsciHavuzu(3, true)

	// Havuz başlatılıyor
	isciHavuzu.Baslat()

	// Görevlerin eklenmesi
	isciHavuzu.GorevEkle("grup1", "gorev1", func() error {
		time.Sleep(2 * time.Second) // Görev 2 saniye sürecek
		fmt.Println("Görev 1 tamamlandı.")
		return nil
	}, 1, 5*time.Second, nil)

	isciHavuzu.GorevEkle("grup1", "gorev2", func() error {
		time.Sleep(1 * time.Second) // Görev 1 saniye sürecek
		fmt.Println("Görev 2 tamamlandı.")
		return nil
	}, 2, 5*time.Second, nil)

	isciHavuzu.GorevEkle("grup1", "gorev3", func() error {
		time.Sleep(3 * time.Second) // Görev 3 saniye sürecek
		fmt.Println("Görev 3 tamamlandı.")
		return nil
	}, 3, 5*time.Second, nil)

	// Bağımlılık eklenmesi (örneğin, gorev3, gorev2'yi bekleyecek)
	isciHavuzu.GorevEkle("grup1", "gorev4", func() error {
		time.Sleep(1 * time.Second) // 1 saniye sürecek
		fmt.Println("Görev 4 tamamlandı.")
		return nil
	}, 4, 5*time.Second, []string{"gorev2"})

	// Grup ilerlemesi kontrolü
	ilerleme := isciHavuzu.GrupIlerleme("grup1")
	fmt.Printf("Grup 1 ilerleme durumu: %.2f%%\n", ilerleme)

	// İşçi havuzunun tamamlanmasını bekliyoruz
	isciHavuzu.Bekle()

	// İşçi havuzunda yapılan etkinliklerin izlenmesi
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	go isciHavuzu.Izle(ctx)

	// Grup iptali
	isciHavuzu.GrupIptal("grup1")

	// İşçi sayısının dinamik olarak değiştirilmesi
	isciHavuzu.IsciSayisiAyarla(5)
}()
