package serverimp

import (
	"context"
	"errors"
	"net/http"
	"os"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/tech/sendico/notification/interface/api"
	"github.com/tech/sendico/notification/interface/api/localizer"
	apiimip "github.com/tech/sendico/notification/internal/api"
	"github.com/tech/sendico/pkg/db"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	mduration "github.com/tech/sendico/pkg/mutil/duration"
	"go.uber.org/zap"
	"gopkg.in/yaml.v3"
)

type httpServerConf struct {
	ListenAddress     string `yaml:"listen_address"`
	ReadHeaderTimeout int    `yaml:"read_header_timeout"`
	ShutdownTimeout   int    `yaml:"shutdown_timeout"`
}

// Config represents the server configuration
type Config struct {
	API        *api.Config       `yaml:"api"`
	DB         *db.Config        `yaml:"database"`
	Localizer  *localizer.Config `yaml:"localizer"`
	HTTPServer *httpServerConf   `yaml:"http_server"`
}

// Instance represents an instance of the server
type Imp struct {
	logger     mlogger.Logger
	api        mservice.MicroService
	config     *Config
	db         db.Factory
	l          localizer.Localizer
	httpServer *http.Server
	debug      bool
	file       string
}

// Shutdown stops the server
func (i *Imp) Shutdown() {
	// Shutdown HTTP server
	ctx, cancel := context.WithTimeout(context.Background(), mduration.Param2Duration(i.config.HTTPServer.ShutdownTimeout, time.Second))
	i.logger.Info("Shutting HTTP server down...")
	if err := i.httpServer.Shutdown(ctx); err != nil {
		if !errors.Is(err, http.ErrServerClosed) {
			i.logger.Warn("Failed to shutdown HTTP server gracefully", zap.Error(err))
			cancel()
			os.Exit(1)
		}
	}
	cancel()
}

func (i *Imp) Run() error {
	if err := i.httpServer.ListenAndServe(); err != nil {
		if !errors.Is(err, http.ErrServerClosed) {
			i.logger.Error("HTTP Server stopped unexpectedly", zap.Error(err))
		}
	}
	i.logger.Info("HTTP Server stopped")

	if err := i.api.Finish(context.Background()); err != nil {
		i.logger.Warn("Error when finishing service", zap.Error(err))
	}

	i.db.CloseConnection()

	return nil
}

// Start starts the server
func (i *Imp) Start() error {
	i.logger.Info("Starting...", zap.String("config_file", i.file), zap.Bool("debug_mode", i.debug))
	// Load configuration file
	data, err := os.ReadFile(i.file)
	if err != nil {
		i.logger.Error("Could not load configuration", zap.Error(err), zap.String("config_file", i.file))
		return err
	}

	if err = yaml.Unmarshal(data, &i.config); err != nil {
		i.logger.Error("Failed to parse configuration", zap.Error(err))
		return err
	}

	if i.db, err = db.NewConnection(i.logger, i.config.DB); err != nil {
		i.logger.Error("Could not open database connection", zap.Error(err))
		return err
	}

	if i.l, err = localizer.CreateLocalizer(i.logger, i.config.Localizer); err != nil {
		i.logger.Error("Failed to create localizer", zap.Error(err))
		return err
	}

	router := chi.NewRouter()
	if i.api, err = apiimip.CreateAPI(i.logger, i.config.API, i.l, i.db, router, i.debug); err != nil {
		i.logger.Error("Failed to create API instance", zap.Error(err))
		return err
	}

	// Startup the HTTP Server in a way that we can gracefully shut it down again
	i.httpServer = &http.Server{
		Addr:              i.config.HTTPServer.ListenAddress,
		Handler:           router,
		ReadHeaderTimeout: mduration.Param2Duration(i.config.HTTPServer.ReadHeaderTimeout, time.Second),
	}

	return i.Run()
}

func Create(logger mlogger.Logger, file string, debug bool) (*Imp, error) {
	srv := &Imp{
		logger: logger,
		debug:  debug,
		file:   file,
	}
	return srv, nil
}
