package api

import (
	"net/http"
	"os"
	"sync/atomic"

	"github.com/aptly-dev/aptly/aptly"
	ctx "github.com/aptly-dev/aptly/context"
	"github.com/aptly-dev/aptly/utils"
	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/rs/zerolog/log"

	"github.com/aptly-dev/aptly/docs"
	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
)

var context *ctx.AptlyContext

func apiMetricsGet() gin.HandlerFunc {
	return func(c *gin.Context) {
		countPackagesByRepos()
		promhttp.Handler().ServeHTTP(c.Writer, c.Request)
	}
}

func redirectSwagger(c *gin.Context) {
	if c.Request.URL.Path == "/docs/index.html" {
		c.Redirect(http.StatusMovedPermanently, "/docs.html")
		return
	}
	if c.Request.URL.Path == "/docs/" {
		c.Redirect(http.StatusMovedPermanently, "/docs.html")
		return
	}
	if c.Request.URL.Path == "/docs" {
		c.Redirect(http.StatusMovedPermanently, "/docs.html")
		return
	}
	c.Next()
}

// Router returns prebuilt with routes http.Handler
func Router(c *ctx.AptlyContext) http.Handler {
	if aptly.EnableDebug {
		gin.SetMode(gin.DebugMode)
	} else {
		gin.SetMode(gin.ReleaseMode)
	}

	router := gin.New()
	context = c

	router.UseRawPath = true

	if c.Config().LogFormat == "json" {
		c.StructuredLogging(true)
		utils.SetupJSONLogger(c.Config().LogLevel, os.Stdout)
		gin.DefaultWriter = utils.LogWriter{Logger: log.Logger}
		router.Use(JSONLogger())
	} else {
		c.StructuredLogging(false)
		utils.SetupDefaultLogger(c.Config().LogLevel)
		router.Use(gin.Logger())
	}

	router.Use(gin.Recovery(), gin.ErrorLogger())

	if c.Config().EnableSwaggerEndpoint {
		router.GET("docs.html", func(c *gin.Context) {
			c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
		})
		router.Use(redirectSwagger)
		url := ginSwagger.URL("/docs/doc.json")
		router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
	}

	if c.Config().EnableMetricsEndpoint {
		MetricsCollectorRegistrar.Register(router)
	}

	if c.Config().ServeInAPIMode {
		router.GET("/repos/", reposListInAPIMode(c.Config().FileSystemPublishRoots))
		router.GET("/repos/:storage/*pkgPath", reposServeInAPIMode)
	}

	api := router.Group("/api")
	if context.Flags().Lookup("no-lock").Value.Get().(bool) {
		// We use a goroutine to count the number of
		// concurrent requests. When no more requests are
		// running, we close the database to free the lock.
		dbRequests = make(chan dbRequest)

		go acquireDatabase()

		api.Use(func(c *gin.Context) {
			var err error

			errCh := make(chan error)
			dbRequests <- dbRequest{acquiredb, errCh}

			err = <-errCh
			if err != nil {
				AbortWithJSONError(c, 500, err)
				return
			}

			defer func() {
				dbRequests <- dbRequest{releasedb, errCh}
				err = <-errCh
				if err != nil {
					AbortWithJSONError(c, 500, err)
				}
			}()

			c.Next()
		})
	}

	{
		if c.Config().EnableMetricsEndpoint {
			api.GET("/metrics", apiMetricsGet())
		}
		api.GET("/version", apiVersion)
		api.GET("/storage", apiDiskFree)

		isReady := &atomic.Value{}
		isReady.Store(false)
		defer isReady.Store(true)
		api.GET("/ready", apiReady(isReady))
		api.GET("/healthy", apiHealthy)
	}

	{
		api.GET("/repos", apiReposList)
		api.POST("/repos", apiReposCreate)
		api.GET("/repos/:name", apiReposShow)
		api.PUT("/repos/:name", apiReposEdit)
		api.DELETE("/repos/:name", apiReposDrop)

		api.GET("/repos/:name/packages", apiReposPackagesShow)
		api.POST("/repos/:name/packages", apiReposPackagesAdd)
		api.DELETE("/repos/:name/packages", apiReposPackagesDelete)

		api.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
		api.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
		api.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage)

		api.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
		api.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)

		api.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
	}

	{
		api.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
	}

	{
		api.GET("/mirrors", apiMirrorsList)
		api.GET("/mirrors/:name", apiMirrorsShow)
		api.GET("/mirrors/:name/packages", apiMirrorsPackages)
		api.POST("/mirrors", apiMirrorsCreate)
		api.PUT("/mirrors/:name", apiMirrorsUpdate)
		api.DELETE("/mirrors/:name", apiMirrorsDrop)
	}

	{
		api.POST("/gpg/key", apiGPGAddKey)
	}

	{
		api.GET("/s3", apiS3List)
	}

	{
		api.GET("/files", apiFilesListDirs)
		api.POST("/files/:dir", apiFilesUpload)
		api.GET("/files/:dir", apiFilesListFiles)
		api.DELETE("/files/:dir", apiFilesDeleteDir)
		api.DELETE("/files/:dir/:name", apiFilesDeleteFile)
	}

	{
		api.GET("/publish", apiPublishList)
		api.GET("/publish/:prefix/:distribution", apiPublishShow)
		api.POST("/publish", apiPublishRepoOrSnapshot)
		api.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
		api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
		api.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
		api.POST("/publish/:prefix/:distribution/sources", apiPublishAddSource)
		api.GET("/publish/:prefix/:distribution/sources", apiPublishListChanges)
		api.PUT("/publish/:prefix/:distribution/sources", apiPublishSetSources)
		api.DELETE("/publish/:prefix/:distribution/sources", apiPublishDropChanges)
		api.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishUpdateSource)
		api.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishRemoveSource)
		api.POST("/publish/:prefix/:distribution/update", apiPublishUpdate)
	}

	{
		api.GET("/snapshots", apiSnapshotsList)
		api.POST("/snapshots", apiSnapshotsCreate)
		api.PUT("/snapshots/:name", apiSnapshotsUpdate)
		api.GET("/snapshots/:name", apiSnapshotsShow)
		api.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
		api.DELETE("/snapshots/:name", apiSnapshotsDrop)
		api.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
		api.POST("/snapshots/:name/merge", apiSnapshotsMerge)
		api.POST("/snapshots/:name/pull", apiSnapshotsPull)
	}

	{
		api.GET("/packages/:key", apiPackagesShow)
		api.GET("/packages", apiPackages)
	}

	{
		api.GET("/graph.:ext", apiGraph)
	}
	{
		api.POST("/db/cleanup", apiDbCleanup)
	}
	{
		api.GET("/tasks", apiTasksList)
		api.POST("/tasks-clear", apiTasksClear)
		api.GET("/tasks-wait", apiTasksWait)
		api.GET("/tasks/:id/wait", apiTasksWaitForTaskByID)
		api.GET("/tasks/:id/output", apiTasksOutputShow)
		api.GET("/tasks/:id/detail", apiTasksDetailShow)
		api.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
		api.GET("/tasks/:id", apiTasksShow)
		api.DELETE("/tasks/:id", apiTasksDelete)
	}

	return router
}
