package application

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"os"
	"os/signal"
	"sync"
	"syscall"
)

var (
	// ErrApplication indicates that error appeared somewhere in application's lifecycle controller.
	ErrApplication = errors.New("application")

	// Version is application's version.
	Version string
	// Branch is a branch name from which application was built.
	Branch string
	// Commit is a commit hash from which application was built.
	Commit string
	// Build is a build number.
	Build string
	// BuildDate is a date on which application was built.
	BuildDate string
)

// Application is an application's lifecycle controlling structure.
type Application struct {
	ctx           context.Context
	shutdownDone  chan struct{}
	cancelFunc    context.CancelFunc
	services      map[string]Service
	dataPath      string
	servicesMutex sync.RWMutex
}

// New creates new application's lifecycle controlling structure.
func New() *Application {
	appl := &Application{}
	appl.initialize()

	return appl
}

// GetContext returns application's global context.
func (a *Application) GetContext() context.Context {
	return a.ctx
}

// GetShutdownDoneChannel returns channel on which lifecycle controller will tell about shutdown completion.
// Should be used only in main()!
func (a *Application) GetShutdownDoneChannel() chan struct{} {
	slog.Debug("Returning shutdown completion channel.")

	return a.shutdownDone
}

func (a *Application) initialize() {
	a.ctx, a.cancelFunc = context.WithCancel(context.Background())
	a.ctx, a.cancelFunc = signal.NotifyContext(a.ctx, os.Interrupt, syscall.SIGTERM)

	a.services = make(map[string]Service)

	a.shutdownDone = make(chan struct{}, 1)
}

// Shutdown stops application and all registered services.
func (a *Application) Shutdown() error {
	a.cancelFunc()

	a.servicesMutex.RLock()
	defer a.servicesMutex.RUnlock()

	for _, service := range a.services {
		if err := service.Shutdown(); err != nil {
			slog.Error("Error appeared when trying to shut down service", "service", service.GetName(), "error", err.Error())
		}
	}

	a.shutdownDone <- struct{}{}

	return nil
}

// Start starts application and all registered services.
func (a *Application) Start() error {
	go a.signalsListener()

	a.servicesMutex.RLock()
	defer a.servicesMutex.RUnlock()

	// First pass - connecting dependencies.
	for _, service := range a.services {
		if err := service.ConnectDependencies(); err != nil {
			return fmt.Errorf("%w: connecting dependencies for service '%s': %w", ErrApplication, service.GetName(), err)
		}
	}

	// Second pass - launching startup tasks.
	for name, service := range a.services {
		slog.Debug("Launching startup tasks for service", "service", name)

		if err := service.LaunchStartupTasks(); err != nil {
			return fmt.Errorf("%w: launching startup tasks for '%s': %w", ErrApplication, service.GetName(), err)
		}
	}

	slog.Debug("Application started.")

	return nil
}
