package controller

import (
	"dataservices/commons/ginparser"
	dto "dataservices/internal/dto/dataapi"
	service "dataservices/internal/service/central"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/dataservices/idl/pbgen/proto/dataapi"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

// GetOpenApiSpecification godoc
// @Summary get oas
// @Description get oas
// @Tags oas
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param format query string true "openapi specification format"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GetOpenApiSpecificationResp
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas [get]
func GetOpenApiSpecification(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas[get]-GetOpenApiSpecification param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}
	format := strings.TrimSpace(c.Query("format"))
	err := service.ValidateAppOpenapiFormat(format)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas[get]-GetOpenApiSpecification format validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	jwt, _ := getAuth0Token(c, ctx)
	res, err := service.GenerateOpenApiSpecification(
		ctx,
		params.AppID,
		&dataapi.Credential{
			Type: dataapi.CrtType_AUTH0,
			Key:  jwt,
		},
		format,
	)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas[get]-GetOpenApiSpecification generate oas failed", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	resp := dto.GetOpenApiSpecificationResp{
		BaseResp: dto.SuccessBaseResp(),
		Data:     res,
	}
	c.JSON(http.StatusOK, resp)
}

// GetOpenApiSpecificationByExternalAppId godoc
// @Summary get oas
// @Description get oas
// @Tags oas
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param external_app_id path string true "app ID"
// @Param format query string true "openapi specification format"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GetOpenApiSpecificationResp
// @Router /orgs/{org_id}/projects/{project_id}/external-apps/{external_app_id}/oas [get]
func GetOpenApiSpecificationByExternalAppId(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_EXTERNAL_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/external-apps/{external_app_id}/oas[get]-GetOpenApiSpecificationByExternalAppId param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}
	format := strings.TrimSpace(c.Query("format"))
	err := service.ValidateAppOpenapiFormat(format)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/external-apps/{external_app_id}/oas[get]-GetOpenApiSpecificationByExternalAppId format validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	dataapp, err := service.GetDataAppByExtId(ctx, params.ExternalAppId)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/external-apps/{external_app_id}/oas[get]-GetOpenApiSpecificationByExternalAppId get dataapp failed", zap.Error(err), zap.String("user", createdBy))
		if errs.IsError(err, errs.ErrNotFound) {
			err = errs.NewBadRequest("DataAPP does not exist.", err)
		} else {
			err = errs.NewInternalDB("Query DataAPP detail failed. ", err)
		}
		commonError(c, err)
		return
	}
	if dataapp.ProjectId != params.ProjectID {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/external-apps/{external_app_id}/oas[get]-GetOpenApiSpecificationByExternalAppId The current user is not authorized to access the app.", zap.Uint64("app_id", params.AppID), zap.String("user", createdBy))
		commonError(c, errs.NewForbidden("The current user is not authorized to access the app.", nil))
		return
	}

	jwt, _ := getAuth0Token(c, ctx)
	res, err := service.GenerateOpenApiSpecification(
		ctx,
		params.AppID,
		&dataapi.Credential{
			Type: dataapi.CrtType_AUTH0,
			Key:  jwt,
		},
		format,
	)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/external-apps/{external_app_id}/oas[get]-GetOpenApiSpecificationByExternalAppId generate oas failed", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	resp := dto.GetOpenApiSpecificationResp{
		BaseResp: dto.SuccessBaseResp(),
		Data:     res,
	}
	c.JSON(http.StatusOK, resp)
}

// DownloadOpenApiSpecification godoc
// @Summary download oas
// @Description download oas
// @Tags oas
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param format query string true "openapi specification format"
// @Accept json
// @Produce json
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/download [get]
func DownloadOpenApiSpecification(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/download[get]-DownloadOpenApiSpecification param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}
	format := strings.TrimSpace(c.Query("format"))
	err := service.ValidateAppOpenapiFormat(format)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/download[get]-DownloadOpenApiSpecification format validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	jwt, _ := getAuth0Token(c, ctx)
	res, err := service.GenerateOpenApiSpecification(
		ctx,
		params.AppID,
		&dataapi.Credential{
			Type: dataapi.CrtType_AUTH0,
			Key:  jwt,
		},
		format,
	)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/download[get]-DownloadOpenApiSpecification generate oas failed", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", res.Filename))
	c.Data(http.StatusOK, "application/octet-stream", []byte(res.Document))
}

// AuthorizeOpenApiSpecification godoc
// @Summary authorize oas
// @Description authorize oas
// @Tags oas
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CommonApiResp
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/authorize [post]
func AuthorizeOpenApiSpecification(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/authorize[post]-AuthorizeOpenApiSpecification param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}

	err := service.AuthorizeOpenApiSpecification(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/oas/authorize[post]-AuthorizeOpenApiSpecification authorize failed", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}

	resp := dto.SuccessV2()

	c.JSON(http.StatusOK, resp)
}

// Export dataapp openapi doc
// @Summary Export dataapp to openapi doc
// @Description Export dataapp to openapi doc
// @Tags dataapi
// @Accept json
// @Produce json
// @Success 200 {object} dto.DataappPostmanCollection
// @Router /external/appexport/openapi [get]
func ExportDataappToOpenAPI(c *gin.Context) {
	ctx := c.Request.Context()

	format := c.DefaultQuery("format", "json")
	err := service.ValidateAppOpenapiFormat(format)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/external/appexport/openapi[get]-ExportDataappToOpenAPI format validate failed!", zap.String("format", format), zap.Error(err))
		commonError(c, err)
		return
	}

	dataappClaims := c.MustGet(ginparser.CONTEXT_DATAAPP_JWT_CLAIMS).(*dto.DataappJWTClaims)
	if dataappClaims.Sence != "openapi" {
		err := errs.NewBadRequest("The token is not supported in the current scene", nil)
		c.Error(err)
		return
	}

	dataapp, err := service.GetDataAppByExtId(ctx, dataappClaims.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/external/appexport/openapi[get]-GetDataAppByExtId get dataapp failed", zap.String("appid", dataappClaims.AppID), zap.String("user", dataappClaims.Creater), zap.Error(err))
		if errs.IsError(err, errs.ErrNotFound) {
			err = errs.NewBadRequest("DataAPP does not exist.", err)
		} else {
			err = errs.NewInternalDB("Query DataAPP detail failed. ", err)
		}
		commonError(c, err)
		return
	}

	res, err := service.GenerateOpenApiSpecification(
		ctx,
		dataapp.ID,
		&dataapi.Credential{
			Type:      dataapi.CrtType_MIXED,
			Key:       dataappClaims.Creater,
			AccountId: dataappClaims.Creater,
		},
		format,
	)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/external/appexport/openapi[get]-GetOpenApiSpecification generate oas failed",
			zap.String("user", dataappClaims.Creater),
			zap.Error(err),
		)
		commonError(c, err)
		return
	}

	switch format {
	case "yaml":
		c.YAML(http.StatusOK, res.Raw)
	default:
		c.JSON(http.StatusOK, res.Raw)
	}
}
