package service

import (
	"context"
	"fmt"
	"gadmin/config"
	"gadmin/internal/admin/consts"
	"gadmin/internal/admin/forms"
	"gadmin/internal/gorm/model"
	"gadmin/internal/gorm/query"
	"gadmin/utility/serializer"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"github.com/xuri/excelize/v2"
	"io/ioutil"
	"os"
	"strings"
	"sync"
	"time"
)

var Fighting = new(sFighting)

type sFighting struct{}

func (s *sFighting) unionModel(ctx *gin.Context, where string) (unionALL string, err error) {
	type Table struct {
		Value string
	}

	dbName := config.GetDBName(os.Getenv("MYSQL_DSN"))
	var sql = "SELECT TABLE_NAME as `value` FROM information_schema.`TABLES` WHERE TABLE_SCHEMA ='" + dbName + "' AND TABLE_NAME LIKE 'chapter_logs_user_details_%'"
	tx := config.DB.Raw(sql)
	var tables []string
	if err = tx.Scan(&tables).Error; err != nil {
		return
	}

	logrus.Warnf("tables:%+v", tables)
	field := "id,server_id,user_id,event_id,chapter_id,room_id,user_created_at,event_at,extra,token,difficulty,event_at_ns"
	for k, table := range tables {
		if k == 0 {
			unionALL = fmt.Sprintf("SELECT %s FROM %s %s", field, table, where)
			continue
		}
		unionALL += fmt.Sprintf(" UNION ALL SELECT %s FROM %s %s", field, table, where)
	}

	return unionALL, nil
}

func (s *sFighting) List(ctx *gin.Context, req forms.FightingListReq) serializer.Response {
	var (
		models      forms.ListRes
		lists       []model.ChapterLogsUserDetail
		offset      int64 = 0
		hasWhere    bool
		where       string
		count       int64
		masterWhere string
	)
	if req.UserId > 0 {
		if hasWhere {
			where += fmt.Sprintf(" and user_id = %v", req.UserId)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where user_id = %v", req.UserId)
		}
	}

	if req.Id > 0 {
		if hasWhere {
			where += fmt.Sprintf(" and id = %v", req.Id)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where id = %v", req.Id)
		}
	}

	if len(req.EventAt) == 2 {
		if hasWhere {
			where += fmt.Sprintf(" and event_at BETWEEN %v AND %v", int32(req.EventAt[0]), int32(req.EventAt[1]))
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where event_at BETWEEN %v AND %v", int32(req.EventAt[0]), int32(req.EventAt[1]))
		}
	}

	if req.ChapterId > 0 {
		if hasWhere {
			where += fmt.Sprintf(" and chapter_id = %v", req.ChapterId)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where chapter_id = %v", req.ChapterId)
		}
	}

	if req.Difficulty > -1 {
		if hasWhere {
			where += fmt.Sprintf(" and difficulty = %v", req.Difficulty)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where difficulty = %v", req.Difficulty)
		}
	}

	unionALL, err := s.unionModel(ctx, where)
	if err != nil {
		return serializer.Err(consts.CodeParamErr, "查询出错 unionModel", err)
	}

	countSql := fmt.Sprintf("SELECT count(1) FROM(%s) as a", unionALL)
	getSql := fmt.Sprintf("SELECT * FROM(%s) as a", unionALL)

	// 排序
	masterWhere += " ORDER BY event_at DESC"

	// 分页
	req.Page, req.PerPage, offset = forms.CalculatePage(req.Page, req.PerPage)

	config.DB.Raw(countSql + masterWhere).Scan(&count)
	if count > 0 {
		masterWhere += fmt.Sprintf(" LIMIT %v", req.PerPage)
		if offset > 0 {
			masterWhere += fmt.Sprintf(" OFFSET %v", offset)
		}
		config.DB.Raw(getSql + masterWhere).Scan(&lists)
	}

	models.List = lists
	models.Page = req.Page
	models.PerPage = req.PerPage
	models.PageCount = (count + req.PerPage - 1) / req.PerPage
	return serializer.Suc(models)
}

func (s *sFighting) GetExtra(extraPath string) serializer.Response {
	// 处理从ocs获取extra逻辑
	if strings.Contains(extraPath, "{") {
		return serializer.Suc(extraPath)
	}
	if response, err := config.GetCOSClient().Object.Get(context.Background(), extraPath, nil); err != nil {
		return serializer.Err(consts.CodeParamErr, "从cos中获取extra发生错误", err)
	} else {
		extra, err := ioutil.ReadAll(response.Body)
		response.Body.Close()
		if err != nil {
			return serializer.Err(consts.CodeParamErr, "读取cos数据出错", err)
		} else {
			return serializer.Suc(string(extra))
		}
	}
}

func (s *sFighting) Export(ctx *gin.Context, req forms.FightingExportReq) {
	var (
		lists       []model.ChapterLogsUserDetail
		hasWhere    bool
		where       string
		masterWhere string
	)

	if req.UserId > 0 {
		if hasWhere {
			where += fmt.Sprintf(" and user_id = %v", req.UserId)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where user_id = %v", req.UserId)
		}
	}
	if req.Id > 0 {
		if hasWhere {
			where += fmt.Sprintf(" and id = %v", req.Id)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where id = %v", req.Id)
		}
	}
	// 导出必须选择一个时间段，且不能大于一天
	if hasWhere {
		where += fmt.Sprintf(" and event_at BETWEEN %v AND %v", int32(req.EventStamp[0]), int32(req.EventStamp[1]))
	} else {
		hasWhere = true
		where += fmt.Sprintf(" where event_at BETWEEN %v AND %v", int32(req.EventStamp[0]), int32(req.EventStamp[1]))
	}

	if req.ChapterId > 0 {
		if hasWhere {
			where += fmt.Sprintf(" and chapter_id = %v", req.ChapterId)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where chapter_id = %v", req.ChapterId)
		}
	}

	if req.Difficulty > -1 {
		if hasWhere {
			where += fmt.Sprintf(" and difficulty = %v", req.Difficulty)
		} else {
			hasWhere = true
			where += fmt.Sprintf(" where difficulty = %v", req.Difficulty)
		}
	}

	unionALL, err := s.unionModel(ctx, where)
	if err != nil {
		logrus.Warnf("Export 查询出错 unionModel: err:%+v", err)
		return
	}

	//countSql := fmt.Sprintf("SELECT count(1) FROM(%s) as a", unionALL)
	getSql := fmt.Sprintf("SELECT * FROM(%s) as a", unionALL)

	// 排序
	masterWhere += " ORDER BY event_at DESC"

	config.DB.Raw(getSql + masterWhere).Scan(&lists)
	// 多协程去cos请求数据
	numWorkers := 100
	var wg sync.WaitGroup
	if numWorkers > len(lists) {
		numWorkers = len(lists)
	}
	// 计算每个worker负责处理的元素范围
	itemsPerWorker := len(lists) / numWorkers
	extraItems := len(lists) % numWorkers
	// 分配任务给每个worker
	for i := 0; i < numWorkers; i++ {
		startIndex := i * itemsPerWorker
		endIndex := startIndex + itemsPerWorker
		// 处理最后一个worker的情况
		if i == numWorkers-1 {
			endIndex += extraItems
		}
		wg.Add(1)
		go func(start, end int) {
			defer wg.Done()
			for j := start; j < end; j++ {
				if strings.Contains(`lists[j].Extra`, "{") {
					continue
				}
				if response, err := config.GetCOSClient().Object.Get(context.Background(), lists[j].Extra, nil); err != nil {
					logrus.Warnf("从cos中获取extra发生错误 err:%+v", err)
					continue
				} else {
					extra, err := ioutil.ReadAll(response.Body)
					response.Body.Close()
					if err != nil {
						logrus.Warnf("读取cos数据出错 err:%+v", err)
						continue
					} else {
						lists[j].Extra = string(extra)
					}
				}
			}
		}(startIndex, endIndex)
	}
	// 等待所有goroutines完成
	wg.Wait()

	f := excelize.NewFile()
	f.SetColWidth("Sheet1", "G", "G", 150)
	f.SetColWidth("Sheet1", "H", "H", 25)
	// 创建一个工作表
	f.SetCellValue("Sheet1", "A1", "ID")
	f.SetCellValue("Sheet1", "B1", "玩家ID")
	f.SetCellValue("Sheet1", "C1", "埋点ID")
	f.SetCellValue("Sheet1", "D1", "关卡ID")
	f.SetCellValue("Sheet1", "E1", "房间ID")
	f.SetCellValue("Sheet1", "F1", "困难度")
	f.SetCellValue("Sheet1", "G1", "其他参数")
	f.SetCellValue("Sheet1", "H1", "埋点触发时间")

	for i, v := range lists {
		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), v.ID)
		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), v.UserID)
		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), v.EventID)
		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), v.ChapterID)
		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), v.RoomID)
		if v.Difficulty == 0 {
			f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i+2), "简单")
		} else {
			f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i+2), "困难")
		}
		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i+2), v.Extra)
		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i+2), time.Unix(int64(v.EventAt), 0).Format("2006-01-02 15:04:05"))
	}

	// 设置工作簿的默认工作表
	f.SetActiveSheet(1)
	ctx.Header("Content-Type", "application/vnd.ms-excel")
	ctx.Header("Content-Disposition", fmt.Sprintf("attachment;filename=玩家(%v)战斗数据导出_%s.xlsx", req.UserId, time.Now().Format("20060102150405")))
	f.WriteTo(ctx.Writer)
}

func (s *sFighting) Export2(ctx *gin.Context, req forms.FightingExportReq) {

	var (
		q      = query.Use(config.DB).ChapterLogsUserDetail
		m      = q.WithContext(ctx)
		models forms.ListRes
		lists  []model.ChapterLogsUserDetail
	)
	if req.UserId > 0 {
		m = m.Where(q.UserID.Eq(req.UserId))
	}

	if req.Id > 0 {
		m = m.Where(q.ID.Eq(req.Id))
	}

	if len(req.EventStamp) == 2 {
		m = m.Where(q.EventAt.Between(int32(req.EventStamp[0]), int32(req.EventStamp[1])))
	}

	if req.ChapterId > 0 {
		m = m.Where(q.ChapterID.Eq(req.ChapterId))
	}

	if req.Difficulty > -1 {
		m = m.Where(q.Difficulty.Eq(req.Difficulty))
	}

	//m = m.Order(q.ID.Desc())
	if err := m.Scan(&lists); err != nil {
		return
	}

	models.List = lists

	f := excelize.NewFile()
	//f.SetColWidth("Sheet1", "C", "C", 12)
	//f.SetColWidth("Sheet1", "D", "D", 25)
	f.SetColWidth("Sheet1", "G", "G", 150)
	f.SetColWidth("Sheet1", "H", "H", 25)
	// 创建一个工作表
	f.SetCellValue("Sheet1", "A1", "ID")
	f.SetCellValue("Sheet1", "B1", "玩家ID")
	f.SetCellValue("Sheet1", "C1", "埋点ID")
	f.SetCellValue("Sheet1", "D1", "关卡ID")
	f.SetCellValue("Sheet1", "E1", "房间ID")
	f.SetCellValue("Sheet1", "F1", "困难度")
	f.SetCellValue("Sheet1", "G1", "其他参数")
	f.SetCellValue("Sheet1", "H1", "埋点触发时间")

	for i, v := range lists {
		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), v.ID)
		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), v.UserID)
		f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), v.EventID)
		f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), v.ChapterID)
		f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), v.RoomID)
		if v.Difficulty == 0 {
			f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i+2), "简单")
		} else {
			f.SetCellValue("Sheet1", fmt.Sprintf("F%d", i+2), "困难")
		}
		f.SetCellValue("Sheet1", fmt.Sprintf("G%d", i+2), v.Extra)
		f.SetCellValue("Sheet1", fmt.Sprintf("H%d", i+2), time.Unix(int64(v.EventAt), 0).Format("2006-01-02 15:04:05"))
	}

	// 设置工作簿的默认工作表
	f.SetActiveSheet(1)
	// content, _ := gocsv.MarshalString(&data)
	ctx.Header("Content-Type", "application/vnd.ms-excel")
	ctx.Header("Content-Disposition", fmt.Sprintf("attachment;filename=玩家(%v)战斗数据导出_%s.xlsx", req.UserId, time.Now().Format("20060102150405")))
	f.WriteTo(ctx.Writer)

}
