/*
 * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package routes

import (
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog/log"

	"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/common/consts"
	"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/controllers"
	"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/mocks"
	"github.com/dynemo-ai/dynemo/deploy/compoundai/api-server/api/schemas"
)

func SetupRouter() *gin.Engine {
	router := gin.Default()
	router.Use(injectCurrentOrganization)

	baseGroup := router.Group("")
	createK8sRoutes(baseGroup)

	api := router.Group("/api")
	api.Use(getAuthInfo)
	v1 := api.Group("/v1")
	v2 := api.Group("/v2")

	/* Start V1 APIs */
	createClusterRoutes(v1)

	// These routes are proxied to NDS
	createCompoundNimRoutes(v1)
	createBentoRepositoriesRoutes(v1)

	createMiscellaneousRoutes(v1)
	createMockedRoutes(v1)
	createOrganizationRoutes(v1)
	createPublicRoutes(v1)
	/* End V1 APIs */

	/* Start V2 APIs */
	deploymentRoutesV2(v2)
	/* End V2 APIs */

	return router
}

func createK8sRoutes(grp *gin.RouterGroup) {
	healthGroup := grp.Group("/healthz")
	healthGroup.GET("", controllers.HealthController.Get)

	readyGroup := grp.Group("/readyz")
	readyGroup.GET("", controllers.HealthController.Get)
}

func createClusterRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/clusters")

	resourceGrp := grp.Group("/:clusterName")

	resourceGrp.GET("", controllers.ClusterController.Get)

	resourceGrp.PATCH("", controllers.ClusterController.Update)

	grp.GET("", controllers.ClusterController.List)

	grp.POST("", controllers.ClusterController.Create)

	compoundComponentRoutes(resourceGrp)
	deploymentRoutes(resourceGrp)
}

func deploymentRoutes(grp *gin.RouterGroup) {
	namespacedGrp := grp.Group("/namespaces/:kubeNamespace/deployments")
	grp = grp.Group("/deployments")

	resourceGrp := namespacedGrp.Group("/:deploymentName")

	resourceGrp.GET("", controllers.DeploymentController.Get)

	resourceGrp.PATCH("", controllers.DeploymentController.Update)

	resourceGrp.POST("/sync_status", controllers.DeploymentController.SyncStatus)

	resourceGrp.POST("/terminate", controllers.DeploymentController.Terminate)

	resourceGrp.DELETE("", controllers.DeploymentController.Delete)

	// resourceGrp.GET("/terminal_records", controllers.DeploymentController.ListTerminalRecords)

	grp.GET("", controllers.DeploymentController.ListClusterDeployments)

	grp.POST("", controllers.DeploymentController.Create)

	deploymentRevisionRoutes(resourceGrp)
}

func deploymentRevisionRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/revisions")

	resourceGrp := grp.Group("/:revisionUid")

	resourceGrp.GET("", controllers.DeploymentRevisionController.Get)

	grp.GET("", controllers.DeploymentRevisionController.List)
}

func deploymentRoutesV2(grp *gin.RouterGroup) {
	grp = grp.Group("/deployments")
	grp.POST("", controllers.DeploymentController.CreateV2)

	resourceGrp := grp.Group("/:deploymentName")
	resourceGrp.GET("", controllers.DeploymentController.GetV2)
	resourceGrp.PUT("", controllers.DeploymentController.UpdateV2)
	resourceGrp.POST("/terminate", controllers.DeploymentController.TerminateV2)
	resourceGrp.DELETE("", controllers.DeploymentController.DeleteV2)
}

func createBentoRepositoriesRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/bento_repositories")

	resourceGrp := grp.Group("/:bentoRepositoryName")

	resourceGrp.GET("", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/deployments", controllers.ProxyController.ReverseProxy)

	grp.GET("", controllers.ProxyController.ReverseProxy)

	grp.POST("", controllers.ProxyController.ReverseProxy)

	bentoRoutes(resourceGrp)
}

func bentoRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/bentos")

	resourceGrp := grp.Group("/:version")

	resourceGrp.GET("", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/update_image_build_status_syncing_at", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/update_image_build_status", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/models", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/deployments", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/start_multipart_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/presign_multipart_upload_url", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/complete_multipart_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/presign_upload_url", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/presign_download_url", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/start_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/finish_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PUT("/upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/download", controllers.ProxyController.ReverseProxy)

	grp.GET("", controllers.ProxyController.ReverseProxy)

	grp.POST("", controllers.ProxyController.ReverseProxy)
}

func createCompoundNimRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/compound_nims")

	resourceGrp := grp.Group("/:compoundNimName")

	resourceGrp.GET("", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/deployments", controllers.DeploymentController.ListCompoundNimDeployments)

	grp.GET("", controllers.ProxyController.ReverseProxy)

	grp.POST("", controllers.ProxyController.ReverseProxy)

	compoundNimVersionRoutes(resourceGrp)
}

func compoundNimVersionRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/versions")

	resourceGrp := grp.Group("/:version")

	resourceGrp.GET("", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/update_image_build_status_syncing_at", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/update_image_build_status", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/models", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/deployments", controllers.DeploymentController.ListCompoundNimVersionDeployments)

	resourceGrp.PATCH("/start_multipart_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/presign_multipart_upload_url", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/complete_multipart_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/presign_upload_url", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/presign_download_url", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/start_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PATCH("/finish_upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.PUT("/upload", controllers.ProxyController.ReverseProxy)

	resourceGrp.GET("/download", controllers.ProxyController.ReverseProxy)

	grp.GET("", controllers.ProxyController.ReverseProxy)

	grp.POST("", controllers.ProxyController.ReverseProxy)
}

func createMiscellaneousRoutes(grp *gin.RouterGroup) {
	versionGrp := grp.Group("/version")

	versionGrp.GET("", controllers.VersionController.Get)
}

// Legacy APIs used by the CLI
func createMockedRoutes(grp *gin.RouterGroup) {
	grp.GET("auth/current", controllers.UserController.GetDefaultUser)
}

func createOrganizationRoutes(grp *gin.RouterGroup) {
	resourceGrp := grp.Group("/current_org")

	resourceGrp.GET("", controllers.OrganizationController.Get)

	resourceGrp.GET("/major_cluster", controllers.OrganizationController.GetMajorCluster)

	resourceGrp.PATCH("", controllers.OrganizationController.Update)

	resourceGrp.GET("/events", controllers.OrganizationController.ListEvents)

	resourceGrp.GET("/event_operation_names", controllers.OrganizationController.ListEventOperationNames)

	grp.GET("/members", controllers.OrganizationMemberController.List)

	grp.POST("/members", controllers.OrganizationMemberController.Create)

	grp.DELETE("/members", controllers.OrganizationMemberController.Delete)

	grp.GET("/deployments", controllers.DeploymentController.ListDeployments)

	grp.GET("/deployment_creation_json_schema", controllers.DeploymentController.CreationJSONSchema)

	grp.GET("/yatai_components", controllers.CompoundComponentController.ListAll)

	grp.GET("/orgs", controllers.OrganizationController.List)

	grp.POST("/orgs", controllers.OrganizationController.Create)
}

func createPublicRoutes(grp *gin.RouterGroup) {
	grp.GET("/info", controllers.InfoController.GetInfo)
}

func compoundComponentRoutes(grp *gin.RouterGroup) {
	grp = grp.Group("/yatai_components")

	grp.GET("", controllers.CompoundComponentController.List)

	grp.POST("", controllers.CompoundComponentController.Register)
}

func injectCurrentOrganization(c *gin.Context) {
	orgName := strings.TrimSpace(c.GetHeader(consts.YataiOrganizationHeaderName))
	if orgName == "" {
		orgName = strings.TrimSpace(c.Query("organization_name"))
	}
	org := mocks.DefaultOrg()

	if orgName != "" {
		org.Name = orgName
	}

	orgId := c.GetHeader(consts.NgcOrganizationHeaderName)
	if orgId == "" {
		orgId = "default"
	}
	org.Uid = orgId

	c.Set(controllers.CurrentOrganizationKey, org)
	c.Next()
}

func getAuthInfo(c *gin.Context) {
	orgId := c.GetHeader(consts.NgcOrganizationHeaderName)
	if orgId == "" {
		orgId = "default"
	}

	userId := c.GetHeader(consts.NgcUserHeaderName)
	if userId == "" {
		userId = "default"
	}

	ownership := &schemas.OwnershipSchema{
		UserId:         userId,
		OrganizationId: orgId,
	}
	log.Info().Msgf("Setting ownership info %+v", ownership)
	c.Set(controllers.OwnershipInfoKey, ownership)
	c.Next()
}
