package statistic

import (
	"bytes"
	"context"
	"encoding/csv"
	"fmt"
	"os"
	"strconv"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/config"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/constants"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/message"
	"github.com/tidbcloud/observability/o11ymetering/pkg/common/models"
	"github.com/tidbcloud/observability/o11ymetering/pkg/facade"
	"github.com/tidbcloud/observability/pkg/common/configs"
)

var switchStatisticMergeReaderWriter = facade.SwitchStatisticMergeReaderWriter

func Report(ctx context.Context, req message.GenerateStatisticReportReq) (message.GenerateStatisticReportResp, error) {
	var reports []*models.MonthlyStatisticReport

	_, err := EnsureLocalData(ctx, constants.StatisticMergeTaskName, int64(req.Timestamp))
	if err != nil {
		return message.GenerateStatisticReportResp{}, err
	}

	switchStatisticMergeReaderWriter(ctx, int64(req.Timestamp))

	startPeriod := models.FromStartTime(time.Unix(int64(req.Timestamp), 0))

	endPeriod := models.FromStartTime(time.Unix(int64(req.Timestamp), 0).AddDate(0, 1, 0))

	shareRequests := allShareRequest(req.Timestamp, string(startPeriod), string(endPeriod))

	for _, shareReq := range shareRequests {
		resp, shareErr := ShareCostsStatisticReport(ctx, shareReq)
		if shareErr != nil {
			log.Errorf("ShareCostsStatisticReport failed, err = %s", shareErr.Error())
			return message.GenerateStatisticReportResp{}, shareErr
		}
		reports = append(reports, resp.TenantStatisticReport...)
	}

	err = facade.GetStatisticReportWriter().InsertReport(ctx, reports)
	if err != nil {
		return message.GenerateStatisticReportResp{}, err
	}
	return message.GenerateStatisticReportResp{}, err
}

func allShareRequest(timestamp int32, startPeriod string, endPeriod string) []message.ShareCostsStatisticReportReq {
	return []message.ShareCostsStatisticReportReq{
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.MetricsTimeseriesCount,
			Category:      constants.Metrics,
			CloudService:  constants.ElasticCompute,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.LogsLokiDataTransmittedBytes,
			Category:      constants.Logs,
			CloudService:  constants.ElasticCompute,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.MetricsTimeseriesCount,
			Category:      constants.Metrics,
			CloudService:  constants.EBS,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.MetricsTimeseriesCount,
			Category:      constants.Metrics,
			CloudService:  constants.ElasticLoadBalancing,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.LogsLokiDataTransmittedBytes,
			Category:      constants.Logs,
			CloudService:  constants.ElasticLoadBalancing,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.MetricsTimeseriesCount,
			Category:      constants.Metrics,
			CloudService:  constants.DataTransfer,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.LogsLokiDataTransmittedBytes,
			Category:      constants.Logs,
			CloudService:  constants.DataTransfer,
		},
		{
			Timestamp:     timestamp,
			StartPeriod:   startPeriod,
			EndPeriod:     endPeriod,
			StatisticName: constants.LogsS3DataTransmittedBytes,
			Category:      constants.Logs,
			CloudService:  constants.SimpleStorageService,
		},
	}
}

var getCategoryShareProportion = config.GetCategoryShareProportion

func ShareCostsStatisticReport(ctx context.Context, req message.ShareCostsStatisticReportReq) (message.ShareCostsStatisticReportResp, error) {
	v, err := facade.GetStatisticReportWriter().GetMonthlyCost(ctx, configs.GetServiceInfo().Region, req.Timestamp, string(req.CloudService))
	if err != nil {
		log.Errorf("GetMonthlyCost failed, timestamp = %d, cloud service = %s, err = %s", req.Timestamp, "Elastic Compute", err.Error())
		return message.ShareCostsStatisticReportResp{}, err
	}

	statisticSum, err := facade.GetCurrentStatisticMergeReaderWriter().MonthlyStatisticSum(ctx, configs.GetServiceInfo().Region, req.StartPeriod, req.EndPeriod, req.StatisticName, req.Category)

	if err != nil {
		log.Errorf("MonthlyStatisticSum failed, startPeriod = %s, statisticName = %s, err = %s", req.StartPeriod, constants.MetricsTimeseriesCount, err.Error())
		return message.ShareCostsStatisticReportResp{}, err
	}

	tenantStatistics, err := facade.GetCurrentStatisticMergeReaderWriter().MonthlyTenantStatistic(ctx, configs.GetServiceInfo().Region, req.StartPeriod, req.EndPeriod, req.StatisticName)

	if err != nil {
		log.Errorf("MonthlyTenantStatistic failed, startPeriod = %s, statisticName = %s, err = %s", req.StartPeriod, constants.MetricsTimeseriesCount, err.Error())
		return message.ShareCostsStatisticReportResp{}, err
	}
	var reports = make([]*models.MonthlyStatisticReport, 0, len(tenantStatistics))

	for _, tenant := range tenantStatistics {
		reports = append(reports, &models.MonthlyStatisticReport{
			Timestamp:     req.Timestamp,
			Vendor:        configs.GetServiceInfo().Vendor,
			Region:        configs.GetServiceInfo().Region,
			Env:           tenant.Env,
			MeteringGroup: tenant.MeteringGroup,
			Tenant:        tenant.Tenant,
			SubCategory:   tenant.SubCategory,
			Category:      tenant.Category,
			CloudService:  req.CloudService,
			Cost:          v * getCategoryShareProportion(req.Category, req.CloudService) * tenant.SumValue / statisticSum,
		})
	}

	return message.ShareCostsStatisticReportResp{
		TenantStatisticReport: reports,
	}, nil
}

func EnsurePath(dir string) {
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		panic(err)
	}
}

func EnsureLocalData(ctx context.Context, taskName string, timestamp int64) (string, error) {
	EnsurePath(fmt.Sprintf("%s/%s/%s", "data", constants.StatisticTaskDataPrefix, taskName))
	filePath := facade.GetScheduleTaskDataSync().LocalDataFilePath(ctx, taskName, timestamp)

	_, err := os.Stat(filePath)
	if err == nil {
		log.Infof("local %s file exists, path = %s", taskName, filePath)
		return filePath, nil
	}
	if os.IsNotExist(err) {
		f, er := os.Create(filePath)
		if er != nil {
			log.Infof("create %s failed, err = %s", filePath, er)
		}
		f.Close()
		log.Infof("copy %s file from s3 to %s", taskName, filePath)
		return facade.GetScheduleTaskDataSync().Download(ctx, taskName, timestamp)
	} else {
		return "", err
	}
}

func CalculateMonthlyProportionSum(ctx context.Context, req message.MonthlyProportionSumReq) (message.MonthlyProportionSumResp, error) {
	t := time.Unix(int64(req.Timestamp), 0)
	monthString := t.Format("2006-01")

	records, err := facade.GetStatisticReportWriter().CalculateMonthlyProportionSum(ctx, int64(req.Timestamp), monthString)
	if err != nil {
		log.Errorf("CalculateMonthlyProportionSum failed, err = %s", err.Error())
		return message.MonthlyProportionSumResp{}, err
	}

	file, err := UploadCSVFromProportionSum(ctx, records, monthString)
	if err != nil {
		log.Errorf("UploadCSVFromProportionSum failed, err = %s", err.Error())
		return message.MonthlyProportionSumResp{}, err
	}

	return message.MonthlyProportionSumResp{
		Timestamp: req.Timestamp,
		File:      file,
	}, nil
}

// UploadCSVFromProportionSum takes a slice of MonthlyProportionSum and returns CSV content as a byte slice.
func UploadCSVFromProportionSum(ctx context.Context, records []models.MonthlyProportionSum, monthString string) (string, error) {
	// Create a buffer to write the CSV data to
	var b bytes.Buffer
	writer := csv.NewWriter(&b)

	// Write the CSV header
	header := []string{"id", "timestamp", "month", "provider", "env", "metering_group", "tenant", "proportion"}
	if err := writer.Write(header); err != nil {
		return "", fmt.Errorf("failed to write CSV header: %w", err)
	}

	// Write each record
	for _, record := range records {
		row := []string{
			strconv.FormatInt(record.Timestamp, 10), // This id field is not used, but it is included in the schema agreed upon with the downstream, so it is filled with timestamp to represent this batch of data.
			strconv.FormatInt(record.Timestamp, 10),
			record.Month,
			record.Provider,
			record.Env,
			record.MeteringGroup,
			record.Tenant,
			strconv.FormatFloat(record.Proportion, 'f', -1, 64), // Format float without fixed decimal places
		}
		if err := writer.Write(row); err != nil {
			return "", fmt.Errorf("failed to write CSV row for record %+v: %w", record, err)
		}
	}

	writer.Flush()
	if err := writer.Error(); err != nil {
		return "", fmt.Errorf("CSV writer error: %w", err)
	}

	// Construct the S3 object key (path)
	s3ObjectKey := fmt.Sprintf("metering/split/o11y_usage_proportion_%s.csv", monthString)
	s3BucketName := "o11y-prod-shared-us-west-2"

	// Create a bytes.Reader from the buffer's content
	csvDataReader := bytes.NewReader(b.Bytes())

	if err := facade.GetS3Client().Put(ctx, s3BucketName, s3ObjectKey, csvDataReader); err != nil {
		return "", fmt.Errorf("failed to upload CSV to S3: %w", err)
	}
	return s3ObjectKey, nil
}
