package localserver

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

type ChatRequestScheduler struct {
	handlersPublishers                   []chan<- *ChatRequest
	handlersDataChannelDispatchCallbacks []DispatchDataChannelCallBack
}

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

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

type ChatRequestHandler interface {
	Init(ctx context.Context, initiator, target, initiatorNodeID, targetNodeID string) (err error)
	Subscribe(ctx context.Context, publisher <-chan *ChatRequest) (reqChan chan *ChatRequest, done chan struct{}, errCh chan error)
	handleChatRequest(ctx context.Context, req *ChatRequest) (err error)
	handleDataChannel(ctx context.Context, dc *DataChannel) (catched bool)
}

func NewChatRequestScheduler(initiator, target, initiatorNodeID, targetNodeID string, handlers ...ChatRequestHandler) (chatRequestScheduler *ChatRequestScheduler, handlerErrCh chan error) {
	chatRequestScheduler = new(ChatRequestScheduler)
	chatRequestScheduler.handlersPublishers = make([]chan<- *ChatRequest, 0)
	chatRequestScheduler.handlersDataChannelDispatchCallbacks = make([]DispatchDataChannelCallBack, 0)
	handlerErrCh = make(chan error)
	reqChans := []chan *ChatRequest{}
	for _, handler := range handlers {
		publisher := make(chan *ChatRequest, 100)
		chatRequestScheduler.handlersPublishers = append(chatRequestScheduler.handlersPublishers, publisher)
		chatRequestScheduler.handlersDataChannelDispatchCallbacks = append(chatRequestScheduler.handlersDataChannelDispatchCallbacks, handler.handleDataChannel)
		fmt.Println("this is the fking handler")
		fmt.Println(handler)
		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 *ChatRequest) {
			done, errCh := chatRequestScheduler.Schedule(reqChan)
			for {
				select {
				case <-done:
					return
				case <-errCh:
					// logger.Println(e)
				}
			}
		}(reqChan)
	}
	for _, handler := range handlers {
		if ierr := handler.Init(context.Background(), initiator, target, initiatorNodeID, targetNodeID); ierr != nil {
			// logger.Println(ierr)
		}
	}
	return
}

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

func (crs *ChatRequestScheduler) DispatchDataChannel(ctx context.Context, dc *DataChannel) (catched bool) {
	wg, lock := sync.WaitGroup{}, &sync.Mutex{}
	timeoutCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
	defer cancel()
	done := make(chan struct{})
	for _, dispatchCallback := range crs.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
	}
}
