package main

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"net/http"
	"net/url"
	"strings"
	"time"

	"gitea.dwysokinski.me/twhelp/corev3/internal/adapter"
	"gitea.dwysokinski.me/twhelp/corev3/internal/app"
	"gitea.dwysokinski.me/twhelp/corev3/internal/chislog"
	"gitea.dwysokinski.me/twhelp/corev3/internal/health"
	"gitea.dwysokinski.me/twhelp/corev3/internal/port"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/urfave/cli/v2"
)

var (
	apiServerPortFlag = &cli.UintFlag{
		Name:    "api.port",
		EnvVars: []string{"API_PORT"},
		Value:   9234, //nolint:gomnd
	}
	apiServerReadTimeoutFlag = &cli.DurationFlag{
		Name:    "api.readTimeout",
		EnvVars: []string{"API_READ_TIMEOUT"},
		Value:   5 * time.Second, //nolint:gomnd
	}
	apiServerReadHeaderTimeoutFlag = &cli.DurationFlag{
		Name:    "api.readHeaderTimeout",
		EnvVars: []string{"API_READ_HEADER_TIMEOUT"},
		Value:   time.Second,
	}
	apiServerWriteTimeoutFlag = &cli.DurationFlag{
		Name:    "api.writeTimeout",
		EnvVars: []string{"API_WRITE_TIMEOUT"},
		Value:   10 * time.Second, //nolint:gomnd
	}
	apiServerIdleTimeoutFlag = &cli.DurationFlag{
		Name:    "api.idleTimeout",
		EnvVars: []string{"API_IDLE_TIMEOUT"},
		Value:   180 * time.Second, //nolint:gomnd
	}
	apiServerShutdownTimeoutFlag = &cli.DurationFlag{
		Name:    "api.shutdownTimeout",
		EnvVars: []string{"API_SHUTDOWN_TIMEOUT"},
		Value:   10 * time.Second, //nolint:gomnd
	}
	apiServerOpenAPIEnabledFlag = &cli.BoolFlag{
		Name:    "api.openApi.enabled",
		EnvVars: []string{"API_OPENAPI_ENABLED"},
		Value:   true,
	}
	apiServerOpenAPISwaggerEnabledFlag = &cli.BoolFlag{
		Name:    "api.openApi.swaggerEnabled",
		EnvVars: []string{"API_OPENAPI_SWAGGER_ENABLED"},
		Value:   true,
	}
	apiServerOpenAPIServersFlag = &cli.StringSliceFlag{
		Name:    "api.openApi.servers",
		EnvVars: []string{"API_OPENAPI_SERVERS"},
	}
	apiServerFlags = []cli.Flag{
		apiServerPortFlag,
		apiServerReadTimeoutFlag,
		apiServerReadHeaderTimeoutFlag,
		apiServerWriteTimeoutFlag,
		apiServerIdleTimeoutFlag,
		apiServerOpenAPIEnabledFlag,
		apiServerOpenAPISwaggerEnabledFlag,
		apiServerOpenAPIServersFlag,
	}
)

const (
	apiBasePath  = "/api"
	metaBasePath = "/_meta"
)

var cmdServe = &cli.Command{
	Name:  "serve",
	Usage: "Run the HTTP server",
	Flags: concatSlices(apiServerFlags, dbFlags),
	Action: func(c *cli.Context) error {
		logger := loggerFromCtx(c.Context)

		// deps
		bunDB, err := newBunDBFromFlags(c)
		if err != nil {
			return err
		}
		defer func() {
			logger.Debug("closing db connections...", slog.Int("db.openConnections", bunDB.Stats().OpenConnections))
			if dbCloseErr := bunDB.Close(); dbCloseErr != nil {
				logger.Warn("couldn't close db connections", slog.Any("error", dbCloseErr))
			} else {
				logger.Debug("db connections closed")
			}
		}()

		// adapters
		versionRepo := adapter.NewVersionBunRepository(bunDB)
		serverRepo := adapter.NewServerBunRepository(bunDB)
		tribeRepo := adapter.NewTribeBunRepository(bunDB)
		playerRepo := adapter.NewPlayerBunRepository(bunDB)
		villageRepo := adapter.NewVillageBunRepository(bunDB)

		// services
		versionSvc := app.NewVersionService(versionRepo)
		serverSvc := app.NewServerService(serverRepo, nil, nil)
		tribeSvc := app.NewTribeService(tribeRepo, nil, nil)
		playerSvc := app.NewPlayerService(playerRepo, nil, nil, nil)
		villageSvc := app.NewVillageService(villageRepo, nil, nil)

		// health
		h := health.New()

		server, err := newHTTPServer(
			httpServerConfig{
				port:              c.Uint(apiServerPortFlag.Name),
				readTimeout:       c.Duration(apiServerReadTimeoutFlag.Name),
				readHeaderTimeout: c.Duration(apiServerReadHeaderTimeoutFlag.Name),
				writeTimeout:      c.Duration(apiServerWriteTimeoutFlag.Name),
				idleTimeout:       c.Duration(apiServerIdleTimeoutFlag.Name),
			},
			func(r chi.Router) error {
				oapiCfg, oapiCfgErr := newOpenAPIConfigFromFlags(c)
				if oapiCfgErr != nil {
					return oapiCfgErr
				}

				r.Use(newAPIMiddlewares(logger)...)

				r.Mount(metaBasePath, port.NewMetaHTTPHandler(h))

				r.Mount(apiBasePath, port.NewAPIHTTPHandler(
					versionSvc,
					serverSvc,
					tribeSvc,
					playerSvc,
					villageSvc,
					port.WithOpenAPIConfig(oapiCfg),
				))

				return nil
			},
		)
		if err != nil {
			return fmt.Errorf("couldn't construct HTTP server: %w", err)
		}

		idleConnsClosed := make(chan struct{})
		go func() {
			waitForShutdownSignal(c.Context)

			logger.Debug("received shutdown signal")

			ctx, cancel := context.WithTimeout(c.Context, c.Duration(apiServerShutdownTimeoutFlag.Name))
			defer cancel()

			if err := server.Shutdown(ctx); err != nil {
				logger.Warn("shutdown failed", slog.Any("error", err))
			}

			close(idleConnsClosed)
		}()

		logger.Info("Server is listening on the addr "+server.Addr, slog.String("addr", server.Addr))

		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			return fmt.Errorf("ListenAndServe failed: %w", err)
		}

		<-idleConnsClosed

		return nil
	},
}

func newOpenAPIConfigFromFlags(c *cli.Context) (port.OpenAPIConfig, error) {
	rawURLs := c.StringSlice(apiServerOpenAPIServersFlag.Name)

	servers := make([]port.OpenAPIConfigServer, 0, len(rawURLs))

	for _, raw := range rawURLs {
		u, err := url.Parse(raw)
		if err != nil {
			return port.OpenAPIConfig{}, err
		}

		servers = append(servers, port.OpenAPIConfigServer{
			URL: u,
		})
	}

	return port.OpenAPIConfig{
		Enabled:        c.Bool(apiServerOpenAPIEnabledFlag.Name),
		SwaggerEnabled: c.Bool(apiServerOpenAPISwaggerEnabledFlag.Name),
		BasePath:       apiBasePath,
		Servers:        servers,
	}, nil
}

func newAPIMiddlewares(logger *slog.Logger) chi.Middlewares {
	return chi.Middlewares{
		chislog.Logger(logger, chislog.WithFilter(func(r *http.Request) bool {
			return !strings.HasPrefix(r.URL.Path, metaBasePath)
		})),
		middleware.Recoverer,
	}
}
