package main

import (
	"os"
	"os/signal"
	"syscall"

	"git.intra.yksa.space/gsn/predictor/internal/jobs/grib/updater"
	"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
	"git.intra.yksa.space/gsn/predictor/internal/pkg/grib"
	"git.intra.yksa.space/gsn/predictor/internal/service"
	"git.intra.yksa.space/gsn/predictor/internal/transport/rest"
	"git.intra.yksa.space/gsn/predictor/internal/transport/rest/handler"
	"git.intra.yksa.space/gsn/predictor/pkg/redis"
	"git.intra.yksa.space/gsn/predictor/pkg/scheduler"
	env "github.com/caarlos0/env/v11"
	"go.uber.org/zap"
)

const servicePrefix = "GSN_PREDICTOR"

func main() {
	lg, err := zap.NewProduction()
	if err != nil {
		panic(err)
	}
	defer lg.Sync()

	// Load configuration from environment with service prefix
	cfg, err := loadConfig()
	if err != nil {
		lg.Fatal("failed to load configuration", zap.Error(err))
	}

	// Load scheduler configuration
	schedulerConfig, err := loadSchedulerConfig()
	if err != nil {
		lg.Fatal("failed to load scheduler configuration", zap.Error(err))
	}

	// Load GRIB updater job configuration
	gribUpdaterConfig, err := loadGribUpdaterConfig()
	if err != nil {
		lg.Fatal("failed to load GRIB updater configuration", zap.Error(err))
	}

	// Initialize Redis service
	redisService, err := redis.New(cfg.Redis)
	if err != nil {
		lg.Fatal("failed to initialize Redis service", zap.Error(err))
	}
	defer redisService.Close()

	// Initialize GRIB service
	gribService, err := grib.New(grib.ServiceConfig{
		Dir:      cfg.Grib.Dir,
		TTL:      cfg.Grib.TTL,
		CacheTTL: cfg.Grib.CacheTTL,
		Redis:    redisService,
		Parallel: cfg.Grib.Parallel,
		Client:   cfg.CreateHTTPClient(),
	})
	if err != nil {
		lg.Fatal("failed to initialize GRIB service", zap.Error(err))
	}
	defer gribService.Close()

	// Initialize service with dependencies
	svc, err := service.New(cfg, gribService, redisService, lg)
	if err != nil {
		lg.Fatal("failed to initialize service", zap.Error(err))
	}
	defer svc.Close()

	// Initialize scheduler
	var sched *scheduler.Scheduler
	if schedulerConfig.Enabled {
		sched = scheduler.New(lg)

		// Add GRIB update job
		gribJob := updater.New(gribService, gribUpdaterConfig, lg)
		if err := sched.AddJob(gribJob); err != nil {
			lg.Error("failed to add GRIB update job to scheduler", zap.Error(err))
		}

		// TODO: Add more jobs here as needed
		// Example:
		// cleanupConfig := cleanup.NewConfig()
		// cleanupJob := cleanup.New(svc, cleanupConfig, lg)
		// if err := sched.AddJob(cleanupJob); err != nil {
		//     lg.Error("failed to add cleanup job to scheduler", zap.Error(err))
		// }

		lg.Info("scheduler initialized with jobs")
	}

	// Initialize handler
	handler := handler.New(svc)

	// Initialize transport
	restConfig, err := loadRestConfig()
	if err != nil {
		lg.Fatal("failed to init transport config", zap.Error(err))
	}

	transport, err := rest.New(lg, handler, restConfig)
	if err != nil {
		lg.Fatal("failed to init transport", zap.Error(err))
	}

	// Start service
	svc.Start()

	// Start scheduler if enabled
	if sched != nil {
		sched.Start()
		lg.Info("scheduler started")
	}

	lg.Info("service started successfully",
		zap.String("grib_dir", cfg.Grib.Dir),
		zap.Duration("grib_ttl", cfg.Grib.TTL),
		zap.Duration("grib_cache_ttl", cfg.Grib.CacheTTL),
		zap.Int("grib_parallel", cfg.Grib.Parallel),
		zap.Bool("scheduler_enabled", schedulerConfig.Enabled),
		zap.Duration("grib_update_interval", gribUpdaterConfig.Interval))

	// Wait for shutdown signal
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	// Start server in goroutine
	go func() {
		lg.Info("starting HTTP server")
		transport.Run()
	}()

	// Wait for shutdown signal
	<-sigChan
	lg.Info("received shutdown signal, stopping service")

	// Stop scheduler first
	if sched != nil {
		sched.Stop()
		lg.Info("scheduler stopped")
	}
}

// loadConfig loads configuration from environment with service prefix
func loadConfig() (*service.Config, error) {
	cfg := &service.Config{}

	if err := env.ParseWithOptions(cfg, env.Options{
		PrefixTagName: servicePrefix + "_",
	}); err != nil {
		return nil, errcodes.Wrap(err, "failed to parse configuration")
	}

	return cfg, nil
}

// loadSchedulerConfig loads scheduler configuration from environment
func loadSchedulerConfig() (*scheduler.Config, error) {
	cfg := &scheduler.Config{}

	if err := env.ParseWithOptions(cfg, env.Options{
		PrefixTagName: servicePrefix + "_SCHEDULER_",
	}); err != nil {
		return nil, errcodes.Wrap(err, "failed to parse scheduler configuration")
	}

	return cfg, nil
}

// loadGribUpdaterConfig loads GRIB updater job configuration from environment
func loadGribUpdaterConfig() (*updater.Config, error) {
	cfg := &updater.Config{}

	if err := env.ParseWithOptions(cfg, env.Options{
		PrefixTagName: servicePrefix + "_GRIB_UPDATER_",
	}); err != nil {
		return nil, errcodes.Wrap(err, "failed to parse GRIB updater configuration")
	}

	return cfg, nil
}

// loadRestConfig loads REST transport configuration from environment with service prefix
func loadRestConfig() (*rest.Config, error) {
	cfg := &rest.Config{}

	if err := env.ParseWithOptions(cfg, env.Options{
		PrefixTagName: servicePrefix + "_REST_",
	}); err != nil {
		return nil, errcodes.Wrap(err, "failed to parse REST configuration")
	}

	return cfg, nil
}
