package localserver

import (
	"context"
	"sync"
	"time"
)

type SquadRequestScheduler struct {
	handlersPublishers                   []chan<- *SquadRequest
	handlersDataChannelDispatchCallbacks []DispatchDataChannelCallBack
}

type SquadRequest struct {
	ReqType string         `json:"reqType"`
	Payload map[string]any `json:"payload"`
	From    string         `json:"from"`
}

type SquadResponse struct {
	Type    string         `json:"type"`
	To      string         `json:"to"`
	From    string         `json:"from"`
	Payload map[string]any `json:"payload"`
}

type SquadRequestHandler interface {
	Init(ctx context.Context, authorizedMembers []string) (err error)
	Subscribe(ctx context.Context, publisher <-chan *SquadRequest) (reqChan chan *SquadRequest, done chan struct{}, errCh chan error)
	handleSquadRequest(ctx context.Context, req *SquadRequest) (err error)
	handleDataChannel(ctx context.Context, dc *DataChannel) (catched bool)
}

func NewSquadRequestScheduler(authorizedMembers []string, handlers ...SquadRequestHandler) (squadRequestScheduler *SquadRequestScheduler, handlerErrCh chan error) {
	squadRequestScheduler = new(SquadRequestScheduler)
	squadRequestScheduler.handlersPublishers = make([]chan<- *SquadRequest, 0)
	squadRequestScheduler.handlersDataChannelDispatchCallbacks = make([]DispatchDataChannelCallBack, 0)
	handlerErrCh = make(chan error)
	reqChans := []chan *SquadRequest{}
	for _, handler := range handlers {
		publisher := make(chan *SquadRequest, 100)
		squadRequestScheduler.handlersPublishers = append(squadRequestScheduler.handlersPublishers, publisher)
		squadRequestScheduler.handlersDataChannelDispatchCallbacks = append(squadRequestScheduler.handlersDataChannelDispatchCallbacks, handler.handleDataChannel)
		reqChan, done, errCh := handler.Subscribe(context.Background(), publisher)
		go func(done <-chan struct{}, errCh <-chan error) {
			for {
				select {
				case <-done:
					return
				case handlerErrCh <- <-errCh:
				}
			}
		}(done, errCh)
		reqChans = append(reqChans, reqChan)
	}

	for _, reqChan := range reqChans {
		go func(reqChan <-chan *SquadRequest) {
			done, errCh := squadRequestScheduler.Schedule(reqChan)
			for {
				select {
				case <-done:
					return
				case <-errCh:
					// logger.Println("from there", e)
				}
			}
		}(reqChan)
	}
	for _, handler := range handlers {
		if ierr := handler.Init(context.Background(), authorizedMembers); ierr != nil {
			// logger.Println(ierr)
		}
		// logger.Println("init done for handler", i)
	}
	return
}

func (zrs *SquadRequestScheduler) Schedule(reqChan <-chan *SquadRequest) (done chan struct{}, errCh chan error) {
	done, errCh = make(chan struct{}), make(chan error)
	go func() {
		for req := range reqChan {
			go func(r *SquadRequest) {
				for _, publisher := range zrs.handlersPublishers {
					publisher <- r
				}
			}(req)
		}
		done <- struct{}{}
	}()
	return
}

func (zrs *SquadRequestScheduler) DispatchDatachannel(ctx context.Context, dc *DataChannel) (catched bool) {
	wg, lock := sync.WaitGroup{}, &sync.Mutex{}
	timeoutCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
	done := make(chan struct{})
	defer cancel()
	for _, dispatchCallback := range zrs.handlersDataChannelDispatchCallbacks {
		wg.Add(1)
		go func(w *sync.WaitGroup, dispatchCallback DispatchDataChannelCallBack) {
			defer w.Done()
			val := dispatchCallback(timeoutCtx, dc)
			if val {
				lock.Lock()
				catched = true
				lock.Unlock()
				return
			}
		}(&wg, dispatchCallback)
	}
	go func() {
		wg.Wait()
		done <- struct{}{}
	}()
	select {
	case <-done:
		return
	case <-timeoutCtx.Done():
		return false
	}
}
