package api

import (
	"net/http"

	"github.com/pingcap/dbaas/billing2/model/billingconfig/event_handler"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/tidbcloud/tiinf/sdk/auth"

	"github.com/pingcap/dbaas/billing2/model/unc"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/handler/metricshandler"
	"github.com/pingcap/dbaas/billing2/middleware"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/model/marketing/plan"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/model/payment/spend_limit"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/version"
)

// API processes all API call
type API struct {
}

type VersionResp struct {
	Hash string `json:"hash"`
}

var OpenAPIPathWhitelist = []string{
	"get:/orgs/\\d+/v1beta1/tenants/\\d+/plans",
}

var Path2ResourceMap = map[string]string{
	`/api/v1/billing/tenants/(\d+)/costexplorer.*`:                 `/orgs/{org_id}/billing/bills/view`,
	`get:/api/v1/billing/tenants/(\d+)/payments/stripe/invoices.*`: `/orgs/{org_id}/billing/invoices/view`,
	`get:/api/v1/billing/tenants/(\d+)/payments.*`:                 `/orgs/{org_id}/billing/payments/view`,
	`put:/api/v1/billing/tenants/(\d+)/payments.*`:                 `/orgs/{org_id}/billing/payments/modify`,
	`post:/api/v1/billing/tenants/(\d+)/payments.*`:                `/orgs/{org_id}/billing/payments/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/payments.*`:              `/orgs/{org_id}/billing/payments/modify`,
	`get:/api/v1/billing/tenants/(\d+)/plans`:                      `/orgs/{org_id}/billing/plans/view`,
	`get:/api/v1/billing/tenants/(\d+)/spend_limits.*`:             `/orgs/{org_id}/billing/payments/view`,
	`post:/api/v1/billing/tenants/(\d+)/spend_limits`:              `/orgs/{org_id}/billing/payments/modify`,
	`put:/api/v1/billing/tenants/(\d+)/spend_limits.*`:             `/orgs/{org_id}/billing/payments/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/spend_limits.*`:          `/orgs/{org_id}/billing/payments/modify`,
	`get:/api/v1/billing/tenants/(\d+)/budgets.*`:                  `/orgs/{org_id}/billing/payments/view`,
	`post:/api/v1/billing/tenants/(\d+)/budgets.*`:                 `/orgs/{org_id}/billing/payments/modify`,
	`put:/api/v1/billing/tenants/(\d+)/budgets.*`:                  `/orgs/{org_id}/billing/payments/modify`,
	`patch:/api/v1/billing/tenants/(\d+)/budgets.*`:                `/orgs/{org_id}/billing/payments/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/budgets.*`:               `/orgs/{org_id}/billing/payments/modify`,
	`get:/api/v1/billing/tenants/(\d+)/billing_info.*`:             `/orgs/{org_id}/billing/billing_info/view`,
	`put:/api/v1/billing/tenants/(\d+)/billing_info.*`:             `/orgs/{org_id}/billing/billing_info/modify`,
	`post:/api/v1/billing/tenants/(\d+)/billing_info.*`:            `/orgs/{org_id}/billing/billing_info/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/billing_info.*`:          `/orgs/{org_id}/billing/billing_info/modify`,
	`get:/api/v1/billing/tenants/(\d+)/credits.*`:                  `/orgs/{org_id}/billing/credits/view`,

	//stripe invoice paying
	// /paying is a Finance-Ops admin endpoint and not org-scoped, so the
	// resource URI deliberately omits {org_id}. Otherwise the variable
	// count in the path pattern does not match the resource URI and
	// NewMapStoreResourceHelper panics. The `:id` route keeps one
	// placeholder ({paying_id}) since the path captures (\d+) once. See
	// TestValidateRBACPath.
	`get:/api/v1/billing/paying.*`:                                      `/billing/payments/view`,
	`post:/api/v1/billing/paying`:                                       `/billing/payments/modify`,
	`post:/api/v1/billing/paying/(\d+)`:                                 `/billing/payments/{paying_id}/modify`,
	`get:/api/v1/billing/tenants/(\d+)/users/contracts.*`:               `/orgs/{org_id}/billing/contracts/view`,
	`get:/api/v1/billing/tenants/(\d+)/contracts/\d+/download`:          `/orgs/{org_id}/billing/contracts/view`,
	`put:/api/v1/billing/tenants/(\d+)/users/contracts/.*`:              `/orgs/{org_id}/billing/contracts/modify`,
	`post:/api/v1/billing/tenants/(\d+)/users/contracts.*`:              `/orgs/{org_id}/billing/contracts/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/users/contracts/.*`:           `/orgs/{org_id}/billing/contracts/modify`,
	`get:/api/v1/billing/tenants/(\d+)/binded_support_plan.*`:           `/orgs/{org_id}/billing/support_plan/view`,
	`put:/api/v1/billing/tenants/(\d+)/binded_support_plan/public/.*`:   `/orgs/{org_id}/billing/support_plan/modify`,
	`post:/api/v1/billing/tenants/(\d+)/binded_support_plan/public.*`:   `/orgs/{org_id}/billing/support_plan/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/binded_support_plan/public.*`: `/orgs/{org_id}/billing/support_plan/modify`,
	`get:/api/v1/billing/tenants/(\d+)/cost_quota`:                      "/orgs/{org_id}/billing/cost_quota/view",
	`put:/api/v1/billing/tenants/(\d+)/cost_quota`:                      "/orgs/{org_id}/billing/cost_quota/modify",
	`get:/api/v1/billing/support_plan.*`:                                `/billing/support_plan/view`,
	`get:/api/v1/billing/tenants/(\d+)/switch/.*`:                       `/orgs/{org_id}/billing/payments/view`,
	`get:/api/v1/billing/tenants/(\d+)/users/discounts`:                 `/orgs/{org_id}/billing/discounts/view`,
	`get:/api/v1/billing/tenants/(\d+)/referral/.*`:                     `/orgs/{org_id}/billing/credits/view`,
	`get:/api/v1/billing/tenants/(\d+)/promotion/.*`:                    `/orgs/{org_id}/billing/credits/view`,
	`post:/api/v1/billing/tenants/(\d+)/billing_action_check.*`:         `/orgs/{org_id}/billing/credits/view`,

	`/api/v1/billing/tenants/(\d+)/billing_configs.*`:                                    `/orgs/{org_id}/billing/bills/view`,
	`/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/costexplorer.*`:                 `/orgs/{org_id}/billing/bills/view`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/payments/stripe/invoices.*`: `/orgs/{org_id}/billing/invoices/view`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/payments.*`:                 `/orgs/{org_id}/billing/payments/view`,
	`put:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/payments.*`:                 `/orgs/{org_id}/billing/payments/modify`,
	`post:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/payments.*`:                `/orgs/{org_id}/billing/payments/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/payments.*`:              `/orgs/{org_id}/billing/payments/modify`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/credits.*`:                  `/orgs/{org_id}/billing/credits/view`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/users/contracts.*`:          `/orgs/{org_id}/billing/contracts/view`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/contracts/\d+/download`:     `/orgs/{org_id}/billing/contracts/view`,
	`put:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/users/contracts/.*`:         `/orgs/{org_id}/billing/contracts/modify`,
	`post:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/users/contracts.*`:         `/orgs/{org_id}/billing/contracts/modify`,
	`delete:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/users/contracts/.*`:      `/orgs/{org_id}/billing/contracts/modify`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/binded_support_plan.*`:      `/orgs/{org_id}/billing/support_plan/view`,
	`get:/api/v1/billing/tenants/(\d+)/billing_config_id/\d+/users/discounts`:            `/orgs/{org_id}/billing/discounts/view`,
	`get:/api/v1/unc/tenants/(\d+)/notifications/.*`:                                     `/orgs/{org_id}/notifications/view`,
	`post:/api/v1/unc/tenants/(\d+)/notifications/.*`:                                    `/orgs/{org_id}/notifications/modify`,

	// open api
	`get:/orgs/(\d+)/v1beta1/bills.*`:                          `/orgs/{org_id}/billing/bills/view`,
	`post:/orgs/(\d+)/v1beta1/billsCostExplorer`:               `/orgs/{org_id}/billing/bills/view`,
	`/v1beta1/msp/tenants/(\d+)/bills.*`:                       `/orgs/{org_id}/billing/bills/view`,
	`/v1beta1/tenants/(\d+)/bills.*`:                           `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/partners/bills.*`:                 `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/partners/billsDetails.*`:          `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/partners/billsCostExplorerArgs`:   `/orgs/{org_id}/billing/bills/view`,
	`post:/orgs/(\d+)/v1beta1/partners/billsCostExplorer.*`:    `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/partners/partnerConfigs`:          `/orgs/{org_id}/billing/partnerConfigs/view`,
	`patch:/orgs/(\d+)/v1beta1/partners/partnerConfigs`:        `/orgs/{org_id}/billing/partnerConfigs/modify`,
	`get:/orgs/(\d+)/v1beta1/partners/credits`:                 `/orgs/{org_id}/billing/credits/view`,
	`get:/orgs/(\d+)/v1beta1/partners/discounts`:               `/orgs/{org_id}/billing/discounts/view`,
	`get:/orgs/(\d+)/v1beta1/partners/payments`:                `/orgs/{org_id}/billing/payments/view`,
	`get:/orgs/\d+/v1beta1/partners/publicSupportPlans`:        `/billing/support_plan/view`,
	`get:/orgs/(\d+)/v1beta1/partners/boundSupportPlans`:       `/orgs/{org_id}/billing/support_plan/view`,
	`get:/orgs/(\d+)/v1beta1/partners/billingProfile`:          `/orgs/{org_id}/billing/billing_info/view`,
	`patch:/orgs/(\d+)/v1beta1/partners/billingProfile`:        `/orgs/{org_id}/billing/billing_info/modify`,
	`get:/orgs/(\d+)/v1beta1/partners/budgets.*`:               `/orgs/{org_id}/billing/payments/view`,
	`post:/orgs/(\d+)/v1beta1/partners/budgets.*`:              `/orgs/{org_id}/billing/payments/modify`,
	`patch:/orgs/(\d+)/v1beta1/partners/budgets.*`:             `/orgs/{org_id}/billing/payments/modify`,
	`delete:/orgs/(\d+)/v1beta1/partners/budgets.*`:            `/orgs/{org_id}/billing/payments/modify`,
	`get:/orgs/(\d+)/v1beta1/partners/swaggerDoc`:              `/orgs/{org_id}/billing/partner/doc/view`,
	`get:/orgs/(\d+)/v1beta1/partners/userManualDoc`:           `/orgs/{org_id}/billing/partner/doc/view`,
	`get:/orgs/(\d+)/v1beta1/partners/invoicedBills.*`:         `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/partners/invoicedBillsDetails.*`:  `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/billsDetails/daily/.*/presignUrl`: `/orgs/{org_id}/billing/bills/view`,
	`get:/orgs/(\d+)/v1beta1/billsDetails/.*/presignUrl`:       `/orgs/{org_id}/billing/bills/view`,
}

// NewService create the gin engine and setup router
// @title Billing System  API
// @version 1.0
// @description This is a billing system server
// @host 127.0.0.1:18888
// @BasePath /api/v1/billing
func NewService() (*gin.Engine, error) {
	r := gin.Default()

	//Hold it! Don't move! befor  r.Croup group.engin   and use middleware .
	metricshandler.RegisterMetricsRoute(r)
	auth.InitAccountClientByEndpoint(config.Get().GetString(config.AccountSvcAddress), 0)

	r.Use(
		ginutils.ErrorMiddleWare,
		middleware.HeaderMiddleware,
	)

	r.GET("/version", func(c *gin.Context) { c.JSON(http.StatusOK, &VersionResp{Hash: version.GitHash}) })
	billingGroup := r.Group("/api/v1/billing")
	ws := ginutils.MyRoute{Route: billingGroup}
	ws.Use(
		middleware.UserMiddleware,
		middleware.NewRBACMiddleWare(Path2ResourceMap),
	)
	RegisterRouter(ws)

	innerGroup := r.Group("/inner/billing")
	innerWS := ginutils.MyRoute{Route: innerGroup}
	RegisterInnerAPIRouter(innerWS)

	billingOpenAPIGroup := r.Group("/v1beta1")
	wsOpenAPI := ginutils.MyRoute{Route: billingOpenAPIGroup}
	wsOpenAPI.Use(
		middleware.CorsMiddleware(),
		auth.GinGetIdentityInfoInterceptor(&auth.IdentityAuthOption{
			EnableLog: true,
		}),
		auth.GinPermissionCheckInterceptor(&auth.PermissionCheckerCommonParam{
			IdentityF:               auth.GinGetIdentityFunc,
			PathPattern2ResourceMap: Path2ResourceMap,
			GenAccessParam4GinF:     auth.GenAccessParam4GinWithPrefixOrgId,
			PathWhitelist:           OpenAPIPathWhitelist,
		}),
	)
	RegisterOpenAPIRouter(wsOpenAPI)

	mspBillingOpenAPIGroup := r.Group("/v1beta1/msp")
	wsMspOpenAPI := ginutils.MyRoute{Route: mspBillingOpenAPIGroup}
	wsMspOpenAPI.Use(
		middleware.OpenAPIPathNormalize(r),
	)
	RegisterMSPOpenAPIRouter(wsMspOpenAPI)

	partnerBillingOpenAPIGroup := r.Group("/v1beta1/partners")
	wsPartnerOpenAPI := ginutils.MyRoute{Route: partnerBillingOpenAPIGroup, DisableFilterByQueryMiddleWare: true}
	wsPartnerOpenAPI.Use(
		middleware.CorsMiddleware(),
		middleware.RemoveApiKeyHeaderMiddleware(),
		auth.GinGetIdentityInfoInterceptor(&auth.IdentityAuthOption{
			EnableLog: true,
		}),
		auth.GinPermissionCheckInterceptor(&auth.PermissionCheckerCommonParam{
			IdentityF:               auth.GinGetIdentityFunc,
			PathPattern2ResourceMap: Path2ResourceMap,
			GenAccessParam4GinF:     auth.GenAccessParam4GinWithPrefixOrgId,
		}),
	)
	wsPartnerOpenAPI.OPTIONS("/*any")
	RegisterPartnerOpenAPIRouter(wsPartnerOpenAPI)
	RegisterPartnerPrivateAPIRouter(wsPartnerOpenAPI)

	uncBillingOpenAPIGroup := r.Group("/api/v1/unc")
	uncAPI := ginutils.MyRoute{Route: uncBillingOpenAPIGroup}
	uncAPI.Use(
		middleware.UserMiddleware,
		middleware.NewRBACMiddleWare(Path2ResourceMap),
	)
	RegisterUNCAPIRouter(uncAPI)
	return r, nil
}

func RegisterRouter(ws ginutils.MyRoute) {
	//measures /customers/:customer/units
	InitializeMeasureRouter(ws)

	//units /units
	InitializeUnitRouter(ws)

	//packages /packages /tenants/:tenant_id/binded_packages
	InitializePackageRouter(ws)

	//payments /tenants/:tenant_id/payments
	InitializePaymentRouter(ws)

	// fx rates /fx_rates
	InitializeFXRateRouter(ws)

	//invoices /tenants/:tenant_id/invoices
	InitializeInvoiceRouter(ws)

	//snapshot /tenants/:tenant_id/billing_config_id/:billing_config_id/snapshot
	InitializeSnapshotRouter(ws)

	//costexplorer /tenants/:tenant_id/costexplorer
	InitializeCostExplorerRouter(ws)

	// Migrate Task /migrate_tasks
	InitializeMigrateRouter(ws)

	//subscription /tenants/:tenant_id/subscription
	InitializeSubscriptionRouter(ws)

	//billing_info /tenants/:tenant_id/billing_info
	InitializeBillingInfoRouter(ws)

	//tax_rates /tenants/:tenant_id/tax_rates
	InitializeTaxRateRouter(ws)

	//support_plan /tenants/:tenant_id/support_plan
	InitializeSupportPlanRouter(ws)

	//credit_type /credit_types
	InitializeUnitFilterRouter(ws)

	//credits /tenants/:tenant_id/credits
	InitializeCreditsRouter(ws)

	//risk_level /tenants/:tenant_id/risk_level
	InitializeCreditRiskLevelRouter(ws)

	//risk_level /tenants/:tenant_id/billing_action_check
	InitializeControlRouter(ws)

	//cost_quota /tenants/:tenant_id/cost_quota
	InitializeCostQuotaRouter(ws)

	//worker_action /worker/action
	InitializeWorkerRunActionRouter(ws)

	//auditlog /auditlog
	InitializeAuditLogRouter(ws)

	//contract //tenants/:tenant_id/contracts
	InitializeContractRouter(ws)

	//credit_risk_levels/:credit_risk_level_id/risk_close_policy //tenants/:tenant_id/tenant_risk_close
	InitializeTenantCreditRiskCloseRouter(ws)

	// test for error record and alert in sentry
	// sentry_validate.go /sentry/validate
	InitializeSentryValidateRouter(ws)

	InitializeSwitchRouter(ws)
	InitializeUsageExplorerRouter(ws)
	// tenant plans /tenants/:tenant_id/plans
	InitializeTenantPlanRouter(ws)

	InitializeTenantDiscountRouter(ws)

	// /recharge_plans  /tenants/:tenant_id/recharge_plans
	InitializeRechargePlanRouter(ws)

	// spend limits /tenants/:tenant_id/spend_limits
	InitializeSpendLimitRouter(ws)
	// budget
	InitializeBudgetRouter(ws)
	//Commodity  /commodity
	InitializeCommodityRouter(ws)
	// event publish /events
	InitializeEventRouter(ws)

	// generate cr table /cr_tables
	InitializeARTableRouter(ws)

	InitializeBillingConfigRouter(ws)
	InitializeMarketplaceRouter(ws)

	InitializeReferralUserRouter(ws)

	InitializeSKUGroupsRouter(ws)

	InitializeAliyunSubscriptionMngtRouter(ws)

	InitializeAWSSubscriptionMngtRouter(ws)

	// for the partner bills visibility & analysis on ops
	InitializeCostExplorerForPartnerOpenAPIRouter(ws)
	InitializeCostExplorerForPartnerPrivateAPIRouter(ws)

	// stripe invoice paying /paying
	InitializeStripeInvoicePayingRouter(ws)
}

func RegisterEventHandler() error {
	if !config.Get().GetBool(config.EnableEventbus) {
		return nil
	}
	err := (&plan.TenantPlanModel{}).RegisterEventHandler()
	if err != nil {
		return err
	}
	err = (&payment.InvoiceModel{}).RegisterMarketplaceEventHandler()
	if err != nil {
		return err
	}
	err = (&paymentsub.PaymentModel{}).RegisterPaymentEventHandler()
	if err != nil {
		return err
	}
	err = (&marketing.RechargeCreditsModel{}).RegisterEventHandler()
	if err != nil {
		return err
	}
	err = (&marketing.SupportPlanModel{}).RegisterSupportPlanEventHandler()
	if err != nil {
		return err
	}
	err = (&spend_limit.SpendLimitModel{}).RegisterSpendLimitHandler()
	if err != nil {
		return err
	}
	err = (&event_handler.BillingConfigEventHandler{}).RegisterEventHandler()
	if err != nil {
		return err
	}
	err = (&unc.NotificationModel{}).RegisterEventHandler()
	if err != nil {
		return err
	}
	err = marketing.RegisterMarketEventHandler()
	if err != nil {
		return err
	}
	return nil
}

func NewWorkerService() (*gin.Engine, error) {
	r := gin.Default()
	r.GET("/version", func(c *gin.Context) { c.JSON(http.StatusOK, &VersionResp{Hash: version.GitHash}) })
	metricshandler.RegisterMetricsRoute(r)
	return r, nil
}
