package api

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/tidbcloud/account/internel/svc/comsvc"

	"github.com/tidbcloud/account/internel/controllers/openapi"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/central/server"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/rbac/useadapter"
	"go.uber.org/zap"
	"google.golang.org/grpc/metadata"
)

const (
	openApiAccessApiKey = "openapi_access_api_key"

	authenticateErrorCode   = "AuthenticateError"
	authorizeErrorCode      = "AuthorizeError"
	paramInvalidErrorCode   = "ParamInvalidError"
	internalServerErrorCode = "InternalServerError"
)

const (
	createApiKeyResourceUri     = "account/openapi/orgs/{org_id}/create_apikey"
	getApiKeyResourceUri        = "account/openapi/orgs/{org_id}/get_apikey"
	listApiKeyResourceUri       = "account/openapi/orgs/{org_id}/list_apikey"
	patchApiKeyResourceUri      = "account/openapi/orgs/{org_id}/patch_apikey"
	deleteApiKeyResourceUri     = "account/openapi/orgs/{org_id}/delete_apikey"
	listApiKeyRolesResourceUri  = "account/openapi/orgs/{org_id}/list_apikey_roles"
	updateApiKeyRoleResourceUri = "account/openapi/orgs/{org_id}/update_apikey_role"

	createProjectApiKeyResourceUri = "account/openapi/orgs/{org_id}/projects/{project_id}/create_apikey"
	getProjectApiKeyResourceUri    = "account/openapi/orgs/{org_id}/projects/{project_id}/get_apikey"
	listProjectApiKeyResourceUri   = "account/openapi/orgs/{org_id}/projects/{project_id}/list_apikey"
	patchProjectApiKeyResourceUri  = "account/openapi/orgs/{org_id}/projects/{project_id}/patch_apikey"
	deleteProjectApiKeyResourceUri = "account/openapi/orgs/{org_id}/projects/{project_id}/delete_apikey"

	getMspCustomerResourceUri             = "account/openapi/orgs/{org_id}/get_msp_customer"
	listMspCustomerResourceUri            = "account/openapi/orgs/{org_id}/list_msp_customer"
	deleteMspCustomerResourceUri          = "account/openapi/orgs/{org_id}/delete_msp_customer"
	createMspCustomerSignupUrlResourceUri = "account/openapi/orgs/{org_id}/create_msp_customer_signup_url"
)

func openApiRouteAuthNFunc(c *gin.Context) {
	authContent := c.Request.Header.Get("X-Auth-Content")

	ctx := metadata.NewIncomingContext(context.Background(), metadata.New(map[string]string{
		"X-Auth-Content": authContent,
	}))

	ctx, err := server.AuthFuncOpenAPI(ctx)
	if err != nil {
		tilog.Info(c.Request.Context(), "openApiRouteAuthNFunc authn error", zap.Any("authContent", authContent), zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusUnauthorized, authenticateErrorCode, "authenticate fail")
		c.Abort()
		return
	}

	c.Set(openApiAccessApiKey, ctx.Value(openapi.OpenAPIAccessKey))

	tilog.Info(c.Request.Context(), "openApiRouteAuthNFunc authn success", zap.Any("authContent", authContent))
	c.Next()
}

func checkAccessKeyPermission(ctx context.Context, ak, resource string, orgId, projectId uint64) (bool, error) {
	resourceParam := map[string]string{
		"org_id":     fmt.Sprintf("%d", orgId),
		"project_id": fmt.Sprintf("%d", projectId),
	}
	hasPermit, msg, err := useadapter.CheckApiKeyPermission(ctx, ak, resource, resourceParam)

	tilog.Info(ctx, fmt.Sprintf("checkAccessKeyPermission finished, ak: %s, resource: %s, orgId: %d", ak, resource, orgId),
		zap.Any("hasPermission", hasPermit), zap.Any("msg", msg), zap.Any("err", err))

	if err != nil {
		return false, err
	}
	return hasPermit, nil
}

func writeError(ctx context.Context, rw http.ResponseWriter, err error, status int, code string, desc string) {

	openApiWriteError(ctx, rw, OpenApiError{
		error:     WrapError(err),
		Code:      code,
		Status:    status,
		MsgPrefix: desc,
	})
}

func openApiWriteError(ctx context.Context, rw http.ResponseWriter, err OpenApiError) {

	tilog.Info(ctx, "openapi v1beta1 response", zap.Any("", err))
	rw.Header().Set("Content-Type", "application/json")
	rw.WriteHeader(err.Status)

	raw, _ := json.Marshal(err)
	_, _ = rw.Write(raw)
}

func openApiWriteResult(rw http.ResponseWriter, ret interface{}) {
	rw.Header().Set("Content-Type", "application/json")
	raw, _ := json.Marshal(ret)
	_, _ = rw.Write(raw)
}

func getApiKeyFromGinContext(c *gin.Context) *account.GetApiKeyByAccessKeyRsp {
	cValue, exists := c.Get(openApiAccessApiKey)
	if !exists {
		return nil
	}
	keyRsp, ok := cValue.(*account.GetApiKeyByAccessKeyRsp)
	if !ok {
		return nil
	}
	return keyRsp
}

func getAccessKeyAndOrgId(c *gin.Context) (*comsvc.Operator, uint64, error) {
	keyRet := getApiKeyFromGinContext(c)
	if keyRet == nil {
		return nil, 0, errors.New("not found access key in context")
	}
	if len(keyRet.ResourceInfos) == 0 {
		return nil, 0, errors.New("not found access key relate resource info in context")
	}
	var orgId uint64 = 0
	for _, r := range keyRet.ResourceInfos {
		if r.ScopeType == entity.RoleBindingScopeOrg.String() {
			orgId = r.ResourceId
			break
		}
	}
	if orgId == 0 {
		return nil, 0, errors.New("not found access key relate org info in context")
	}
	tilog.Info(c.Request.Context(), "getAccessKeyAndOrgId", zap.Any("accessKey", keyRet.ApiKey.AccessKey), zap.Any("orgId", orgId))
	return &comsvc.Operator{
		OpTye: entity.RoleBindingTargetAPIKey,
		OpID:  keyRet.ApiKey.Id,
		OpVal: keyRet.ApiKey.AccessKey,
	}, orgId, nil
}
