package main

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/logger"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/broker"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/config"
	httprouter "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/http"
)

var (
	version string
	commit  string
	date    string
)

func main() {
	cfg := config.MustLoad(version, commit, date)

	log, err := logger.New(cfg.Env)
	if err != nil {
		panic(err)
	}

	log.Info("Starting App", slog.String("version", version), slog.String("commit", commit), slog.String("build_date", date))

	brok := setupBroker(log, &cfg.Broker)
	defer func(brok *broker.RabbitMQBroker) {
		if err := brok.Disconnect(); err != nil {
			log.Error("Error disconnecting from broker")
		}
	}(brok)

	server, err := setupServer(log, cfg)
	if err != nil {
		panic(fmt.Sprintf("can not start http server: %v", err))
	}

	serverErrors := make(chan error, 1)

	go func() {
		log.Info("starting server", slog.Int("port", cfg.Port))
		serverErrors <- server.ListenAndServe()
	}()

	shutdownSignal := make(chan os.Signal, 1)
	signal.Notify(shutdownSignal, os.Interrupt, syscall.SIGTERM)

	select {
	case err := <-serverErrors:
		log.Error("Error starting server: %v\n", slog.Any("err", err))
	case sig := <-shutdownSignal:
		log.Info("received signal. shutting down server", slog.String("signal", sig.String()))
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	log.Info("shutting down server...")

	if err := server.Shutdown(ctx); err != nil {
		log.Error(fmt.Sprintf("Error shutting down server: %v\n", err))
	}

	log.Info("server gracefully stopped")
}

func setupServer(logger *slog.Logger, cfg *config.Config) (*http.Server, error) {
	router, err := httprouter.New(logger, cfg)
	if err != nil {
		return nil, err
	}

	httprouter.SetupRouter(router, cfg.Build)

	return &http.Server{
		Addr:              fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
		Handler:           router.Mux,
		ReadHeaderTimeout: 15 * time.Second,
	}, nil
}

func doWithRetry(function func() error, serviceName string, logger *slog.Logger) {
	if err := function(); err != nil {
		logger.Error(fmt.Sprintf("Error connecting to %s: %v", serviceName, err),
			slog.String("error", err.Error()))

		go func() {
			ticker := time.NewTicker(constants.DefaultRetryTimeout)
			defer ticker.Stop()

			for range ticker.C {
				if err := function(); err != nil {
					logger.Error(fmt.Sprintf("Error connecting to %s: %v", serviceName, err),
						slog.String("error", err.Error()))
				} else {
					logger.Info(fmt.Sprintf("Successfully connected to %s service", serviceName))
					break
				}
			}
		}()
	} else {
		logger.Info(fmt.Sprintf("Successfully connected to %s service", serviceName))
	}
}

func setupBroker(logger *slog.Logger, cfg *config.Broker) *broker.RabbitMQBroker {
	brok := broker.NewRabbitMQBroker(cfg)

	doWithRetry(func() error {
		return brok.Connect()
	}, "Broker", logger)

	return brok
}
