package executor

import (
	"context"
	"sync/atomic"
	"time"

	"go.uber.org/zap"
)

// Start launches background schema sync when configured.
func (e *Executor) Start() {
	if e == nil {
		return
	}
	e.ensureInternals()
	if !e.schemaSyncBackground || e.schemaSync == nil {
		return
	}

	e.schemaSyncMu.Lock()
	if e.schemaSyncStop != nil {
		e.schemaSyncMu.Unlock()
		return
	}
	stop := make(chan struct{})
	e.schemaSyncStop = stop
	e.schemaSyncMu.Unlock()

	interval := e.schemaSync.interval()
	if interval <= 0 {
		interval = defaultSchemaSyncInterval
	}

	e.schemaSyncWG.Add(1)
	go func() {
		defer e.schemaSyncWG.Done()
		ticker := time.NewTicker(interval)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				e.runSchemaSync()
			case <-stop:
				return
			}
		}
	}()
}

// Close stops background schema sync work.
func (e *Executor) Close() {
	if e == nil {
		return
	}
	e.schemaSyncMu.Lock()
	stop := e.schemaSyncStop
	if stop != nil {
		close(stop)
		e.schemaSyncStop = nil
	}
	e.schemaSyncMu.Unlock()
	e.schemaSyncWG.Wait()
}

// SetCredentials records the last-seen credentials for background work.
func (e *Executor) SetCredentials(user, password string) {
	if e == nil || user == "" {
		return
	}
	e.credMu.Lock()
	e.credUser = user
	e.credPassword = password
	e.credSet = true
	e.credMu.Unlock()

	e.maybePrecreateCQLMeta(user, password)
}

func (e *Executor) credentials() (string, string, bool) {
	if e == nil {
		return "", "", false
	}
	e.credMu.RLock()
	defer e.credMu.RUnlock()
	if !e.credSet || e.credUser == "" {
		return "", "", false
	}
	return e.credUser, e.credPassword, true
}

func (e *Executor) runSchemaSync() {
	if e == nil || e.schemaSync == nil {
		return
	}
	user, password, ok := e.credentials()
	if !ok {
		return
	}
	e.schemaSync.MaybeSync(context.Background(), user, password)
}

func (e *Executor) maybePrecreateCQLMeta(user, password string) {
	if e == nil || !e.cqlMetaPrecreate || e.ensureCQLMeta == nil || user == "" {
		return
	}
	if atomic.LoadInt32(&e.cqlMetaPrecreateDone) == 1 {
		return
	}
	if !atomic.CompareAndSwapInt32(&e.cqlMetaPrecreateIn, 0, 1) {
		return
	}

	go func() {
		defer atomic.StoreInt32(&e.cqlMetaPrecreateIn, 0)
		if atomic.LoadInt32(&e.cqlMetaPrecreateDone) == 1 {
			return
		}
		if err := e.ensureCQLMeta(context.Background(), user, password); err != nil {
			if e.logger != nil {
				e.logger.Warn("cql_meta precreate failed", zap.Error(err))
			}
			return
		}
		atomic.StoreInt32(&e.cqlMetaPrecreateDone, 1)
	}()
}
