package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/tidbcloud/devops/migrate"
	"github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/deps"
	"github.com/tidbcloud/devops/pkg/infra/eventbus"
	"github.com/tidbcloud/devops/pkg/infra/log"
	"github.com/tidbcloud/devops/pkg/service"

	"github.com/robfig/cron/v3"
)

func run(ctx context.Context, deps *deps.Deps) error {
	var err error
	exitCh := make(chan error)
	logger := log.Logger.WithName("Run")

	logger.Info("start to run devops server", "env", consts.GetEnv(), "partition", consts.GetPartition())

	// migrate db
	logger.Info("start to migrate db")
	err = deps.Container.Invoke(migrate.RunDBMigrate)
	if err != nil {
		return fmt.Errorf("failed to migrate db: %s", err)
	}
	logger.Info("end to migrate db")

	// run cron
	logger.Info("start to run cron")
	err = deps.Container.Invoke(func(c *cron.Cron) {
		c.Start()
	})
	if err != nil {
		return fmt.Errorf("failed to run cron: %s", err)
	}

	// run services
	logger.Info("start to run services")
	err = deps.Container.Invoke(func(m *service.Manager) error {
		go func() {
			aerr := m.RunServices()
			if aerr != nil {
				exitCh <- fmt.Errorf("server manager run failed: %w ", aerr)
			}
		}()
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to run services: %s", err)
	}
	logger.Info("end to run services")

	// run eventbus
	//
	// NOTE: eventbus must be run after services.
	// Services subscribe events in `new` function, and subscription MUST be called before
	// eventbus.Run(), so we must make sure all services are constructed and
	// register before running eventbus.
	logger.Info("start to run eventbus")
	err = deps.Container.Invoke(func(e *eventbus.EventBus) error {
		return e.Run()
	})
	if err != nil {
		return fmt.Errorf("failed to run eventbus: %s", err)
	}

	logger.Info("start to run grpc server")
	err = deps.Container.Invoke(func(s *controller.GrpcServer) error {
		go func() {
			aerr := s.Run()
			if aerr != nil {
				exitCh <- fmt.Errorf("grpc server run failed: %w ", aerr)
			}
		}()
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to run grpc server: %s", err)
	}

	// run api server
	logger.Info("start to run api server")
	err = deps.Container.Invoke(func(s *controller.APIServer) error {
		go func() {
			aerr := s.Run()
			if aerr != nil {
				exitCh <- fmt.Errorf("api server manager run failed: %w ", aerr)
			}
		}()
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to run api server: %s", err)
	}

	// block until shutdown or error
	exitErrs := []error{}
	select {
	case <-ctx.Done():
		log.Logger.Info("exit due to receiving a signal", "signal", context.Cause(ctx).Error())
	case err = <-exitCh:
		log.Logger.Error(err, "exit due to receiving an error", "error", err)
		exitErrs = append(exitErrs, err)
	}

	// stop grpc server
	logger.Info("stopping grpc server")
	if err := deps.Container.Invoke(func(s *controller.GrpcServer) error {
		return s.Shutdown(ctx)
	}); err != nil {
		exitErrs = append(exitErrs, err)
	}
	logger.Info("stopped grpc server")

	// stop api server
	logger.Info("stopping api server")
	if err := deps.Container.Invoke(func(s *controller.APIServer) error {
		return s.Shutdown(ctx)
	}); err != nil {
		exitErrs = append(exitErrs, err)
	}
	logger.Info("stopped api server")

	// stop services
	logger.Info("stopping all services")
	if err := deps.Container.Invoke(func(m *service.Manager) {
		m.StopServices()
	}); err != nil {
		exitErrs = append(exitErrs, err)
	}
	logger.Info("stopped all services")

	// stop eventbus
	logger.Info("stopping eventbus")
	if err := deps.Container.Invoke(func(e *eventbus.EventBus) {
		e.Close()
	}); err != nil {
		exitErrs = append(exitErrs, err)
	}

	// stop cron
	logger.Info("stopping cron")
	if err := deps.Container.Invoke(func(c *cron.Cron) {
		c.Stop()
	}); err != nil {
		exitErrs = append(exitErrs, err)
	}
	logger.Info("stopped cron")

	return errors.Join(exitErrs...)
}
