package service

import (
	"context"
	"encoding/csv"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/viper"
	"go.uber.org/zap"

	"github.com/pingcap-inc/data-ingestion-tools/internal/itcronjob/store"
	"github.com/pingcap-inc/data-ingestion-tools/model"
	"github.com/pingcap-inc/data-ingestion-tools/pkg/log"
)

type BillSrv interface {
	SyncBill(ctx context.Context)
}

type billService struct {
	store store.Factory
}

var _ BillSrv = (*billService)(nil)

func newBill(str *service) *billService {
	return &billService{store: str.store}
}

const (
	cloudType = "云厂商"
	project   = "项目"
	cost      = "费用(¥)"
	month     = "月份"
)

type funcType func()

func (b *billService) SyncBill(ctx context.Context) {
	localPath := viper.GetString("localPath")
	ksSrv := ksService{store: b.store}
	ucSrv := ucService{store: b.store}
	larkSrv := larkService{store: b.store}
	gcsSrv := gcsService{store: b.store}
	monthNow := time.Now().Format("2006-01")
	// monthNow := "2023-03"
	wg := &sync.WaitGroup{}
	errChan := make(chan error, 10)
	go worker(wg, func() {
		dmsRecords, err := larkSrv.GetBitRecords(ctx, "dms")
		if err != nil {
			errChan <- err
		}
		err = larkSrv.BatchDeleteRecords(ctx, "dms", dmsRecords)
		if err != nil {
			errChan <- err
		}
		osRecords, err := larkSrv.GetBitRecords(ctx, "os")
		if err != nil {
			errChan <- err
		}
		err = larkSrv.BatchDeleteRecords(ctx, "os", osRecords)
		if err != nil {
			errChan <- err
		}
	})
	ksBill, err := ksSrv.FetchKsBill(ctx, monthNow)
	if err != nil {
		log.L(ctx).Error("fetch ks bill failed", zap.Error(err))

		return
	}
	ucBill, err := ucSrv.FetchUcBill(ctx, monthNow)
	if err != nil {
		log.L(ctx).Error("fetch uc bill failed", zap.Error(err))

		return
	}
	err = Save(ctx, localPath, model.KsCloud, monthNow, ksBill)
	if err != nil {
		return
	}
	err = Save(ctx, localPath, model.UCloud, monthNow, ucBill)
	if err != nil {
		return
	}
	go worker(wg, func() {
		err = gcsSrv.UploadToGCS(ctx, model.UCloud)
		if err != nil {
			errChan <- err
		}
		err = gcsSrv.UploadToGCS(ctx, model.KsCloud)
		if err != nil {
			errChan <- err
		}
	})
	var fields []map[string]interface{}
	for _, record := range ksBill {
		fields = append(fields, map[string]interface{}{
			cloudType: "金山云",
			project:   record.ProjName,
			month:     monthNow,
			cost:      record.Cost,
		})
	}
	for _, record := range ucBill {
		fields = append(fields, map[string]interface{}{
			cloudType: "ucloud",
			project:   record.ProjName,
			month:     monthNow,
			cost:      record.Cost,
		})
	}

	wg.Wait()
	if len(errChan) > 0 {
		for err = range errChan {
			log.L(ctx).Error("delete bit record failed", zap.Error(err))
		}

		return
	}
	go larkSrv.WriteToBi(ctx, "dms", fields)
	go DropOldFiles(ctx)
	log.L(ctx).Info("sync bill success")
}

func Save(ctx context.Context, localPath, cloudName, monthNow string, billRecords []model.BillingRecord) error {
	err := SaveCsv(ctx, localPath, cloudName, monthNow, billRecords)
	if err != nil {
		return err
	}
	err = MergeCsv(ctx, localPath, cloudName)
	if err != nil {
		return err
	}

	return nil
}

func SaveCsv(ctx context.Context, localPath, cloudName, monthNow string, billRecords []model.BillingRecord) error {
	fileName := localPath + "/" + cloudName + "/" + monthNow + ".csv"
	var records [][]string
	records = append(records, []string{"project", "month", "cost"})
	for _, record := range billRecords {
		records = append(records, []string{record.ProjName, monthNow, record.Cost})
	}
	file, err := os.Create(fileName)
	if err != nil {
		log.L(ctx).Error("create file failed", zap.Error(err))

		return err
	}

	writer := csv.NewWriter(file)
	for _, record := range records {
		err := writer.Write(record)
		if err != nil {
			panic(err)
		}
	}
	writer.Flush()

	return nil
}

func MergeCsv(ctx context.Context, fpath, cloudName string) error {
	var records [][]string
	records = append(records, []string{"project", "month", "cost"})
	cloudFile := fpath + "/" + cloudName + "/" + cloudName + ".csv"
	f, err := os.Create(cloudFile)
	if err != nil {
		log.L(ctx).Error("create file failed", zap.Error(err))

		return err
	}
	writer := csv.NewWriter(f)
	writer.Write([]string{"project", "month", "cost"})
	filepath.Walk(fpath+"/"+cloudName, func(path string, info os.FileInfo, err error) error {
		// 如果当前路径是一个目录，则跳过
		if info.IsDir() {
			return nil
		}

		// 与cloudfile相同则跳过
		if path == cloudFile {
			return nil
		}
		// 打开本地文件
		ftmp, err := os.Open(path)
		if err != nil {
			log.L(ctx).Errorf("Failed to open local file: %v", err)

			return err
		}
		defer ftmp.Close()
		reader := csv.NewReader(ftmp)
		if _, err = reader.Read(); err != nil {
			if err != io.EOF {
				log.L(ctx).Errorf("Failed to read local file: %v", err)

				return err
			}
		}

		for {
			record, err := reader.Read()
			if err == io.EOF {
				break
			} else if err != nil {
				log.L(ctx).Errorf("Failed to read local file: %v", err)

				return err
			}

			if err := writer.Write(record); err != nil {
				log.L(ctx).Errorf("Failed to write local file: %v", err)

				return err
			}
		}

		return nil
	})
	writer.Flush()

	return nil
}

func worker(wg *sync.WaitGroup, function funcType) {
	wg.Add(1)
	defer wg.Done()
	function()
}

// DropOldFiles 删除1年前的老文件
func DropOldFiles(ctx context.Context) {
	// 设置要清理的目录和日期阈值
	directory := viper.GetString("localPath")
	threshold := time.Now().AddDate(-1, 0, 0) // 一年前的时间

	// 遍历目录中的所有文件
	err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			// 从文件名中提取日期部分
			name := info.Name()
			dateStr := name[:7]                         // 假设日期部分长度为8
			date, err := time.Parse("2006-01", dateStr) // 解析日期字符串
			if err != nil {
				// 如果无法解析日期，则忽略该文件
				return nil
			}
			// 比较日期是否早于阈值
			if date.Before(threshold) {
				// 如果是，则删除该文件
				err := os.Remove(path)
				if err != nil {
					log.L(ctx).Errorf("Failed to remove file: %v", err)
				} else {
					log.L(ctx).Infof("Removed file: %v", path)
				}
			}
		}
		return nil
	})
	if err != nil {
		log.L(ctx).Errorf("Failed to walk directory: %v", err)
	}

	log.L(ctx).Info("DropOldFiles done")
}
