package central

import (
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/samber/lo"
	centralapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/central"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/api/types"
	apiutils "github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/api/utils"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc"
	coregrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/core_portal"
	globaldedicatedv1grpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/dedicated"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func GetAuditLogStatus(ctx *gin.Context) {
	req := &centralapi.GetAuditLogStatusReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		resp, err := globaldedicatedv1grpc.GetAuditLogConfig(grpc.NewContext(ctx), &globaldedicatedv1.GetAuditLogConfigRequest{
			ClusterId: req.ClusterId,
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.GetAuditLogStatusResp{
			Enabled: resp.GetEnabled(),
			AuditLogConfig: &centralapi.AuditLogConfig{
				BucketUrl:      resp.BucketUrl,
				Region:         resp.Region,
				RoleArn:        resp.RoleArn,
				DataSourceType: 0, // Deprecated in 3-layer arch (The Frontend does not use this field in 3-layer arch, and use it in cloud native arch)
			},
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.GetAuditLogStatus(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func UpdateAuditLogStatus(ctx *gin.Context) {
	req := &centralapi.UpdateAuditLogStatusReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		resp, err := globaldedicatedv1grpc.UpdateAuditLogConfig(grpc.NewContext(ctx), &globaldedicatedv1.UpdateAuditLogConfigRequest{
			AuditLogConfig: &globaldedicatedv1.AuditLogConfig{
				ClusterId: req.ClusterId,
				Enabled:   lo.ToPtr(req.Enabled),
				BucketUrl: req.GetAuditLogConfig().BucketUrl,
				Region:    req.GetAuditLogConfig().Region,
				RoleArn:   req.GetAuditLogConfig().RoleArn,
			},
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.UpdateAuditLogStatusResp{
			Enabled:  resp.GetEnabled(),
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.UpdateAuditLogStatus(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func PrecheckAuditLogConfig(ctx *gin.Context) {
	req := &centralapi.PrecheckAuditLogConfigReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		resp, err := globaldedicatedv1grpc.PrecheckAuditLogConfig(grpc.NewContext(ctx), &globaldedicatedv1.PrecheckAuditLogConfigRequest{
			AuditLogConfig: &globaldedicatedv1.AuditLogConfig{
				ClusterId: req.ClusterId,
				BucketUrl: req.GetAuditLogConfig().BucketUrl,
				Region:    req.GetAuditLogConfig().Region,
				RoleArn:   req.GetAuditLogConfig().RoleArn,
			},
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.PrecheckAuditLogConfigResp{
			Valid:    resp.Valid,
			Message:  resp.Message,
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.PrecheckAuditLogConfig(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func CheckAuditLogPluginStatus(ctx *gin.Context) {
	req := &centralapi.CheckAuditLogPluginStatusReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		resp, err := globaldedicatedv1grpc.GetAuditLogConfig(grpc.NewContext(ctx), &globaldedicatedv1.GetAuditLogConfigRequest{
			ClusterId: req.ClusterId,
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.CheckAuditLogPluginStatusResp{
			Enabled:  resp.GetEnabled(),
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.CheckAuditLogPluginStatus(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func GetAuditLogAccessRecords(ctx *gin.Context) {
	req := &centralapi.GetAuditLogAccessRecordsReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		resp, err := globaldedicatedv1grpc.ListAuditLogFilterRules(grpc.NewContext(ctx), &globaldedicatedv1.ListAuditLogFilterRulesRequest{
			ClusterId: req.ClusterId,
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.GetAuditLogAccessRecordsResp{
			Records: lo.Map(resp.GetRules(), func(record *globaldedicatedv1.AuditLogFilterRule, index int) *centralapi.AuditLogAccessRecord {
				return &centralapi.AuditLogAccessRecord{
					Id:             record.Id,
					UserExpr:       record.UserExpr,
					DbExpr:         record.DbExpr,
					TableExpr:      record.TableExpr,
					AccessTypeList: record.AccessTypeList,
				}
			}),
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.GetAuditLogAccessRecords(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func CreateAuditLogAccessRecord(ctx *gin.Context) {
	req := &centralapi.CreateAuditLogAccessRecordReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		_, err := globaldedicatedv1grpc.CreateAuditLogFilterRule(grpc.NewContext(ctx), &globaldedicatedv1.CreateAuditLogFilterRuleRequest{
			Rule: &globaldedicatedv1.AuditLogFilterRule{
				ClusterId:      req.ClusterId,
				Id:             req.GetRecord().Id,
				UserExpr:       req.GetRecord().UserExpr,
				DbExpr:         req.GetRecord().DbExpr,
				TableExpr:      req.GetRecord().TableExpr,
				AccessTypeList: req.GetRecord().AccessTypeList,
			},
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.CreateAuditLogAccessRecordResp{
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.CreateAuditLogAccessRecord(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func DeleteAuditLogAccessRecord(ctx *gin.Context) {
	req := &centralapi.DeleteAuditLogAccessRecordReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.UnauthorizedResp(ctx, err)
		return
	}
	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid audit record id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId
	req.ClusterId = clusterId
	req.Id = id

	// check is new serverless cluster
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		// is serverless cluster
		types.SuccessResp(ctx, &centralapi.DefaultResp{
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodePermission),
				ErrMsg:  "serverless cluster does not support db audit log",
			},
		})
		return
	}

	// check is new dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok { // is new dedicated cluster
		_, err := globaldedicatedv1grpc.DeleteAuditLogFilterRule(grpc.NewContext(ctx), &globaldedicatedv1.DeleteAuditLogFilterRuleRequest{
			ClusterId: req.ClusterId,
			Uk: &globaldedicatedv1.DeleteAuditLogFilterRuleRequest_Id{
				Id: req.Id,
			},
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		successResp := &centralapi.DeleteAuditLogAccessRecordResp{
			BaseResp: &centralapi.BaseResp{},
		}
		types.SuccessResp(ctx, successResp)
		return
	}

	// core-portal
	resp, err := coregrpc.DeleteAuditLogAccessRecord(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}
