package auth

import (
	"context"
	"fmt"
	"sync"

	"git.loafle.net/commons/configuration-go"
	cdr "git.loafle.net/commons/di-go/registry"
	logging "git.loafle.net/commons/logging-go"
	occn "git.loafle.net/overflow/commons-go/config/noauthprobe"
	occi "git.loafle.net/overflow/commons-go/core/interfaces"

	"git.loafle.net/overflow/probe/auth/annotation"
	"git.loafle.net/overflow/probe/auth/service"
	"git.loafle.net/overflow/probe/config"
)

type Authenticator struct {
	authConfig occn.Auth

	services []interface{}

	endChan  chan error
	stopChan chan struct{}
	stopWg   sync.WaitGroup
}

func (a *Authenticator) EndableStart() (<-chan error, error) {
	if a.stopChan != nil {
		return nil, fmt.Errorf("already running. Stop it before starting it again")
	}

	conf := configuration.New()

	if configuration.Exists(config.NoAuthProbeConfigFilePath()) {
		if err := conf.Load(&a.authConfig, config.NoAuthProbeConfigFilePath()); nil != err {
			logging.Logger().Errorf("%s %v", err)
			return nil, fmt.Errorf("loading of auth config file[%s] failed", config.NoAuthProbeConfigFilePath())
		}
	}

	if nil != a.authConfig.DeniedDate {
		return nil, fmt.Errorf("cannot start because this probe have been denied from overFlow at %s", a.authConfig.DeniedDate.String())
	}

	cdr.RegisterResource("AuthConfig", &a.authConfig)
	authDoneChan := make(chan error)
	cdr.RegisterResource("AuthDoneChan", authDoneChan)

	services, err := cdr.GetInstancesByAnnotationType(annotation.AuthRPCServiceAnnotationType)
	if nil != err {
		return nil, err
	}
	a.services = services

	if err := occi.ExecServices(a.services, occi.ServiceMethodInit, service.OrderedServices, false); nil != err {
		return nil, err
	}

	a.endChan = make(chan error)
	a.stopChan = make(chan struct{})

	a.stopWg.Add(1)
	go a.handleAuthenticator(authDoneChan)

	return a.endChan, nil
}

func (a *Authenticator) Stop(ctx context.Context) error {
	if a.stopChan == nil {
		return nil
	}
	close(a.stopChan)
	a.stopWg.Wait()

	occi.ExecServices(a.services, occi.ServiceMethodDestroy, service.OrderedServices, true)

	a.stopChan = nil

	close(a.endChan)

	return nil
}

func (a *Authenticator) handleAuthenticator(authDoneChan chan error) {
	var err error
	defer func() {
		a.stopWg.Done()
		a.endChan <- err
	}()

	err = occi.ExecServices(a.services, occi.ServiceMethodStart, service.OrderedServices, false)
	if nil != err {
		return
	}

LOOP:
	for {
		select {
		case err = <-authDoneChan:
			break LOOP
		case <-a.stopChan:
			break LOOP
		}
	}

	occi.ExecServices(a.services, occi.ServiceMethodStop, service.OrderedServices, true)
}
