package api

import (
	"fmt"
	"io"
	"mime"
	"net/http"

	"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/model/billing"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/validator"
	"go.uber.org/multierr"
	"go.uber.org/zap"
)

// @Summary Create Unit
// @Schemes
// @Accept json
// @Produce json
// @Param unit body forms.UnitInfo true "unit"
// @Success 200 {object} forms.UnitInfo
// @Router /units [post]
func (s *API) CreateUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	unitform := &forms.UnitInfo{}
	if err := c.ShouldBindJSON(unitform); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	err := validator.ValidateStruct(unitform)
	if err != nil {
		return nil, err
	}

	model := &billing.UnitModel{}

	info, err := model.CreateAndUpdateUnit(unitform)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Create Unit By Group
// @Schemes
// @Accept json
// @Produce json
// @Param  UnitByGroup body forms.UnitByGroup true "UnitByGroup"
// @Success 200 {object} forms.UnitInfo
// @Router /units/_create_by_group [post]
func (s *API) CreateUnitByGroup(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	unitByGroup := &forms.UnitByGroup{}
	if err := c.ShouldBindJSON(unitByGroup); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	model := &billing.UnitModel{}

	info, err := model.CreateUnitByGroup(unitByGroup, false)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Create Unit By Group
// @Schemes
// @Accept json
// @Produce json
// @Param UnitByGroup body forms.UnitByGroup true "UnitByGroup"
// @Success 200 {object} forms.UnitInfo
// @Router /units/_create_by_group/dryrun [post]
func (s *API) DryrunCreateUnitByGroup(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	unitByGroup := &forms.UnitByGroup{}
	if err := c.ShouldBindJSON(unitByGroup); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	model := &billing.UnitModel{}

	info, err := model.CreateUnitByGroup(unitByGroup, true)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Bulk CreateOrUpdate Unit
// @Schemes
// @Accept json
// @Produce json
// @Param unit body []forms.UnitInfo true "units"
// @Success 200 {object} forms.OKType
// @Router /units/_bulk [post]
func (s *API) BulkCreateOrUpdateUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	unitforms := []forms.UnitInfo{}
	if err := c.ShouldBindJSON(&unitforms); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	if !config.Get().GetBool(config.APISwitchUpdateUnit) {

		log.Error("BulkCreateOrUpdateUnit request ", zap.Any("request", unitforms))
		return nil, billing_errors.NewSystemError(fmt.Errorf("API-Suspended"))
	}

	for _, unitInfo := range unitforms {
		err := validator.ValidateStruct(&unitInfo)
		if err != nil {
			return nil, err
		}
	}
	model := &billing.UnitModel{}

	errs := []error{}

	for _, unitInfo := range unitforms {
		_, err := model.CreateAndUpdateUnitFromCoreSVC(&unitInfo)
		if err != nil {
			errs = append(errs, err)
		}
	}

	err := multierr.Combine(errs...)

	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

// @Summary Update Unit
// @Schemes
// @Accept json
// @Produce json
// @Param unit_key path string true "UnitKey"
// @Success 200 {object} forms.UnitInfo
// @Router /units/{unit_key} [put]
func (s *API) UpdateUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uriForm := &UnitURIForm{}

	if err := c.ShouldBindUri(&uriForm); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	unitform := &forms.UnitInfo{}
	if err := c.ShouldBindJSON(unitform); err != nil {
		return nil, err
	}

	unitform.UnitKey = uriForm.UnitKey
	err := validator.ValidateStruct(unitform)
	if err != nil {
		return nil, err
	}
	model := &billing.UnitModel{}

	info, err := model.CreateAndUpdateUnit(unitform)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary Patch Unit
// @Schemes
// @Accept json
// @Produce json
// @Param unit_key path string true "UnitKey"
// @Param unit body forms.UnitPatch true "unit patch"
// @Success 200 {object} forms.UnitInfo
// @Router /units/{unit_key} [patch]
func (s *API) PatchUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uriForm := &UnitURIForm{}

	if err := c.ShouldBindUri(&uriForm); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	patch := &forms.UnitPatch{}
	if err := c.ShouldBindJSON(patch); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	model := &billing.UnitModel{}
	info, err := model.PatchUnit(uriForm.UnitKey, patch)
	if err != nil {
		return nil, err
	}
	return info, nil
}

// @Summary List Units
// @Description Return units matching group_id or the group's non-empty category. Without group_id, return all units.
// @Schemes
// @Accept json
// @Produce json
// @Param group_id query int false "group_id"
// @Success 200 {object} []forms.UnitInfo
// @Router /units [get]
func (s *API) ListUnits(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	queryForm := &UnitListQueryForm{}
	if err := c.ShouldBindQuery(queryForm); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	return (&billing.UnitModel{}).ListUnits(queryForm.GroupID)
}

type UnitListQueryForm struct {
	GroupID uint `form:"group_id"`
}

const maxUnitCSVImportSize = 10 << 20

// @Summary Export Units CSV
// @Description Export units selected by group_id or the group's non-empty category. Without group_id, the CSV contains only its header. SQL NULL values are represented as \N.
// @Schemes
// @Produce text/csv
// @Param group_id query int false "group_id"
// @Success 200 {file} file "units CSV file"
// @Router /units/_export [get]
func (s *API) ExportUnitsCSV(c *gin.Context) {
	queryForm := &UnitListQueryForm{}
	if err := c.ShouldBindQuery(queryForm); err != nil {
		ginutils.ReturnError(c, billing_errors.NewInvalidInputError(err))
		return
	}

	content, err := (&billing.UnitModel{}).ExportUnitsCSV(queryForm.GroupID)
	if err != nil {
		ginutils.ReturnError(c, err)
		return
	}
	c.Header("Content-Disposition", `attachment; filename="units.csv"`)
	c.Data(http.StatusOK, "text/csv; charset=utf-8", content)
}

// @Summary Import Units CSV
// @Description Atomically upsert units by unit_key. The endpoint accepts a multipart file field named file or a raw text/csv body.
// @Schemes
// @Accept multipart/form-data
// @Accept text/csv
// @Produce json
// @Param file formData file false "units CSV file (required for multipart requests)"
// @Success 200 {object} forms.UnitCSVImportResult
// @Router /units/_import [post]
func (s *API) ImportUnitsCSV(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxUnitCSVImportSize)

	input, closeInput, err := unitCSVInput(c)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	if closeInput != nil {
		defer closeInput()
	}

	return (&billing.UnitModel{}).ImportUnitsCSV(input)
}

func unitCSVInput(c *gin.Context) (io.Reader, func() error, error) {
	mediaType, _, err := mime.ParseMediaType(c.GetHeader("Content-Type"))
	if err != nil {
		return nil, nil, fmt.Errorf("invalid Content-Type: %w", err)
	}
	if mediaType != "multipart/form-data" {
		return c.Request.Body, nil, nil
	}

	file, _, err := c.Request.FormFile("file")
	if err != nil {
		return nil, nil, fmt.Errorf("read multipart file field %q: %w", "file", err)
	}
	return file, file.Close, nil
}

type UnitURIForm struct {
	UnitKey string `uri:"unit_key"` //nolint
}

// @Summary List Units By GroupID
// @Schemes
// @Accept json
// @Produce json
// @Param group_id path string true "group_id"
// @Success 200 {object} []forms.UnitInfo
// @Router /units/ [get]
func (s *API) ListUnitsByGroupID(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uriForm := &GroupIDURIForm{}
	if err := c.ShouldBindUri(&uriForm); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	model := &billing.UnitModel{}
	units, err := model.ListUnitsByGroupID(uriForm.GroupID)
	if err != nil {
		return nil, err
	}
	return units, nil
}

// @Summary Get Unit
// @Schemes
// @Accept json
// @Produce json
// @Param unit_key path string true "UnitKey"
// @Success 200 {object} forms.UnitInfo
// @Router /units/{unit_key} [get]
func (s *API) GetUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uriForm := &UnitURIForm{}

	if err := c.ShouldBindUri(&uriForm); err != nil {
		return nil, err
	}

	model := &billing.UnitModel{}
	unitInfo, err := model.GetUnit(uriForm.UnitKey)
	if err != nil {
		return nil, err
	}
	return unitInfo, nil
}

// @Summary Delete Unit
// @Schemes
// @Accept json
// @Produce json
// @Param unit_key path string true "UnitKey"
// @Success 200 {object} forms.OKType
// @Router /units/{unit_key} [delete]
func (s *API) DeleteUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uriForm := &UnitURIForm{}

	if err := c.ShouldBindUri(&uriForm); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	model := &billing.UnitModel{}
	err := model.DeleteUnit(uriForm.UnitKey)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

type NetWorkUnitURIForm struct {
	Provider string `uri:"provider"`
	Region   string `uri:"region"`
}

// @Summary Add Region NetWork Unit
// @Schemes
// @Accept json
// @Produce json
// @Param provider path string true "Provider"
// @Param region path string true "Region"
// @Success 200 {object} forms.OKType
// @Router /units/network/{provider}/{region} [post]
func (s *API) AddRegionNetWorkUnit(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uriForm := &NetWorkUnitURIForm{}

	if err := c.ShouldBindUri(&uriForm); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	model := &billing.UnitModel{}
	err := model.AddRegionNetWorkUnit(uriForm.Provider, uriForm.Region)
	if err != nil {
		return nil, err
	}
	return forms.OK, nil
}

func InitializeUnitRouter(router ginutils.MyRoute) {
	api := &API{}
	router.HandleWithErrorWithoutQueryFilter("GET", "/units", api.ListUnits)
	router.Handle("GET", "/units/_export", api.ExportUnitsCSV)
	router.HandleWithError("POST", "/units/_import", api.ImportUnitsCSV)
	router.HandleWithError("POST", "/units", api.CreateUnit)
	router.HandleWithError("POST", "/units/_create_by_group", api.CreateUnitByGroup)
	router.HandleWithError("POST", "/units/_create_by_group/dryrun", api.DryrunCreateUnitByGroup)
	router.HandleWithError("POST", "/units/_bulk", api.BulkCreateOrUpdateUnit)
	router.HandleWithError("GET", "/units/:unit_key", api.GetUnit)
	router.HandleWithError("PUT", "/units/:unit_key", api.UpdateUnit)
	router.HandleWithError("PATCH", "/units/:unit_key", api.PatchUnit)
	router.HandleWithError("DELETE", "/units/:unit_key", api.DeleteUnit)
	router.HandleWithError("POST", "/units/network/:provider/:region", api.AddRegionNetWorkUnit)

}
func InitializeInnerUnitRouter(router ginutils.MyRoute) {
	api := &API{}
	router.HandleWithErrorWithoutQueryFilter("GET", "/units", api.ListUnits)
	router.Handle("GET", "/units/_export", api.ExportUnitsCSV)
	router.HandleWithError("POST", "/units/_import", api.ImportUnitsCSV)
	router.HandleWithError("POST", "/units/_create_by_group", api.CreateUnitByGroup)
	router.HandleWithError("POST", "/units/_create_by_group/dryrun", api.DryrunCreateUnitByGroup)
	router.HandleWithError("GET", "/units/:unit_key", api.GetUnit)
	router.HandleWithError("PUT", "/units/:unit_key", api.UpdateUnit)
	router.HandleWithError("PATCH", "/units/:unit_key", api.PatchUnit)
	router.HandleWithError("DELETE", "/units/:unit_key", api.DeleteUnit)

}
