package controller

import (
	"maps"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/copier"
	"gorm.io/gorm"

	"github.com/nezhahq/nezha/model"
	"github.com/nezhahq/nezha/pkg/tsdb"
	"github.com/nezhahq/nezha/service/singleton"
)

// Show service
// @Summary Show service
// @Security BearerAuth
// @Schemes
// @Description Show service
// @Tags common
// @Produce json
// @Success 200 {object} model.CommonResponse[model.ServiceResponse]
// @Router /service [get]
func showService(c *gin.Context) (*model.ServiceResponse, error) {
	res, err, _ := requestGroup.Do("list-service", func() (any, error) {
		singleton.AlertsLock.RLock()
		defer singleton.AlertsLock.RUnlock()
		stats := singleton.ServiceSentinelShared.CopyStats()
		var cycleTransferStats map[uint64]model.CycleTransferStats
		copier.Copy(&cycleTransferStats, singleton.AlertsCycleTransferStatsStore)
		return []any{
			stats, cycleTransferStats,
		}, nil
	})
	if err != nil {
		return nil, err
	}

	return &model.ServiceResponse{
		Services:           res.([]any)[0].(map[uint64]model.ServiceResponseItem),
		CycleTransferStats: res.([]any)[1].(map[uint64]model.CycleTransferStats),
	}, nil
}

// List service
// @Summary List service
// @Security BearerAuth
// @Schemes
// @Description List service
// @Tags auth required
// @Param id query uint false "Resource ID"
// @Produce json
// @Success 200 {object} model.CommonResponse[[]model.Service]
// @Router /service/list [get]
func listService(c *gin.Context) ([]*model.Service, error) {
	var ss []*model.Service
	ssl := singleton.ServiceSentinelShared.GetSortedList()
	if err := copier.Copy(&ss, &ssl); err != nil {
		return nil, err
	}

	return ss, nil
}

// Get service history
// @Summary Get service history by service ID
// @Security BearerAuth
// @Schemes
// @Description Get service monitoring history for a specific service
// @Tags common
// @param id path uint true "Service ID"
// @param period query string false "Time period: 1d, 7d, 30d (default: 1d)"
// @Produce json
// @Success 200 {object} model.CommonResponse[model.ServiceHistoryResponse]
// @Router /service/{id}/history [get]
func getServiceHistory(c *gin.Context) (*model.ServiceHistoryResponse, error) {
	idStr := c.Param("id")
	serviceID, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		return nil, err
	}

	// 检查服务是否存在
	service, ok := singleton.ServiceSentinelShared.Get(serviceID)
	if !ok || service == nil {
		return nil, singleton.Localizer.ErrorT("service not found")
	}

	// 解析时间范围
	periodStr := c.DefaultQuery("period", "1d")
	period, err := tsdb.ParseQueryPeriod(periodStr)
	if err != nil {
		return nil, err
	}

	// 权限检查：未登录用户只能查看 1d 数据
	_, isMember := c.Get(model.CtxKeyAuthorizedUser)
	if !isMember && period != tsdb.Period1Day {
		return nil, singleton.Localizer.ErrorT("unauthorized: only 1d data available for guests")
	}

	response := &model.ServiceHistoryResponse{
		ServiceID:   serviceID,
		ServiceName: service.Name,
		Servers:     make([]model.ServerServiceStats, 0),
	}

	if !singleton.TSDBEnabled() {
		return queryServiceHistoryFromDB(serviceID, period, response)
	}

	result, err := singleton.TSDBShared.QueryServiceHistory(serviceID, period)
	if err != nil {
		return nil, err
	}

	serverMap := singleton.ServerShared.GetList()

	for i := range result.Servers {
		if server, ok := serverMap[result.Servers[i].ServerID]; ok {
			result.Servers[i].ServerName = server.Name
		}
	}
	response.Servers = result.Servers

	return response, nil
}

func queryServiceHistoryFromDB(serviceID uint64, period tsdb.QueryPeriod, response *model.ServiceHistoryResponse) (*model.ServiceHistoryResponse, error) {
	since := time.Now().Add(-period.Duration())

	var histories []model.ServiceHistory
	if err := singleton.DB.Where("service_id = ? AND server_id != 0 AND created_at >= ?", serviceID, since).
		Order("server_id, created_at").Find(&histories).Error; err != nil {
		return nil, err
	}

	serverMap := singleton.ServerShared.GetList()
	grouped := make(map[uint64][]model.ServiceHistory)
	for _, h := range histories {
		grouped[h.ServerID] = append(grouped[h.ServerID], h)
	}

	for serverID, records := range grouped {
		stats := model.ServerServiceStats{
			ServerID: serverID,
		}
		if server, ok := serverMap[serverID]; ok {
			stats.ServerName = server.Name
		}

		var totalDelay float64
		var totalUp, totalDown uint64
		dps := make([]model.DataPoint, 0, len(records))
		for _, r := range records {
			status := uint8(1)
			if r.Down > 0 && r.Up == 0 {
				status = 0
			}
			dps = append(dps, model.DataPoint{
				Timestamp: r.CreatedAt.Unix() * 1000,
				Delay:     r.AvgDelay,
				Status:    status,
			})
			totalDelay += r.AvgDelay
			totalUp += r.Up
			totalDown += r.Down
		}

		var avgDelay float64
		if len(records) > 0 {
			avgDelay = totalDelay / float64(len(records))
		}
		var upPercent float32
		if totalUp+totalDown > 0 {
			upPercent = float32(totalUp) / float32(totalUp+totalDown) * 100
		}
		stats.Stats = model.ServiceHistorySummary{
			AvgDelay:   avgDelay,
			UpPercent:  upPercent,
			TotalUp:    totalUp,
			TotalDown:  totalDown,
			DataPoints: dps,
		}
		response.Servers = append(response.Servers, stats)
	}

	return response, nil
}

// List server services
// @Summary List service histories by server id
// @Security BearerAuth
// @Schemes
// @Description List service histories for a specific server
// @Tags common
// @param id path uint true "Server ID"
// @param period query string false "Time period: 1d, 7d, 30d (default: 1d)"
// @Produce json
// @Success 200 {object} model.CommonResponse[[]model.ServiceInfos]
// @Router /server/{id}/service [get]
func listServerServices(c *gin.Context) ([]*model.ServiceInfos, error) {
	idStr := c.Param("id")
	serverID, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		return nil, err
	}

	m := singleton.ServerShared.GetList()
	server, ok := m[serverID]
	if !ok || server == nil {
		return nil, singleton.Localizer.ErrorT("server not found")
	}

	_, isMember := c.Get(model.CtxKeyAuthorizedUser)
	authorized := isMember

	if server.HideForGuest && !authorized {
		return nil, singleton.Localizer.ErrorT("unauthorized")
	}

	// 解析时间范围
	periodStr := c.DefaultQuery("period", "1d")
	period, err := tsdb.ParseQueryPeriod(periodStr)
	if err != nil {
		return nil, err
	}

	// 权限检查：未登录用户只能查看 1d 数据
	if !isMember && period != tsdb.Period1Day {
		return nil, singleton.Localizer.ErrorT("unauthorized: only 1d data available for guests")
	}

	services := singleton.ServiceSentinelShared.GetSortedList()

	var result []*model.ServiceInfos

	if !singleton.TSDBEnabled() {
		return queryServerServicesFromDB(serverID, server.Name, period, services)
	}

	historyResults, err := singleton.TSDBShared.QueryServiceHistoryByServerID(serverID, period)
	if err != nil {
		return nil, err
	}

	for _, service := range services {
		if service.Cover == model.ServiceCoverAll {
			if service.SkipServers[serverID] {
				continue
			}
		} else {
			if !service.SkipServers[serverID] {
				continue
			}
		}

		historyResult, ok := historyResults[service.ID]
		if !ok || len(historyResult.Servers) == 0 {
			continue
		}

		serverStats := historyResult.Servers[0]

		infos := &model.ServiceInfos{
			ServiceID:    service.ID,
			ServerID:     serverID,
			ServiceName:  service.Name,
			ServerName:   server.Name,
			DisplayIndex: service.DisplayIndex,
			CreatedAt:    make([]int64, len(serverStats.Stats.DataPoints)),
			AvgDelay:     make([]float64, len(serverStats.Stats.DataPoints)),
		}

		for i, dp := range serverStats.Stats.DataPoints {
			infos.CreatedAt[i] = dp.Timestamp
			infos.AvgDelay[i] = dp.Delay
		}

		result = append(result, infos)
	}

	return result, nil
}

func queryServerServicesFromDB(serverID uint64, serverName string, period tsdb.QueryPeriod, services []*model.Service) ([]*model.ServiceInfos, error) {
	since := time.Now().Add(-period.Duration())

	var histories []model.ServiceHistory
	if err := singleton.DB.Where("server_id = ? AND created_at >= ?", serverID, since).
		Order("service_id, created_at").Find(&histories).Error; err != nil {
		return nil, err
	}

	grouped := make(map[uint64][]model.ServiceHistory)
	for _, h := range histories {
		grouped[h.ServiceID] = append(grouped[h.ServiceID], h)
	}

	var result []*model.ServiceInfos
	for _, service := range services {
		if service.Cover == model.ServiceCoverAll {
			if service.SkipServers[serverID] {
				continue
			}
		} else {
			if !service.SkipServers[serverID] {
				continue
			}
		}

		records, ok := grouped[service.ID]
		if !ok {
			continue
		}

		infos := &model.ServiceInfos{
			ServiceID:    service.ID,
			ServerID:     serverID,
			ServiceName:  service.Name,
			ServerName:   serverName,
			DisplayIndex: service.DisplayIndex,
			CreatedAt:    make([]int64, 0, len(records)),
			AvgDelay:     make([]float64, 0, len(records)),
		}

		for _, r := range records {
			infos.CreatedAt = append(infos.CreatedAt, r.CreatedAt.Truncate(time.Minute).Unix()*1000)
			infos.AvgDelay = append(infos.AvgDelay, r.AvgDelay)
		}

		result = append(result, infos)
	}

	return result, nil
}

// List server with service
// @Summary List server with service
// @Security BearerAuth
// @Schemes
// @Description List servers that have service monitoring data
// @Tags common
// @Produce json
// @Success 200 {object} model.CommonResponse[[]uint64]
// @Router /service/server [get]
func listServerWithServices(c *gin.Context) ([]uint64, error) {
	// 从内存中获取有服务监控配置的服务器列表
	services := singleton.ServiceSentinelShared.GetList()
	serverMap := singleton.ServerShared.GetList()

	serverIDSet := make(map[uint64]bool)

	for _, service := range services {
		if service.Cover == model.ServiceCoverAll {
			// 除了跳过的服务器，其他都包含
			for serverID := range serverMap {
				if !service.SkipServers[serverID] {
					serverIDSet[serverID] = true
				}
			}
		} else {
			// 只包含指定的服务器
			for serverID, enabled := range service.SkipServers {
				if enabled {
					serverIDSet[serverID] = true
				}
			}
		}
	}

	_, isMember := c.Get(model.CtxKeyAuthorizedUser)
	authorized := isMember

	var ret []uint64
	for id := range serverIDSet {
		server, ok := serverMap[id]
		if !ok || server == nil {
			continue
		}
		if !server.HideForGuest || authorized {
			ret = append(ret, id)
		}
	}

	return ret, nil
}

// Create service
// @Summary Create service
// @Security BearerAuth
// @Schemes
// @Description Create service
// @Tags auth required
// @Accept json
// @param request body model.ServiceForm true "Service Request"
// @Produce json
// @Success 200 {object} model.CommonResponse[uint64]
// @Router /service [post]
func createService(c *gin.Context) (uint64, error) {
	var mf model.ServiceForm
	if err := c.ShouldBindJSON(&mf); err != nil {
		return 0, err
	}

	uid := getUid(c)

	var m model.Service
	m.UserID = uid
	m.Name = mf.Name
	m.Target = strings.TrimSpace(mf.Target)
	m.Type = mf.Type
	m.SkipServers = mf.SkipServers
	m.Cover = mf.Cover
	m.DisplayIndex = mf.DisplayIndex
	m.Notify = mf.Notify
	m.NotificationGroupID = mf.NotificationGroupID
	m.Duration = mf.Duration
	m.LatencyNotify = mf.LatencyNotify
	m.MinLatency = mf.MinLatency
	m.MaxLatency = mf.MaxLatency
	m.EnableShowInService = mf.EnableShowInService
	m.EnableTriggerTask = mf.EnableTriggerTask
	m.RecoverTriggerTasks = mf.RecoverTriggerTasks
	m.FailTriggerTasks = mf.FailTriggerTasks

	if err := validateServers(c, &m); err != nil {
		return 0, err
	}

	if err := singleton.DB.Create(&m).Error; err != nil {
		return 0, newGormError("%v", err)
	}

	if err := singleton.ServiceSentinelShared.Update(&m); err != nil {
		return 0, err
	}

	singleton.ServiceSentinelShared.UpdateServiceList()
	return m.ID, nil
}

// Update service
// @Summary Update service
// @Security BearerAuth
// @Schemes
// @Description Update service
// @Tags auth required
// @Accept json
// @param id path uint true "Service ID"
// @param request body model.ServiceForm true "Service Request"
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
// @Router /service/{id} [patch]
func updateService(c *gin.Context) (any, error) {
	strID := c.Param("id")
	id, err := strconv.ParseUint(strID, 10, 64)
	if err != nil {
		return nil, err
	}
	var mf model.ServiceForm
	if err := c.ShouldBindJSON(&mf); err != nil {
		return nil, err
	}
	var m model.Service
	if err := singleton.DB.First(&m, id).Error; err != nil {
		return nil, singleton.Localizer.ErrorT("service id %d does not exist", id)
	}

	if !m.HasPermission(c) {
		return nil, singleton.Localizer.ErrorT("permission denied")
	}

	m.Name = mf.Name
	m.Target = strings.TrimSpace(mf.Target)
	m.Type = mf.Type
	m.SkipServers = mf.SkipServers
	m.Cover = mf.Cover
	m.DisplayIndex = mf.DisplayIndex
	m.Notify = mf.Notify
	m.NotificationGroupID = mf.NotificationGroupID
	m.Duration = mf.Duration
	m.LatencyNotify = mf.LatencyNotify
	m.MinLatency = mf.MinLatency
	m.MaxLatency = mf.MaxLatency
	m.EnableShowInService = mf.EnableShowInService
	m.EnableTriggerTask = mf.EnableTriggerTask
	m.RecoverTriggerTasks = mf.RecoverTriggerTasks
	m.FailTriggerTasks = mf.FailTriggerTasks

	if err := validateServers(c, &m); err != nil {
		return 0, err
	}

	if err := singleton.DB.Save(&m).Error; err != nil {
		return nil, newGormError("%v", err)
	}

	if err := singleton.ServiceSentinelShared.Update(&m); err != nil {
		return nil, err
	}

	singleton.ServiceSentinelShared.UpdateServiceList()
	return nil, nil
}

// Batch delete service
// @Summary Batch delete service
// @Security BearerAuth
// @Schemes
// @Description Batch delete service
// @Tags auth required
// @Accept json
// @param request body []uint true "id list"
// @Produce json
// @Success 200 {object} model.CommonResponse[any]
// @Router /batch-delete/service [post]
func batchDeleteService(c *gin.Context) (any, error) {
	var ids []uint64
	if err := c.ShouldBindJSON(&ids); err != nil {
		return nil, err
	}

	if !singleton.ServiceSentinelShared.CheckPermission(c, slices.Values(ids)) {
		return nil, singleton.Localizer.ErrorT("permission denied")
	}

	err := singleton.DB.Transaction(func(tx *gorm.DB) error {
		return tx.Unscoped().Delete(&model.Service{}, "id in (?)", ids).Error
	})
	if err != nil {
		return nil, err
	}
	singleton.ServiceSentinelShared.Delete(ids)
	singleton.ServiceSentinelShared.UpdateServiceList()
	return nil, nil
}

func validateServers(c *gin.Context, ss *model.Service) error {
	if !singleton.ServerShared.CheckPermission(c, maps.Keys(ss.SkipServers)) {
		return singleton.Localizer.ErrorT("permission denied")
	}

	return nil
}
