package api

import (
	"errors"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/db/entity"
	snapshotbilling "github.com/pingcap/dbaas/billing2/model/billing/snapshot"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
)

type SnapshotRerunPreviewReq struct {
	StartDate      string `json:"start_date" binding:"required"`
	EndDate        string `json:"end_date" binding:"required"`
	InvoiceType    string `json:"invoice_type"`
	InvoiceDate    string `json:"invoice_date"`
	RerunRequestID *uint  `json:"rerun_request_id"`
	RerunOfRunID   *uint  `json:"rerun_of_run_id"`
	Publish        bool   `json:"publish"`
}

type SnapshotRerunPreviewResp struct {
	Status             string        `json:"status"`
	Published          bool          `json:"published"`
	RunID              uint          `json:"run_id,string"`
	InputBundleID      uint          `json:"input_bundle_id,string"`
	MeteringSnapshotID uint          `json:"metering_snapshot_id,string"`
	TenantID           uint          `json:"tenant_id,string"`
	BillingConfigID    uint          `json:"billing_config_id,string"`
	InvoiceType        string        `json:"invoice_type"`
	InvoiceDate        time.Time     `json:"invoice_date"`
	PeriodMonth        string        `json:"period_month"`
	WindowStart        time.Time     `json:"window_start"`
	WindowEnd          time.Time     `json:"window_end"`
	RawPrice           string        `json:"raw_price"`
	CostPrice          string        `json:"cost_price"`
	FactCount          int64         `json:"fact_count"`
	SourceMode         string        `json:"source_mode"`
	Missing            []string      `json:"missing,omitempty"`
	Meta               entity.Labels `json:"meta"`
}

type SnapshotAPI struct{}

func (s *SnapshotAPI) PreviewRawUserUnitCostRerun(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	uri := &RecalculateBillingConfigURIStruct{}
	if err := c.ShouldBindUri(uri); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	req := &SnapshotRerunPreviewReq{}
	if err := c.ShouldBindJSON(req); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	if req.Publish {
		return nil, billing_errors.NewInvalidInputError(errors.New("snapshot rerun publish is not supported yet"))
	}
	start, end, err := parseSnapshotRerunWindow(req.StartDate, req.EndDate)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	invoiceType := req.InvoiceType
	if invoiceType == "" {
		invoiceType = entity.InvoiceType_Recal
	}
	invoiceDate := start
	if req.InvoiceDate != "" {
		invoiceDate, err = time.Parse(time.RFC3339, req.InvoiceDate)
		if err != nil {
			return nil, billing_errors.NewInvalidInputError(err)
		}
		invoiceDate = invoiceDate.UTC()
	}

	run, err := snapshotbilling.NewSettlementRunModel().CreateSnapshotPrimaryRun(snapshotbilling.CreateSnapshotPrimaryRunInput{
		TenantID:        uri.TenantID,
		BillingConfigID: uri.BillingConfigID,
		InvoiceType:     invoiceType,
		InvoiceDate:     invoiceDate,
		VersionSet: entity.Labels{
			"settlement_engine": entity.BillingSnapshotRunSourceModeSnapshotPrimary,
			"rerun_source":      "raw_user_unit_cost",
		},
		BundleType:       entity.BillingSnapshotInputBundleTypeRerunCandidate,
		RerunRequestID:   req.RerunRequestID,
		RerunOfRunID:     req.RerunOfRunID,
		RerunWindowStart: &start,
		RerunWindowEnd:   &end,
	})
	if err != nil {
		return nil, err
	}
	preview, err := snapshotbilling.NewInvoicePreviewModel().PreviewInvoiceFromRun(run.RunID)
	if err != nil {
		return nil, err
	}
	return &SnapshotRerunPreviewResp{
		Status:             "preview",
		Published:          false,
		RunID:              preview.RunID,
		InputBundleID:      preview.InputBundleID,
		MeteringSnapshotID: preview.MeteringSnapshotID,
		TenantID:           preview.TenantID,
		BillingConfigID:    preview.BillingConfigID,
		InvoiceType:        preview.InvoiceType,
		InvoiceDate:        preview.InvoiceDate,
		PeriodMonth:        preview.PeriodMonth,
		WindowStart:        preview.WindowStart,
		WindowEnd:          preview.WindowEnd,
		RawPrice:           preview.RawPrice.String(),
		CostPrice:          preview.CostPrice.String(),
		FactCount:          preview.FactCount,
		SourceMode:         run.SourceMode,
		Missing: []string{
			"scoped credits ledger/apply is not implemented",
			"rerun publish/patch is not implemented",
			"raw_user_unit_cost source reuses already calculated legacy prices instead of recalculating latest pricing and discounts",
		},
		Meta: preview.Meta,
	}, nil
}

func parseSnapshotRerunWindow(startDate string, endDate string) (time.Time, time.Time, error) {
	start, err := time.Parse(time.RFC3339, startDate)
	if err != nil {
		return time.Time{}, time.Time{}, err
	}
	end, err := time.Parse(time.RFC3339, endDate)
	if err != nil {
		return time.Time{}, time.Time{}, err
	}
	start = start.UTC()
	end = end.UTC()
	if !end.After(start) {
		return time.Time{}, time.Time{}, errors.New("end_date must be after start_date")
	}
	if timeutils.GetMonthDate(start) != timeutils.GetMonthDate(end.Add(-time.Nanosecond)) {
		return time.Time{}, time.Time{}, errors.New("start_date and end_date must be in the same billing month")
	}
	return start, end, nil
}

func InitializeSnapshotRouter(router ginutils.MyRoute) {
	s := &SnapshotAPI{}
	router.HandleWithError("POST", "/tenants/:tenant_id/billing_config_id/:billing_config_id/snapshot/reruns/raw_user_unit_costs/preview", s.PreviewRawUserUnitCostRerun)
}
