package api

import (
	"dataapps/commons/grpc"
	"dataapps/docs"
	"dataapps/internal/controller/eda"
	"dataapps/internal/controller/sqleditor"
	"net/http"

	"github.com/gin-gonic/gin"
	swaggerfiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/tiinf/transport/http/tigin"
)

const (
	sqlEditorClusterStatusRoutePattern = "/api/v1/dataapps/sqleditor/orgs/:org_id/projects/:project_id/clusters/status"
	sqlEditorUsersRoutePattern         = "/api/v1/dataapps/sqleditor/orgs/:org_id/projects/:project_id/users"
)

func installAuthAndRBACPlugin(ginEng *gin.Engine) {
	// Auth CheckUser
	ginEng.Use(CheckUser)
	accountClient := grpc.GetAccountProviderSvcClient()
	// ginEng.Use(tigin.CheckAuthByAuth0(accountClient))
	// RBAC Permission
	ginEng.Use(checkRBACPermission(accountClient))
}

func checkRBACPermission(accountClient account.AccountAPIServiceClient) gin.HandlerFunc {
	checkPermission := tigin.CheckRBACPermission(accountClient, rbacPathPattern2ResourceMap(), rbacPathWhitelist())
	checkClusterStatusPermission := CheckSQLClusterStatusPermission(accountClient)
	return func(c *gin.Context) {
		if c.Request.Method == http.MethodGet && c.FullPath() == sqlEditorClusterStatusRoutePattern {
			checkClusterStatusPermission(c)
			return
		}
		// Users is a personal preference endpoint scoped to the authenticated
		// user rather than to a project or cluster.
		if isSQLEditorSpecialRBACRoute(c.Request.Method, c.FullPath()) {
			c.Next()
			return
		}
		checkPermission(c)
	}
}

func isSQLEditorSpecialRBACRoute(method, fullPath string) bool {
	if fullPath == sqlEditorUsersRoutePattern {
		return method == http.MethodGet || method == http.MethodPut
	}
	return fullPath == sqlEditorClusterStatusRoutePattern && method == http.MethodGet
}

func rbacPathPattern2ResourceMap() map[string]string {
	return map[string]string{
		"put:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/settings":                                     "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/put_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/settings":                                     "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"post:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/statements":                                  "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/post_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/dbs/[^/]+/[^/]+/meta":                         "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/dbs/[^/]+/[^/]+/exist":                        "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"post:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sqlfiles":                                    "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/post_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sqlfiles/[^/]+":                               "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"put:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sqlfiles/[^/]+":                               "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/put_api",
		"delete:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sqlfiles/[^/]+":                            "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/delete_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sqlfiles":                                     "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"post:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/session":                                     "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/post_api",
		"delete:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/session/[^/]+":                             "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/delete_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/meta":                                         "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/schemas/databases":                            "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/schemas/databases/[^/]+/tables":               "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/schemas/databases/[^/]+/tables/[^/]+/columns": "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/schemas/search":                               "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"post:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/sample/initialize":                           "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/post_api",
		"post:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/oplog":                                       "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/post_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/dataapi/sample":                               "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/enter/audit":                                  "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/clusters/(\\d+)/download/audit":                               "orgs/{org_id}/projects/{project_id}/tidbs/{cluster_id}/sqleditor/get_api",
		"put:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/settings":                                                     "orgs/{org_id}/projects/{project_id}/sqleditor/put_api",
		"post:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/bot":                                                         "orgs/{org_id}/projects/{project_id}/sqleditor/post_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/permission/entry":                                             "orgs/{org_id}/projects/{project_id}/sqleditor/get_api",
		"get:/api/v1/dataapps/sqleditor/orgs/(\\d+)/projects/(\\d+)/streaming":                                                    "orgs/{org_id}/projects/{project_id}/sqleditor/get_api",
		"get:/api/v1/eda/orgs/(\\d+)/projects/(\\d+)/.*":                                                                          "orgs/{org_id}/projects/{project_id}/eda/get_api",
		"post:/api/v1/eda/orgs/(\\d+)/projects/(\\d+)/.*":                                                                         "orgs/{org_id}/projects/{project_id}/eda/post_api",
		"patch:/api/v1/eda/orgs/(\\d+)/projects/(\\d+)/.*":                                                                        "orgs/{org_id}/projects/{project_id}/eda/patch_api",
		"put:/api/v1/eda/orgs/(\\d+)/projects/(\\d+)/.*":                                                                          "orgs/{org_id}/projects/{project_id}/eda/put_api",
		"delete:/api/v1/eda/orgs/(\\d+)/projects/(\\d+)/.*":                                                                       "orgs/{org_id}/projects/{project_id}/eda/delete_api",
	}
}

func rbacPathWhitelist() []string {
	return []string{
		"/healthz",
		"/metrics",
		"/inner/sqleditor/.*",
	}
}

func Routers(ginEng *gin.Engine) {
	ginEng.Any(
		"/healthz", func(c *gin.Context) {
			c.Status(200)
		},
	)

	// Metric
	ginEng.Use(Metric())

	ginEng.Use(InjectLogger())

	// Error
	routeError(ginEng)

	routeInnerRestAPI(ginEng)

	// Cors
	routeCors(ginEng)
	installAuthAndRBACPlugin(ginEng)

	// Request Body Check
	ginEng.Use(CheckRequestBodySize)

	// Check permission to access dedicated cluster
	ginEng.Use(CheckDedicatedClusterPermission)

	// Real REST-API registry
	routeRestAPI(ginEng)
	routeEDAAPI(ginEng)
}

func routeError(router *gin.Engine) {
	router.Use(APIErrorJSONReporter())
}

func routeCors(router *gin.Engine) {
	router.Use(Cors())
}

func routeEDAAPI(router *gin.Engine) {
	edaProxy := router.Group("/api/v1/eda")
	{
		edaProxy.Any("/orgs/:org_id/projects/:project_id/*path", eda.EDAProxy)
	}
}

func routeRestAPI(router *gin.Engine) {
	docs.SwaggerInfo.BasePath = "/api/v1/dataapps/sqleditor"
	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
	dataAppsSqlEditor := router.Group("/api/v1/dataapps/sqleditor")
	{
		dataAppsSqlEditor.PUT("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/settings", sqleditor.UpdateSettings)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/settings", sqleditor.ShowSettings)
		dataAppsSqlEditor.POST("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/statements", sqleditor.Statements)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/dbs/:db_name/:table_name/meta", sqleditor.ShowTableMeta)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/dbs/:db_name/:table_name/exist", sqleditor.IsDBTableExist)
		dataAppsSqlEditor.POST("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/sqlfiles", sqleditor.CreateSQLFile)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/sqlfiles/:sql_file_id", sqleditor.ShowSQLFile)
		dataAppsSqlEditor.PUT("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/sqlfiles/:sql_file_id", sqleditor.UpdateSQLFile)
		dataAppsSqlEditor.DELETE("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/sqlfiles/:sql_file_id", sqleditor.DeleteSQLFile)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/sqlfiles", sqleditor.ListSQLFile)
		dataAppsSqlEditor.POST("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/session", sqleditor.CreateSession)
		dataAppsSqlEditor.DELETE("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/session/:session_id", sqleditor.CloseSession)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/meta", sqleditor.ShowClusterMeta)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/schemas/databases", sqleditor.ListDatabases)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/schemas/databases/:db_name/tables", sqleditor.ListTables)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/schemas/databases/:db_name/tables/:table_name/columns", sqleditor.ListColumns)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/schemas/search", sqleditor.SearchSchemas)
		dataAppsSqlEditor.POST("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/sample/initialize", sqleditor.SampleDataInitialize)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/status", sqleditor.CheckClusterStatus)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/users", sqleditor.ShowUserInfo)
		dataAppsSqlEditor.PUT("/orgs/:org_id/projects/:project_id/users", sqleditor.UpdateUserInfo)
		dataAppsSqlEditor.PUT("/orgs/:org_id/projects/:project_id/settings", sqleditor.UpdateDataAppSettings)
		dataAppsSqlEditor.POST("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/oplog", sqleditor.CreateOplog)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/dataapi/sample", sqleditor.ShowChat2DataURL)
		dataAppsSqlEditor.POST("/orgs/:org_id/projects/:project_id/bot", sqleditor.InteractBot, CheckBotRequestBodySize)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/enter/audit", sqleditor.AuditLog4EnterChat2Query)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/clusters/:cluster_id/download/audit", sqleditor.AuditLog4DownloadQueryResult)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/permission/entry", sqleditor.ServerlessPermission2Entry)
		dataAppsSqlEditor.GET("/orgs/:org_id/projects/:project_id/streaming", sqleditor.QueryStreamResultByJobID)
	}
}

func routeInnerRestAPI(router *gin.Engine) {
	innerSqlEditor := router.Group("/inner/sqleditor")
	{
		innerSqlEditor.POST("/oplog", sqleditor.InnerCreateOplog)
	}
}
