package api

import (
	"context"
	"fmt"
	"net/http"

	"github.com/tidbcloud/account/internel/dto"

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

	"github.com/jinzhu/copier"

	"github.com/tidbcloud/account/thirdparty/infra"

	orgDao "github.com/tidbcloud/account/internel/dao/org"
	"github.com/tidbcloud/account/internel/svc/admin"
	"github.com/tidbcloud/account/internel/svc/auditlog"
	orgsvc "github.com/tidbcloud/account/internel/svc/org"

	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/admin/forms"
	"github.com/tidbcloud/account/central/jobqueue"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
)

var (
	NonDeletedAndClearedCLusterStatuses = []core.ClusterStatus{
		core.ClusterStatus_CLUSTER_STATUS_INITIALIZING, core.ClusterStatus_CLUSTER_STATUS_PREPARING,
		core.ClusterStatus_CLUSTER_STATUS_LAUNCHING, core.ClusterStatus_CLUSTER_STATUS_NORMAL,
		core.ClusterStatus_CLUSTER_STATUS_SCALING, core.ClusterStatus_CLUSTER_STATUS_RECOVERING,
	}
)

// @Summary Sync Salesforce Leads
// @Tags Salesforce
// @Accept  json
// @Produce json
// @Param org_id path string true "org_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/salesforce [post]
func SalesforceSyncLeads(c *gin.Context) {
	ctx := c.Request.Context()
	req := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	syncReq := config.SyncSalesforceSchema{TenantID: req.OrgID}
	err := jobqueue.Enqueue(ctx, config.JobSyncSalesforce, syncReq)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}
	tilog.Info(ctx, "SalesforceSyncLeads", zap.Any("syncReq", syncReq))

	Success(c, nil, http.StatusOK)
}

// @Summary List Tenant
// @tags Tenant
// @Accept  json
// @Produce json
// @Param page query forms.PaginationReq  true "pagination"
// @Param search query forms.ListTenant  true "search"
// @Success 200 {object} forms.ListTenantResp
// @Router /orgs [get]
func ListTenants(c *gin.Context) {
	ctx := c.Request.Context()
	req := forms.ListTenant{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	limitOffset := GetLimitAndOffset(c)
	userContext := GetUserContext(c)
	param := &admin.ListTenantsReq{
		Filter: admin.TenantFilter{
			ID:     (*entity.ID)(req.TenantFilter.ID),
			Plan:   ParseTenantPlans(req.TenantFilter.Plan),
			Kind:   ParseTenantKinds(req.TenantFilter.Kind),
			Name:   req.TenantFilter.Name,
			Alias:  req.TenantFilter.Alias,
			Email:  req.TenantFilter.Email,
			Status: ParseTenantStatuses(req.TenantFilter.Status),
			OrgTag: req.OrgTag,
		},
		Search:  req.Search,
		Sorter:  admin.BuildSorter(req.SortBy, req.Order),
		Limit:   limitOffset.Limit,
		Offset:  limitOffset.Offset,
		NoLimit: req.NoLimit,
		Brief:   req.Brief,
	}
	tilog.Info(ctx, "ListTenants", zap.Any("param", param))
	dbRecords, total, err := userContext.ListTenants(ctx, param)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}

	items := []forms.Tenant{}
	for _, item := range dbRecords {
		items = append(items, buildAPITenantItem(item))
	}
	result := forms.ListTenantResp{
		Total: total,
		Items: items,
	}

	Success(c, result, http.StatusOK)
}

// @Summary Get Tenant By ID
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Success 200 {object} forms.Tenant
// @Router /orgs/{org_id} [get]
func GetTenantById(c *gin.Context) {
	ctx := c.Request.Context()
	req := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)
	items, _, err := userContext.ListTenants(ctx, &admin.ListTenantsReq{Filter: admin.TenantFilter{ID: (*entity.ID)(&req.OrgID)}, NoLimit: true})
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}

	if len(items) > 0 {
		Success(c, buildAPITenantItem(items[0]), http.StatusOK)
		return
	}

	Fail(c, nil, http.StatusNotFound)
}

// @Summary AddImpersonation
// @tags Impersonation
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/impersonation [post]
func AddImpersonation(c *gin.Context) {
	req := forms.GetOrgIDReq{}
	ctx := c.Request.Context()
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)
	impersonate := admin.ImpersonateReq{OrgID: req.OrgID}
	if err := userContext.AddImpersonation(ctx, &impersonate); err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}
	Success(c, nil, http.StatusOK)
}

// @Summary Delete Impersonation
// @tags Impersonation
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id}/impersonation [delete]
func DeleteImpersonation(c *gin.Context) {
	ctx := c.Request.Context()
	req := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	userContext := GetUserContext(c)
	if err := userContext.DeleteImpersonation(c, req.OrgID); err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}
	tilog.Info(ctx, "DeleteImpersonation", zap.Any("req", req))
	Success(c, nil, http.StatusOK)
}

// @Summary Delete Tenant By ID
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id} [delete]
func DeleteTenant(c *gin.Context) {
	ctx := c.Request.Context()
	req := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	tilog.Info(ctx, "DeleteTenant", zap.Any("req", req))
	svc, _ := orgsvc.GetOrgSvc()
	err := svc.DeleteOrgByID(c, req.OrgID, false)

	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}
	email, _ := GetUserEmail(c)
	//sentry.CaptureMessage(fmt.Sprintf("tenant %d been deleted by %+v, %t", req.OrgId, user, ok))
	tilog.Info(ctx, fmt.Sprintf("tenant %d been deleted by %s", req.OrgID, email))

	Success(c, nil, http.StatusOK)
}

// @Summary Serach Tenant access logs
// @tags AccessLog
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Param search query forms.ListAccessLog  true "search"
// @Param page query forms.PaginationReq  true "page"
// @Success 200 {object} forms.ListAccessLogResp
// @Router /orgs/{org_id}/access-logs [get]
func TenantAccessLogs(c *gin.Context) {
	reqUri := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	req := forms.ListAccessLog{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	req.OrgID = reqUri.OrgID

	limitOffset := GetLimitAndOffset(c)

	userContext := GetUserContext(c)

	tenantID := reqUri.OrgID
	query := auditlog.ListAccessLogQuery{
		StartTimeInSec: req.StartTime,
		EndTimeInSec:   req.EndTime,
		User:           req.User,
		Content:        req.Content,
		From:           limitOffset.Offset,
		Size:           limitOffset.Limit,
	}
	dbRecords, count, err := userContext.ListAccessLog(tenantID, &query)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}

	items := []forms.AccessLog{}
	for _, item := range dbRecords {
		items = append(items, buildAPIAccessLog(item))
	}

	result := forms.ListAccessLogResp{
		Total: count,
		Items: items,
	}

	Success(c, result, http.StatusOK)
}

// @Summary Search Management Portal access logs
// @tags AccessLog
// @Accept  json
// @Produce json
// @Param search query forms.ListAccessLog  true "search"
// @Param page query forms.PaginationReq  true "page"
// @Success 200 {object} forms.ListAccessLogResp
// @Router /access-logs [get]
func ListAccessLogs(c *gin.Context) {
	req := forms.ListAccessLog{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	limitOffset := GetLimitAndOffset(c)
	userContext := GetUserContext(c)

	tenantID := req.OrgID
	query := auditlog.ListAccessLogQuery{
		StartTimeInSec: req.StartTime,
		EndTimeInSec:   req.EndTime,
		User:           req.User,
		Content:        req.Content,
		From:           limitOffset.Offset,
		Size:           limitOffset.Limit,
	}
	dbRecords, count, err := userContext.ListAccessLog(tenantID, &query)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}

	items := []forms.AccessLog{}
	for _, item := range dbRecords {
		items = append(items, buildAPIAccessLog(item))
	}

	result := forms.ListAccessLogResp{
		Total: count,
		Items: items,
	}

	Success(c, result, http.StatusOK)
}

// @Summary UpdateTenant
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @param req body forms.PatchTenantReq true "patch tenant"
// @Success 200 {object} forms.EmptyResponse
// @Router /orgs/{org_id} [PATCH]
func PatchTenant(c *gin.Context) {
	ctx := c.Request.Context()
	reqUri := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&reqUri); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	reqBody := forms.PatchTenantReq{}
	if err := c.ShouldBindJSON(&reqBody); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	tilog.Info(ctx, "PatchTenant req", zap.Any("req", reqBody))
	updateParam := orgDao.UpdateOrgParam{Id: reqUri.OrgID}
	if reqBody.Alias != nil {
		updateParam.Alias = *reqBody.Alias
	}
	if reqBody.Email != nil {
		updateParam.Email = *reqBody.Email
	}
	if reqBody.IsUpdateThirdPartyAccountCode != nil && *reqBody.IsUpdateThirdPartyAccountCode {
		updateParam.ThirdPartyAccountCode = reqBody.ThirdPartyAccountCode
		updateParam.IsUpdateThirdPartyAccountCode = true
	}
	if reqBody.IsUpdateThirdPartyAccountProvider != nil && *reqBody.IsUpdateThirdPartyAccountProvider {
		updateParam.ThirdPartyAccountProvider = reqBody.ThirdPartyAccountProvider
		updateParam.IsUpdateThirdPartyAccountProvider = true
	}

	if reqBody.ThirdPartyFreeTrial != nil {
		updateParam.ThirdPartyFreeTrial = reqBody.ThirdPartyFreeTrial
	}

	if reqBody.OrgTag != nil {
		updateParam.ThirdPartyFreeTrial = reqBody.ThirdPartyFreeTrial
	}
	if reqBody.OrgType != nil && *reqBody.OrgType == string(entity.OrgTypeEnterprise) {
		orgType := entity.OrgTypeEnterprise
		updateParam.OrgType = &orgType
	}
	if reqBody.OrgType != nil && *reqBody.OrgType == string(entity.OrgTypePersonal) {
		orgType := entity.OrgTypePersonal
		updateParam.OrgType = &orgType
	}

	//var kind entity.TenantKind
	//if reqBody.Kind != nil && *reqBody.Kind == string(entity.TenantKindExternal) {
	//	kind = entity.TenantKindExternal
	//} else if reqBody.Kind != nil && *reqBody.Kind == string(entity.TenantKindInternal) {
	//	kind = entity.TenantKindInternal
	//}
	//updateParam.Kind = &kind

	orgSvc, _ := orgsvc.GetOrgSvc()
	err := orgSvc.UpdateOrg(ctx, updateParam)
	if err != nil {
		tilog.Error(ctx, "failed to update org", zap.Error(err))
		Fail(c, err, http.StatusInternalServerError)
	}
	//if err := publish.PublishOrgEvent(ctx, dto.OrgEventData{OrgId: reqUri.OrgID, OrgEventType: entity.EntityEventTypeUpdate}); err != nil {
	//	tilog.Error(ctx, "PublishOrgEvent err", zap.Any("TenantID", reqUri.OrgID), zap.Error(err))
	//}
	Success(c, nil, http.StatusOK)
}

// @Summary GetUserOrgs
// @tags Tenant
// @Accept  json
// @Produce json
// @Param page query forms.GetUserOrgsReq  true "user_id"
// @Success 200 {object} forms.GetUserOrgsResp
// @Router /orgs/user-org [get]
func GetUserOrgs(c *gin.Context) {
	ctx := c.Request.Context()

	req := forms.GetUserOrgsReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetOrgUsers req", zap.Any("req", req))

	orgSvc, _ := orgsvc.GetOrgSvc()
	orgs, err := orgSvc.ListOrgsByUserID(ctx, req.UserId)

	if err != nil {
		tilog.Error(ctx, "failed to ListOrgsByUserID", zap.Error(err))
		Fail(c, err, http.StatusBadRequest)
	}
	items := []forms.Tenant{}
	for _, item := range orgs {
		items = append(items, buildUserOrgItem(item))
	}
	resp := forms.GetUserOrgsResp{Orgs: items}
	tilog.Info(ctx, "GetOrgUsers success", zap.Any("user org size", len(items)))
	Success(c, resp, http.StatusOK)
}

// @Summary GetTenantResource
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Success 200 {object} forms.ResourceResult
// @Router /orgs/{org_id}/data-resource [get]
func GetTenantResource(c *gin.Context) {
	ctx := c.Request.Context()

	req := forms.GetOrgIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetTenantResource req", zap.Any("req", req))
	pids, err := getProjectIds(ctx, req.OrgID)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}
	resourceResult, err := infra.CheckOrgResource(ctx, req.OrgID, pids)
	if err != nil {
		Fail(c, err, http.StatusInternalServerError)
		return
	}
	resp := &forms.ResourceResult{}
	_ = copier.Copy(resp, resourceResult)
	tilog.Info(ctx, "GetTenantResource success", zap.Any("resourceResult", resp))
	Success(c, resp, http.StatusOK)
}

func getProjectIds(ctx context.Context, orgId uint64) ([]uint64, error) {
	var pids []uint64
	params := dto.ListParams{
		Filters: map[string]interface{}{
			"tenant_id": orgId,
			"status":    entity.ProjectStatusActive,
		},
		SortBy: "id",
		Order:  "asc",
	}
	objs, err := comsvc.ListEntities(ctx, params, &entity.Project{})
	if err != nil {

		return pids, err
	}
	projescts := objs.([]entity.Project)

	for _, p := range projescts {
		pids = append(pids, p.ID.Uint64())
	}
	return pids, err
}

// @Summary GetProjectResource
// @tags Tenant
// @Accept  json
// @Produce json
// @Param org_id path string  true "org_id"
// @Success 200 {object} forms.ResourceResult
// @Router /orgs/{org_id}/projects/:project_id/data-resource [get]
func GetProjectResource(c *gin.Context) {
	ctx := c.Request.Context()

	req := forms.GetProjectIDReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}

	tilog.Info(ctx, "GetProjectResource req", zap.Any("req", req))

	resourceResult, err := infra.CheckProjectResource(ctx, req.ProjectID)
	if err != nil {
		Fail(c, err, http.StatusBadRequest)
		return
	}
	resp := &forms.ResourceResult{}
	_ = copier.Copy(resp, resourceResult)
	tilog.Info(ctx, "GetProjectResource success", zap.Any("resourceResult", resp))
	Success(c, resp, http.StatusOK)
}
