package api

import (
	"errors"
	"net/http"
	"path/filepath"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/auditloghandler"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/pagination"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
)

var contractModel = &billing.ContractModel{}

type ContractURIStruct struct {
	TenantID   uint `uri:"tenant_id" binding:"required"`
	ContractID uint `uri:"contract_id" binding:"required"`
}

type ContractOperationURIStruct struct {
	TenantID   uint   `uri:"tenant_id" binding:"required"`
	ContractID uint   `uri:"contract_id" binding:"required"`
	Action     string `uri:"action" binding:"required"`
}

// @Summary Create Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract body forms.ContractInput true "Contract"
// @Success 200 {object} forms.Contract
// @Router /tenants/{tenant_id}/contracts [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/contracts [post]
func (s *API) CreateContract(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	contract := &forms.ContractInput{}
	err = c.ShouldBindJSON(&contract)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	isMspPayment, err := paymentModel.IsMspPayment(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	if err != nil {
		return nil, err
	}
	if isMspPayment {
		return nil, billing_errors.NewInvalidInputError(errors.New("MSP Customer  don't support contract."))
	}
	info, err := contractModel.CreateContract(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID, contract)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Get Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract_id path int true "id"
// @Success 200 {object} forms.Contract
// @Router /tenants/{tenant_id}/contracts/{contract_id} [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/contracts/{contract_id} [get]
func (s *API) GetContract(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	contractURIArgs := &ContractURIStruct{}
	err := c.ShouldBindUri(contractURIArgs)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	info, err := contractModel.GetContract(contractURIArgs.TenantID, contractURIArgs.ContractID)
	if err != nil {
		return nil, err
	}
	return info, nil
}

func getContractURIParams(c *gin.Context) (*ContractURIStruct, error) {
	contractURIArgs := &ContractURIStruct{}
	err := c.ShouldBindUri(contractURIArgs)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	return contractURIArgs, nil
}

// @Summary Get User View Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract_id path int true "id"
// @Success 200 {object} forms.Contract
// @Router /tenants/{tenant_id}/users/contracts/{contract_id} [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/users/contracts/{contract_id} [get]
func (s *API) GetUserViewContract(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	contractURIArgs, err := getContractURIParams(c)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	billingConfigID, err := getBillingConfigIDFromURI(c)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	info, err := contractModel.GetUserViewContract(contractURIArgs.TenantID, billingConfigID, contractURIArgs.ContractID)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary List All Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param page query int false "page,default=1,min=1,max=100"
// @Param page_size query int false "page_size,default=20,min=1,max=100"
// @Success 200 {object} forms.ContractsResponse
// @Router /tenants/{tenant_id}/contracts [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/contracts [get]
func (s *API) ListContracts(c *gin.Context) (ginutils.HandlerFuncResult, error) {

	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	p, err := pagination.GetPagination(c)
	if err != nil {
		return nil, err
	}
	infos, err := contractModel.ListContracts(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID, p)
	if err != nil {
		return nil, err
	}
	return infos, nil
}

// @Summary List User View Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param page query int false "page,default=1,min=1,max=100"
// @Param page_size query int false "page_size,default=20,min=1,max=100"
// @Success 200 {object} forms.ContractsResponse
// @Router /tenants/{tenant_id}/users/contracts [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/users/contracts [get]
func (s *API) ListUserViewContracts(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	billingCfgStruct, err := getBillingCfgFromURI(c)
	if err != nil {
		return nil, err
	}
	p, err := pagination.GetPagination(c)
	if err != nil {
		return nil, err
	}
	infos, err := contractModel.ListUserViewContracts(billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID, p)
	if err != nil {
		return nil, err
	}
	return infos, nil
}

// @Summary Update Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract_id path int true "contract_id"
// @Param contract body forms.ContractInput true "Contract"
// @Success 200 {object} forms.Contract
// @Router /tenants/{tenant_id}/contracts/{contract_id} [put]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/contracts/{contract_id} [put]
func (s *API) UpdateContract(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	contractURIArgs := &ContractURIStruct{}
	err := c.ShouldBindUri(contractURIArgs)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	contract := &forms.ContractInput{}
	err = c.ShouldBindJSON(contract)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	info, err := contractModel.UpdateContract(contractURIArgs.TenantID, contractURIArgs.ContractID, contract)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Admin Operate Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract_id path int true "contract_id"
// @Param action path string true "action" Enums(create, submit, cancel, delete, terminate)
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/contracts/{contract_id}/actions/{action} [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/contracts/{contract_id}/actions/{action} [post]
func (s *API) OperateContractByAdmin(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	operateArgs := &ContractOperationURIStruct{}
	err := c.ShouldBindUri(operateArgs)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	contractAction := forms.ContractOperationInput{
		ContractID: operateArgs.ContractID,
		Action:     operateArgs.Action,
	}
	info, err := contractModel.OperateContractByAdmin(operateArgs.TenantID, &contractAction)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Operate Contract
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract_id path int true "contract_id"
// @Param action path string true "action" Enums(accept, reject)
// @Success 200 {object} forms.OKType
// @Router /tenants/{tenant_id}/users/contracts/{contract_id}/actions/{action} [post]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/users/contracts/{contract_id}/actions/{action} [post]
func (s *API) OperateContractByUser(c *gin.Context) (ret ginutils.HandlerFuncResult, retErr error) {
	operateArgs := &ContractOperationURIStruct{}
	err := c.ShouldBindUri(operateArgs)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	contractAction := forms.ContractOperationInput{
		ContractID: operateArgs.ContractID,
		Action:     operateArgs.Action,
	}
	defer func() {
		event := auditlog.EventContractAction
		contract, e := contractModel.GetContract(operateArgs.TenantID, operateArgs.ContractID)
		if e != nil {
			log.Error("failed to log audit-log ", zap.Error(e), zap.Int("Event", int(event)))
			return
		}
		auditloghandler.Log(event, c, contract.TenantID, auditloghandler.Contract2AuditLogDetails(contract, operateArgs.Action), retErr)
	}()
	info, err := contractModel.OperateContractByUser(operateArgs.TenantID, &contractAction)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Download Contract
// @Schemes
// @Param tenant_id path string true "tenant_id"
// @Param billing_config_id path int false "billing_config_id"
// @Param contract_id path int true "contract_id"
// @Router /tenants/{tenant_id}/contracts/{contract_id}/download [get]
// @Router /tenants/{tenant_id}/billing_config_id/{billing_config_id}/contracts/{contract_id}/download [get]
func (s *API) DownloadContract(c *gin.Context) {
	contractURIArgs := &ContractURIStruct{}
	err := c.ShouldBindUri(contractURIArgs)
	if err != nil {
		ginutils.ReturnError(c, err)
		return
	}
	info, err := contractModel.GetContract(contractURIArgs.TenantID, contractURIArgs.ContractID)
	if err != nil {
		ginutils.ReturnError(c, err)
		return
	}
	defer func() {
		auditloghandler.Log(auditlog.EventContractAction, c, info.TenantID, auditloghandler.Contract2AuditLogDetails(info, "download"), err)
	}()
	file, err := centralhandler.GetCentralHandler().DownloadFile(info.FilePath)
	if err != nil {
		ginutils.ReturnError(c, err)
		return
	}
	_, fileName := filepath.Split(info.FilePath)

	c.Header("Content-Disposition", "attachment; filename="+fileName)
	c.Data(http.StatusOK, config.Get().GetString(config.CDNFileContentType), file)
}

func InitializeContractRouter(router ginutils.MyRoute) {
	s := &API{}
	router.HandleWithError("POST", "/tenants/:tenant_id/contracts", s.CreateContract)
	router.HandleWithError("PUT", "/tenants/:tenant_id/contracts/:contract_id", s.UpdateContract)
	router.HandleWithError("GET", "/tenants/:tenant_id/contracts/:contract_id", s.GetContract)
	router.HandleWithError("GET", "/tenants/:tenant_id/contracts", s.ListContracts)
	router.HandleWithError("POST", "/tenants/:tenant_id/contracts/:contract_id/actions/:action", s.OperateContractByAdmin)
	router.Handle("GET", "/tenants/:tenant_id/contracts/:contract_id/download", s.DownloadContract)

	router.HandleWithError("GET", "/tenants/:tenant_id/users/contracts/:contract_id", s.GetUserViewContract)
	router.HandleWithError("GET", "/tenants/:tenant_id/users/contracts", s.ListUserViewContracts)
	router.HandleWithError("POST", "/tenants/:tenant_id/users/contracts/:contract_id/actions/:action", s.OperateContractByUser)

	// for billing config add api
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/contracts", s.CreateContract)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/contracts", s.ListContracts)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/users/contracts", s.ListUserViewContracts)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/users/contracts/:contract_id", s.GetUserViewContract)

	//no modify

	router.HandleWithError("PUT", "/tenants/:tenant_id/billing_config_id/:billing_config_id/contracts/:contract_id", s.UpdateContract)
	router.HandleWithError("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/contracts/:contract_id", s.GetContract)
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/contracts/:contract_id/actions/:action", s.OperateContractByAdmin)
	router.Handle("GET", "/tenants/:tenant_id/billing_config_id/:billing_config_id/contracts/:contract_id/download", s.DownloadContract)
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/users/contracts/:contract_id/actions/:action", s.OperateContractByUser)

}
