package localserver

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

type DispatchDataChannelCallBack = func(ctx context.Context, dc *DataChannel) (catched bool)

type ZoneRequestScheduler struct {
	handlersPublishers                   []chan<- *ZoneRequest
	handlersDataChannelDispatchCallbacks []DispatchDataChannelCallBack
}

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

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

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

func verifyFieldsString(payload map[string]interface{}, fields ...string) (err error) {
	for _, field := range fields {
		if _, ok := payload[field]; !ok {
			err = fmt.Errorf("no field %s in payload", field)
			return
		} else if _, ok := payload[field].(string); !ok {
			err = fmt.Errorf("field %s in payload is not a string", field)
			return
		}
	}
	return
}

func verifyFieldsBool(payload map[string]interface{}, fields ...string) (err error) {
	for _, field := range fields {
		if _, ok := payload[field]; !ok {
			err = fmt.Errorf("no field %s in payload", field)
			return
		} else if _, ok := payload[field].(bool); !ok {
			err = fmt.Errorf("field %s in payload is not a bool", field)
			return
		}
	}
	return
}

func verifyFieldsFloat64(payload map[string]interface{}, fields ...string) (err error) {
	for _, field := range fields {
		if _, ok := payload[field]; !ok {
			err = fmt.Errorf("no field %s in payload", field)
			return
		} else if _, ok := payload[field].(float64); !ok {
			err = fmt.Errorf("field %s in payload is not a float64", field)
			return
		}
	}
	return
}

func verifyFieldsSliceInterface(payload map[string]interface{}, fields ...string) (err error) {
	for _, field := range fields {
		if _, ok := payload[field]; !ok {
			err = fmt.Errorf("no field %s in payload", field)
			return
		} else if _, ok := payload[field].([]any); !ok {
			err = fmt.Errorf("field %s in payload is not a []interface{}", field)
			return
		}
	}
	return
}

func NewZoneRequestScheduler(authorizedMembers []string, handlers ...ZoneRequestHandler) (zoneRequestScheduler *ZoneRequestScheduler, handlerErrCh chan error) {
	zoneRequestScheduler = new(ZoneRequestScheduler)
	zoneRequestScheduler.handlersPublishers = make([]chan<- *ZoneRequest, 0)
	zoneRequestScheduler.handlersDataChannelDispatchCallbacks = make([]DispatchDataChannelCallBack, 0)
	handlerErrCh = make(chan error)
	reqChans := []chan *ZoneRequest{}
	for _, handler := range handlers {
		publisher := make(chan *ZoneRequest,100)
		zoneRequestScheduler.handlersPublishers = append(zoneRequestScheduler.handlersPublishers, publisher)
		zoneRequestScheduler.handlersDataChannelDispatchCallbacks = append(zoneRequestScheduler.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 *ZoneRequest) {
			done, errCh := zoneRequestScheduler.Schedule(reqChan)
			for {
				select {
				case <-done:
					return
				case e := <-errCh:
					logger.Println("from there", e)
				}
			}
		}(reqChan)
	}
	for i, 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 *ZoneRequestScheduler) Schedule(reqChan <-chan *ZoneRequest) (done chan struct{}, errCh chan error) {
	done, errCh = make(chan struct{}), make(chan error)
	go func() {
		for req := range reqChan {
			go func(r *ZoneRequest) {
				for _, publisher := range zrs.handlersPublishers {
					go func(p chan<- *ZoneRequest) {
						p <- r
					}(publisher)
				}
			}(req)
		}
		done <- struct{}{}
	}()
	return
}

func (zrs *ZoneRequestScheduler) 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
	}
}
