package sqleditor

import (
	"dataapps/errs"
	dto "dataapps/internal/dto/sqleditor"
	entity "dataapps/internal/entity/sqleditor"
	service "dataapps/internal/service/sqleditor"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	tierrs "github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
)

// ListDatabases godoc
// @Summary list databases
// @Description list databases in a cluster
// @Tags sqleditor
// @Param org_id path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowDBsRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/schemas/databases [get]
func ListDatabases(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: ListDatabases-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}

	dbs, bizErr := service.ListDatabases(ctx, dto.ListDatabasesInput{
		CreatedBy:  createdBy,
		ProjectID:  projectId,
		ClusterID:  clusterId,
		Auth0Token: auth0Token,
	})
	if bizErr != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: ListDatabases failed. clusterid:%d user:%s", clusterId, createdBy))
		c.Error(bizErr)
		return
	}
	if dbs == nil {
		dbs = make([]*dto.DatabaseItem, 0)
	}

	c.JSON(http.StatusOK, &dto.ShowDBsRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     dbs,
	})
}

// ListTables godoc
// @Summary list tables by database
// @Description list tables in a database
// @Tags sqleditor
// @Param org_id path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param db_name path string true "db name"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowTablesRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/schemas/databases/{db_name}/tables [get]
func ListTables(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: ListTables-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}
	dbName := c.Param("db_name")
	if strings.TrimSpace(dbName) == "" {
		c.Error(tierrs.NewBadRequest("db_name is required", nil))
		return
	}

	tables, bizErr := service.ListTables(ctx, dto.ListTablesInput{
		CreatedBy:  createdBy,
		ProjectID:  projectId,
		ClusterID:  clusterId,
		Database:   dbName,
		Auth0Token: auth0Token,
	})
	if bizErr != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: ListTables failed. clusterid:%d db:%s user:%s", clusterId, dbName, createdBy))
		c.Error(bizErr)
		return
	}
	if tables == nil {
		tables = make([]string, 0)
	}

	c.JSON(http.StatusOK, &dto.ShowTablesRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     tables,
	})
}

// ListColumns godoc
// @Summary list columns by database and table
// @Description list columns in a table
// @Tags sqleditor
// @Param org_id path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param db_name path string true "db name"
// @Param table_name path string true "table name"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowColumnsRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/schemas/databases/{db_name}/tables/{table_name}/columns [get]
func ListColumns(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: ListColumns-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}

	dbName := c.Param("db_name")
	tableName := c.Param("table_name")
	if strings.TrimSpace(dbName) == "" {
		c.Error(tierrs.NewBadRequest("db_name is required", nil))
		return
	}
	if strings.TrimSpace(tableName) == "" {
		c.Error(tierrs.NewBadRequest("table_name is required", nil))
		return
	}

	cols, bizErr := service.ListColumns(ctx, dto.ListColumnsInput{
		CreatedBy:  createdBy,
		ProjectID:  projectId,
		ClusterID:  clusterId,
		Database:   dbName,
		Table:      tableName,
		Auth0Token: auth0Token,
	})
	if bizErr != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: ListColumns failed. clusterid:%d db:%s table:%s user:%s", clusterId, dbName, tableName, createdBy))
		c.Error(bizErr)
		return
	}
	if cols == nil {
		cols = make([]*entity.Columns, 0)
	}

	c.JSON(http.StatusOK, &dto.ShowColumnsRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     cols,
	})
}

// SearchSchemas godoc
// @Summary search databases/tables/columns
// @Description search schema objects by keyword and type(database/table/column)
// @Tags sqleditor
// @Param org_id path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param type query string true "database|table|column"
// @Param keyword query string true "search keyword"
// @Param limit query int false "max results, default 100, max 1000"
// @Accept json
// @Produce json
// @Success 200 {object} dto.SchemaSearchRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/schemas/search [get]
func SearchSchemas(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: SearchSchemas-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}

	tp := strings.TrimSpace(c.Query("type"))
	keyword := c.Query("keyword")
	if tp == "" {
		c.Error(tierrs.NewBadRequest("type is required", nil))
		return
	}
	if strings.TrimSpace(keyword) == "" {
		c.Error(tierrs.NewBadRequest("keyword is required", nil))
		return
	}
	var searchType dto.SchemaSearchType
	switch tp {
	case "database":
		searchType = dto.SchemaSearchTypeDatabase
	case "table":
		searchType = dto.SchemaSearchTypeTable
	case "column":
		searchType = dto.SchemaSearchTypeColumn
	default:
		c.Error(tierrs.NewBadRequest("invalid type, must be one of: database, table, column", nil))
		return
	}

	limit := 0
	if limitStr := strings.TrimSpace(c.Query("limit")); limitStr != "" {
		v, err := strconv.Atoi(limitStr)
		if err != nil || v <= 0 {
			c.Error(tierrs.NewBadRequest("invalid limit", err))
			return
		}
		limit = v
	}

	items, bizErr := service.SearchSchemas(ctx, dto.SearchSchemasInput{
		CreatedBy:  createdBy,
		ProjectID:  projectId,
		ClusterID:  clusterId,
		Type:       searchType,
		Keyword:    keyword,
		Limit:      limit,
		Auth0Token: auth0Token,
	})
	if bizErr != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: SearchSchemas failed. clusterid:%d type:%s keyword:%s user:%s", clusterId, tp, keyword, createdBy))
		c.Error(bizErr)
		return
	}
	if items == nil {
		items = make([]*dto.SchemaSearchItem, 0)
	}

	c.JSON(http.StatusOK, &dto.SchemaSearchRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     items,
	})
}
