package api

import (
	"encoding/json"
	"io/ioutil"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
)

var packageModel = &billing.PackageModel{}
var bindedPackageModel = &billing.BindedPackageModel{}

// @Summary Create Package
// @Schemes
// @Accept json
// @Produce json
// @Param Package body forms.PackageInfoInput true "Package"
// @Success 200 {object} forms.PackageInfo
// @Router /packages [post]
func (s *API) CreatePacakge(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	packageInfo := &forms.PackageInfo{}
	err := c.ShouldBindJSON(packageInfo)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	info, err := packageModel.CreatePackage(packageInfo)
	if err != nil {
		return nil, err
	}
	return info, nil
}

type PackageIDStruct struct {
	PackageID uint `uri:"package_id" binding:"required"`
}

// @Summary Update Package
// @Schemes
// @Accept json
// @Produce json
// @Param package_id path string true "package_id"
// @Param Package body forms.PackageInfoInput true "Package"
// @Success 200 {object} forms.PackageInfo
// @Router /packages/{package_id} [put]
func (s *API) UpdatePackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	packageInfo := &forms.PackageInfo{}
	packageIDStruct := &PackageIDStruct{}
	err := c.ShouldBindUri(packageIDStruct)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	packageID := packageIDStruct.PackageID

	// for result will be read more than once
	body, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	err = json.Unmarshal(body, packageInfo)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	info, err := packageModel.UpdatePackage(packageID, packageInfo)
	if err != nil {
		return nil, err
	}
	return info, nil

}

// @Summary Patch Package
// @Schemes
// @Accept json
// @Produce json
// @Param package_id path string true "package_id"
// @Param Package body forms.PackageInfoInput true "Package"
// @Success 200 {object} forms.PackageInfo
// @Router /packages/{package_id} [patch]
func (s *API) PatchPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	packageInfo := &forms.PackageInfo{}
	packageIDStruct := &PackageIDStruct{}
	err := c.ShouldBindUri(packageIDStruct)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	packageID := packageIDStruct.PackageID

	// for result will be read more than once
	body, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	err = json.Unmarshal(body, packageInfo)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	mapInput := gin.H{}
	err = json.Unmarshal(body, &mapInput)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	dest, err := packageModel.GetPackage(packageID)
	if err != nil {
		return nil, err
	}
	ginutils.PartialMerge(dest, packageInfo, mapInput)

	info, err := packageModel.UpdatePackage(packageID, dest)
	if err != nil {
		return nil, err
	}
	return info, nil

}

// @Summary List Package
// @Schemes
// @Accept json
// @Produce json
// @Success 200 {object} []forms.PackageInfo
// @Router /packages [get]
func (s *API) ListPackages(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	packages, err := packageModel.GetAllPackages()
	if err != nil {
		return nil, err
	}
	return packages, nil
}

// @Summary List Package
// @Schemes
// @Accept json
// @Produce json
// @Success 200 {object} forms.PackageInfo
// @Router /packages/{package_id} [get]
func (s *API) GetPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	packageIDStruct := &PackageIDStruct{}
	err := c.ShouldBindUri(packageIDStruct)
	if err != nil {
		err = billing_errors.NewInvalidInputError(err)
		return nil, err
	}
	packageID := packageIDStruct.PackageID
	info, err := packageModel.GetPackage(packageID)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Delete Package
// @Schemes
// @Accept json
// @Produce json
// @Param package_id path string true "package_id"
// @Success 200 {object} forms.OKType
// @Router /packages/{package_id} [delete]
func (s *API) DeletePackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	packageIDStruct := &PackageIDStruct{}
	err := c.ShouldBindUri(packageIDStruct)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	packageID := packageIDStruct.PackageID
	err = packageModel.DeletePackage(packageID)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

type BindedPackageURI struct {
	TenantID        uint `uri:"tenant_id" binding:"required"`
	BindedPacakgeID uint `uri:"binded_package_id"`
}

// @Summary Bind Package
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param Package body forms.BindedPackageInfoInput true "Package"
// @Success 200 {object} forms.BindedPackageInfo
// @Router /tenants/{tenant_id}/binded_packages [post]
func (s *API) BindUserPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	bindedPackage := forms.BindedPackageInfo{}
	err = c.ShouldBindJSON(&bindedPackage)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	info, err := bindedPackageModel.BindPackage(bindedPackageURI.TenantID, bindedPackage.PackageID)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary List Binded Package
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} []forms.BindedPackageInfo
// @Router /tenants/{tenant_id}/binded_packages [get]
func (s *API) ListUserBindedPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	infos, err := bindedPackageModel.GetAllUserBindedPackage(bindedPackageURI.TenantID)
	if err != nil {
		return nil, err
	}
	return infos, nil
}

// @Summary Get Binded Package
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.BindedPackageInfo
// @Router /tenants/{tenant_id}/binded_packages/{binded_package_id} [get]
func (s *API) GetUserBindedPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	info, err := bindedPackageModel.GetBindedPackage(bindedPackageURI.BindedPacakgeID)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Unbind Package
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/binded_packages/{binded_package_id} [delete]
func (s *API) UnbindUserPackage(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	err = bindedPackageModel.UnbindPackage(bindedPackageURI.BindedPacakgeID)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Charge Credits
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param ChargeCredits body forms.CreditsChargeInfoInput true "ChargeCredits"
// @Success 200 {object} forms.BindedPackageInfo
// @Router /tenants/{tenant_id}/binded_packages/{binded_package_id}/charge_credits [post]
func (s *API) BindedPackageChargeCredits(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	chargeInfo := &forms.CreditsChargeInfo{}
	err = c.ShouldBindJSON(chargeInfo)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	info, err := bindedPackageModel.ChargeCredits(bindedPackageURI.BindedPacakgeID, chargeInfo)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Charge Credits History
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param ChargeCredits body forms.CreditsChargeInfoInput true "ChargeCredits"
// @Success 200 {object} forms.CreditsChargeHistory
// @Router /tenants/{tenant_id}/binded_packages/{binded_package_id}/charge_credits [get]
func (s *API) GetChargeCreditsHistory(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	return bindedPackageModel.GetChargeCreditsHistory(bindedPackageURI.BindedPacakgeID)
}

// @Summary Ubbind Package
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param Discount body forms.ChangeBindedDiscountInput true "Discount"
// @Success 200 {object} forms.BindedPackageInfo
// @Router /tenants/{tenant_id}/binded_packages/{binded_package_id}/discount [put]
func (s *API) BindedPackageChangeDiscount(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	bindedPackageURI := &BindedPackageURI{}
	err := c.ShouldBindUri(bindedPackageURI)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	changeInfo := &forms.ChangeBindedDiscountInput{}
	err = c.ShouldBindJSON(changeInfo)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	info, err := bindedPackageModel.ChangeBindedDiscount(bindedPackageURI.BindedPacakgeID, changeInfo)
	if err != nil {
		return nil, err
	}
	return info, nil
}

func InitializePackageRouter(router ginutils.MyRoute) {
	api := &API{}

	router.HandleWithError("POST", "/packages", api.CreatePacakge)
	router.HandleWithError("GET", "/packages", api.ListPackages)
	router.HandleWithError("PUT", "/packages/:package_id", api.UpdatePackage)
	router.HandleWithError("PATCH", "/packages/:package_id", api.PatchPackage)
	router.HandleWithError("GET", "/packages/:package_id", api.GetPackage)
	router.HandleWithError("DELETE", "/packages/:package_id", api.DeletePackage)

	router.HandleWithError("GET", "/tenants/:tenant_id/binded_packages", api.ListUserBindedPackage)
	router.HandleWithError("POST", "/tenants/:tenant_id/binded_packages", api.BindUserPackage)
	router.HandleWithError("GET", "/tenants/:tenant_id/binded_packages/:binded_package_id", api.GetUserBindedPackage)
	router.HandleWithError("DELETE", "/tenants/:tenant_id/binded_packages/:binded_package_id", api.UnbindUserPackage)

	router.HandleWithError("POST", "/tenants/:tenant_id/binded_packages/:binded_package_id/charge_credits", api.BindedPackageChargeCredits)
	router.HandleWithError("GET", "/tenants/:tenant_id/binded_packages/:binded_package_id/charge_credits", api.GetChargeCreditsHistory)
	router.HandleWithError("PUT", "/tenants/:tenant_id/binded_packages/:binded_package_id/discount", api.BindedPackageChangeDiscount)
}
