package api

import (
	"bytes"
	"encoding/csv"
	"errors"
	"net/http"
	"sort"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
)

type costExplorerAPI struct {
}

// @Summary Cost Explorer
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param Query body forms.CostExplorerQuery true "Query"
// @Success 200 {object} forms.CostExplorerResult
// @Router /tenants/{tenant_id}/costexplorer [post]
func (s *costExplorerAPI) QueryCost(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	tenantID, err := getTenantIDFromURI(c)
	if err != nil {
		return nil, err
	}

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

	model := &payment.CostExplorerModel{}

	return model.CalculateCost(tenantID, query)
}

type MonthlyCostURI struct {
	TenantID uint `uri:"tenant_id" binding:"required"`
	Year     int  `uri:"year" binding:"required"`
	Month    int  `uri:"month" binding:"required"`
}

// @Summary Cost Explorer Monthly
// @Schemes
// @Accept json
// @Produce json
// @Param tenant_id path string true "tenant_id"
// @Param year path string true "year"
// @Param month path string true "month"
// @Success 200 {object} forms.MonthlycostExplorerResult
// @Router /tenants/{tenant_id}/costexplorer/monthly_cost/year/{year}/month/{month} [get]
func (s *costExplorerAPI) MonthlyCost(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	input := &MonthlyCostURI{}
	err := c.ShouldBindUri(input)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	tenantID := input.TenantID

	model := &payment.CostExplorerModel{}

	costs, err := model.GetMonthlyCost(tenantID, input.Year, input.Month)
	if err != nil {
		return nil, err
	}

	lastMonthDate := timeutils.GetMonthDateByYearMonth(input.Year, input.Month).AddDate(0, -1, 0)

	invoiceGenerated := true

	invoiceModel := &payment.InvoiceModel{}
	_, err = invoiceModel.GetMonthlyInvoice(tenantID, lastMonthDate)
	if err != nil {
		if errors.Is(err, billing_errors.ObjectNotExistsError) {
			res, err := model.GetMonthlyCost(tenantID, lastMonthDate.Year(), int(lastMonthDate.Month()))
			if err != nil {
				return nil, err
			}

			// fot last month not need create invoice
			if res.UnbindedInvoiceCosts.CostPrice.Cmp(decimal.Zero) > 0 {
				invoiceGenerated = false
			}
		} else {
			return nil, err
		}
	}

	return &forms.MonthlycostExplorerResult{
		CostExplorerResult: *costs,
		InvoiceGenerated:   invoiceGenerated,
	}, nil
}

func costsToCSV(costs *forms.CostExplorerResult) [][]string {
	res := [][]string{}
	invoiceCosts := costs.InvoiceCostsCosts
	headers := []string{}
	categorys := []string{}
	for category := range invoiceCosts.CategoryUnitsCosts {
		categorys = append(categorys, category)
	}
	sort.StringSlice(categorys).Sort()
	headers = append(headers, "Project")
	headers = append(headers, categorys...)
	headers = append(headers, "Total")

	res = append(res, headers)

	projects := []string{}
	for project := range invoiceCosts.LabelsUnitsCosts["project"] {
		projects = append(projects, project)
	}

	sort.StringSlice(projects).Sort()

	for _, project := range projects {
		projectCategoryCosts := invoiceCosts.LabelsUnitsCosts["project"][project]
		line := []string{}
		line = append(line, project)
		for _, category := range categorys {
			line = append(line, projectCategoryCosts.CategoryUnitsCosts[category].CostPrice.String())
		}
		line = append(line, projectCategoryCosts.CostPrice.String())
		res = append(res, line)
	}
	line := []string{}
	line = append(line, "Total")
	for _, category := range categorys {
		line = append(line, invoiceCosts.CategoryUnitsCosts[category].CostPrice.String())
	}
	line = append(line, invoiceCosts.CostPrice.String())
	res = append(res, line)
	return res
}

// @Summary Cost Explorer Monthly
// @Schemes
// @Accept json
// @Produce octet-stream
// @Param tenant_id path string true "tenant_id"
// @Param year path string true "year"
// @Param month path string true "month"
// @Router /tenants/{tenant_id}/costexplorer/monthly_cost/year/{year}/month/{month}/export [post]
func (s *costExplorerAPI) MonthlyCostExport(c *gin.Context) {
	input := &MonthlyCostURI{}
	err := c.ShouldBindUri(input)
	if err != nil {
		ginutils.ReturnError(c, billing_errors.NewInvalidInputError(err))
	}
	tenantID := input.TenantID

	model := &payment.CostExplorerModel{}

	costs, err := model.GetMonthlyCost(tenantID, input.Year, input.Month)

	if err != nil {
		ginutils.ReturnError(c, err)
	}

	csvResult := costsToCSV(costs)

	writer := &bytes.Buffer{}

	err = csv.NewWriter(writer).WriteAll(csvResult)
	if err != nil {
		ginutils.ReturnError(c, billing_errors.NewSystemError(err))
	}

	c.Data(http.StatusOK, "application/octet-stream", writer.Bytes())
}

func InitializeCostExplorerRouter(router ginutils.MyRoute) {
	s := &costExplorerAPI{}
	router.HandleWithError("POST", "/tenants/:tenant_id/costexplorer", s.QueryCost)
	router.HandleWithError("GET", "/tenants/:tenant_id/costexplorer/monthly_cost/year/:year/month/:month", s.MonthlyCost)
	router.Handle("POST", "/tenants/:tenant_id/costexplorer/monthly_cost/year/:year/month/:month/export", s.MonthlyCostExport)
}
