package platform

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"
)

type accessEvidenceStore interface {
	AccessEvidence(context.Context) ([]AccessEvidenceRow, error)
}

type accessThresholdStore interface {
	AccessThresholds(context.Context) (AccessThresholdConfig, error)
	SaveAccessThresholds(context.Context, AccessThresholdUpdate) (AccessThresholdConfig, error)
}

type accessUnresolvedIdentityStore interface {
	AccessUnresolvedIdentities(context.Context, AccessUnresolvedIdentityQuery) (Page[AccessUnresolvedIdentity], error)
}

func defaultAccessThresholds(now time.Time) AccessThresholdConfig {
	return AccessThresholdConfig{
		Version:             1,
		DefaultThresholdSec: 300,
		DelayThresholdSec:   30,
		LongOfflineSec:      1800,
		Protocols: []AccessProtocolThreshold{
			{Protocol: "GB32960", ThresholdSec: 300},
			{Protocol: "JT808", ThresholdSec: 300},
			{Protocol: "YUTONG_MQTT", ThresholdSec: 600},
		},
		UpdatedBy: "system",
		UpdatedAt: now.Format(time.RFC3339),
	}
}

func (s *Service) AccessThresholds(ctx context.Context) (AccessThresholdConfig, error) {
	store, ok := s.store.(accessThresholdStore)
	if !ok {
		return defaultAccessThresholds(time.Now()), nil
	}
	config, err := store.AccessThresholds(ctx)
	if err != nil {
		return AccessThresholdConfig{}, err
	}
	return normalizeAccessThresholdConfig(config, time.Now()), nil
}

func (s *Service) UpdateAccessThresholds(ctx context.Context, update AccessThresholdUpdate) (AccessThresholdConfig, error) {
	if err := validateAccessThresholdUpdate(update); err != nil {
		return AccessThresholdConfig{}, err
	}
	store, ok := s.store.(accessThresholdStore)
	if !ok {
		return AccessThresholdConfig{}, clientError{Code: "ACCESS_THRESHOLD_READ_ONLY", Message: "当前存储不支持更新接入阈值"}
	}
	update.Actor = strings.TrimSpace(update.Actor)
	if update.Actor == "" {
		update.Actor = "platform-admin"
	}
	return store.SaveAccessThresholds(ctx, update)
}

func (s *Service) AccessVehicles(ctx context.Context, query AccessQuery) (Page[AccessVehicleRow], error) {
	rows, config, err := s.accessRows(ctx, query)
	if err != nil {
		return Page[AccessVehicleRow]{}, err
	}
	_ = config
	limit := query.Limit
	if limit <= 0 {
		limit = 50
	}
	if limit > 200 {
		limit = 200
	}
	offset := query.Offset
	if offset < 0 {
		offset = 0
	}
	total := len(rows)
	if offset >= total {
		return Page[AccessVehicleRow]{Items: []AccessVehicleRow{}, Total: total, Limit: limit, Offset: offset}, nil
	}
	end := offset + limit
	if end > total {
		end = total
	}
	return Page[AccessVehicleRow]{Items: append([]AccessVehicleRow(nil), rows[offset:end]...), Total: total, Limit: limit, Offset: offset}, nil
}

func (s *Service) AccessUnresolvedIdentities(ctx context.Context, query AccessUnresolvedIdentityQuery) (Page[AccessUnresolvedIdentity], error) {
	store, ok := s.store.(accessUnresolvedIdentityStore)
	if !ok {
		return Page[AccessUnresolvedIdentity]{}, fmt.Errorf("store does not provide unresolved identity evidence")
	}
	query.Keyword = strings.TrimSpace(query.Keyword)
	query.Protocol = strings.ToUpper(strings.TrimSpace(query.Protocol))
	if query.Protocol != "" && query.Protocol != "JT808" {
		return Page[AccessUnresolvedIdentity]{Items: []AccessUnresolvedIdentity{}, Limit: 50}, nil
	}
	if query.Limit <= 0 {
		query.Limit = 50
	}
	if query.Limit > 200 {
		query.Limit = 200
	}
	if query.Offset < 0 {
		query.Offset = 0
	}
	return store.AccessUnresolvedIdentities(ctx, query)
}

func (s *Service) AccessSummary(ctx context.Context, query AccessQuery) (AccessSummary, error) {
	rows, config, err := s.accessRows(ctx, query)
	if err != nil {
		return AccessSummary{}, err
	}
	now := time.Now()
	result := AccessSummary{TotalVehicles: len(rows), AsOf: now.Format(time.RFC3339), ThresholdVersion: config.Version}
	protocols := map[string]*AccessDistribution{}
	oems := map[string]*AccessDistribution{}
	for _, row := range rows {
		switch row.ConnectionState {
		case "healthy":
			result.HealthyVehicles++
		case "incomplete":
			result.IncompleteVehicles++
		case "degraded":
			result.DegradedVehicles++
		}
		switch row.OnlineState {
		case "online":
			result.OnlineVehicles++
		case "offline":
			result.OfflineVehicles++
			if row.FreshnessSec != nil && *row.FreshnessSec >= config.LongOfflineSec {
				result.LongOfflineVehicles++
			}
		case "never_reported":
			result.NeverReported++
		default:
			result.UnknownVehicles++
		}
		if row.DelayAbnormal {
			result.DelayAbnormal++
		}
		if reportedOnDay(row.LatestReceivedAt, now) {
			result.ReportedToday++
		}
		for _, status := range row.ProtocolStatuses {
			if status.Connected {
				addAccessDistribution(protocols, status.Protocol, status.OnlineState == "online")
			}
		}
		addAccessDistribution(oems, firstNonEmpty(row.OEM, "未维护"), row.OnlineState == "online")
	}
	if result.TotalVehicles > 0 {
		result.OnlineRate = float64(result.OnlineVehicles) / float64(result.TotalVehicles) * 100
	}
	result.Protocols = sortedAccessDistributions(protocols)
	result.OEMs = sortedAccessDistributions(oems)
	return result, nil
}

func (s *Service) accessRows(ctx context.Context, query AccessQuery) ([]AccessVehicleRow, AccessThresholdConfig, error) {
	if err := validateAccessQuery(query); err != nil {
		return nil, AccessThresholdConfig{}, err
	}
	store, ok := s.store.(accessEvidenceStore)
	if !ok {
		return nil, AccessThresholdConfig{}, fmt.Errorf("store does not provide access evidence")
	}
	config, err := s.AccessThresholds(ctx)
	if err != nil {
		return nil, AccessThresholdConfig{}, err
	}
	evidence, err := store.AccessEvidence(ctx)
	if err != nil {
		return nil, AccessThresholdConfig{}, err
	}
	now := time.Now()
	byVIN := make(map[string][]AccessEvidenceRow, len(evidence))
	order := make([]string, 0, len(evidence))
	for _, item := range evidence {
		vin := strings.TrimSpace(item.VIN)
		if _, exists := byVIN[vin]; !exists {
			order = append(order, vin)
		}
		byVIN[vin] = append(byVIN[vin], item)
	}
	rows := make([]AccessVehicleRow, 0, len(byVIN))
	for _, vin := range order {
		row := buildAccessVehicleGroup(byVIN[vin], config, now)
		if keepAccessRow(row, query) {
			rows = append(rows, row)
		}
	}
	sort.SliceStable(rows, func(i, j int) bool {
		left, right := accessStateRank(rows[i].OnlineState), accessStateRank(rows[j].OnlineState)
		if left != right {
			return left < right
		}
		if rows[i].LatestReceivedAt != rows[j].LatestReceivedAt {
			return rows[i].LatestReceivedAt > rows[j].LatestReceivedAt
		}
		return rows[i].VIN < rows[j].VIN
	})
	return rows, config, nil
}

func buildAccessVehicleGroup(items []AccessEvidenceRow, config AccessThresholdConfig, now time.Time) AccessVehicleRow {
	if len(items) == 0 {
		return AccessVehicleRow{}
	}
	base := items[0]
	row := AccessVehicleRow{
		VIN:                 strings.TrimSpace(base.VIN),
		Plate:               strings.TrimSpace(base.Plate),
		OEM:                 strings.TrimSpace(base.OEM),
		Model:               strings.TrimSpace(base.Model),
		Company:             strings.TrimSpace(base.Company),
		ExpectedProtocols:   append([]string(nil), canonicalVehicleProtocols...),
		ActualProtocols:     []string{},
		MissingProtocols:    []string{},
		ProtocolStatuses:    []AccessProtocolStatus{},
		ExpectationEvidence: "平台标准接入基线：GB32960 / JT808 / YUTONG_MQTT",
		ConnectionState:     "not_connected",
		OnlineState:         "never_reported",
		FirstSeenEvidence:   "尚未形成任何协议接入快照",
		ReportIntervalProof: "需要连续上报样本后才能计算",
	}
	byProtocol := make(map[string]AccessEvidenceRow, len(items))
	for _, item := range items {
		protocol := strings.ToUpper(strings.TrimSpace(item.Protocol))
		if protocol != "" {
			byProtocol[protocol] = item
		}
		if row.Plate == "" {
			row.Plate = strings.TrimSpace(item.Plate)
		}
		if row.OEM == "" {
			row.OEM = strings.TrimSpace(item.OEM)
		}
		if row.Model == "" {
			row.Model = strings.TrimSpace(item.Model)
		}
		if row.Company == "" {
			row.Company = strings.TrimSpace(item.Company)
		}
	}
	onlineCount := 0
	unknownCount := 0
	connectedCount := 0
	for _, protocol := range canonicalVehicleProtocols {
		item, connected := byProtocol[protocol]
		if !connected {
			threshold := accessProtocolThreshold(config, protocol)
			row.MissingProtocols = append(row.MissingProtocols, protocol)
			row.ProtocolStatuses = append(row.ProtocolStatuses, AccessProtocolStatus{
				Protocol: protocol, Expected: true, Connected: false, OnlineState: "never_reported", ThresholdSec: threshold,
				FirstSeenEvidence: "应接协议尚未形成实时快照", ReportIntervalEvidence: "尚无接收样本",
			})
			continue
		}
		source := buildAccessVehicleRow(item, config, now)
		connectedCount++
		row.ActualProtocols = append(row.ActualProtocols, protocol)
		if source.OnlineState == "online" {
			onlineCount++
		}
		if source.DelayAbnormal {
			row.DelayAbnormal = true
		}
		row.ProtocolStatuses = append(row.ProtocolStatuses, AccessProtocolStatus{
			Protocol: protocol, Expected: true, Connected: true, Provider: source.Provider,
			FirstSeenAt: source.FirstSeenAt, LatestEventAt: source.LatestEventAt, LatestReceivedAt: source.LatestReceivedAt,
			ReportIntervalSec: source.ReportIntervalSec, DataDelaySec: source.DataDelaySec, FreshnessSec: source.FreshnessSec,
			OnlineState: source.OnlineState, ThresholdSec: source.ThresholdSec, DelayAbnormal: source.DelayAbnormal,
			FirstSeenEvidence: source.FirstSeenEvidence, ReportIntervalEvidence: source.ReportIntervalProof,
			Longitude: item.Longitude, Latitude: item.Latitude, SpeedKmh: item.SpeedKmh, SOCPercent: item.SOCPercent, TotalMileageKm: item.TotalMileageKm, DailyMileageKm: item.DailyMileageKm,
		})
		if row.FirstSeenAt == "" || source.FirstSeenAt != "" && source.FirstSeenAt < row.FirstSeenAt {
			row.FirstSeenAt = source.FirstSeenAt
			row.FirstSeenEvidence = source.FirstSeenEvidence
			row.FirstSeenSource = source.FirstSeenSource
		}
		if row.LatestReceivedAt == "" || source.LatestReceivedAt > row.LatestReceivedAt {
			copyAccessPrimaryFields(&row, source)
		}
	}
	for protocol, item := range byProtocol {
		if containsString(canonicalVehicleProtocols, protocol) {
			continue
		}
		source := buildAccessVehicleRow(item, config, now)
		row.ActualProtocols = append(row.ActualProtocols, protocol)
		if source.OnlineState == "online" {
			onlineCount++
		}
		if source.OnlineState == "unknown" {
			unknownCount++
		}
		row.ProtocolStatuses = append(row.ProtocolStatuses, AccessProtocolStatus{
			Protocol: protocol, Expected: false, Connected: true, Provider: source.Provider,
			FirstSeenAt: source.FirstSeenAt, LatestEventAt: source.LatestEventAt, LatestReceivedAt: source.LatestReceivedAt,
			ReportIntervalSec: source.ReportIntervalSec, DataDelaySec: source.DataDelaySec, FreshnessSec: source.FreshnessSec,
			OnlineState: source.OnlineState, ThresholdSec: source.ThresholdSec, DelayAbnormal: source.DelayAbnormal,
			FirstSeenEvidence: source.FirstSeenEvidence, ReportIntervalEvidence: source.ReportIntervalProof,
			Longitude: item.Longitude, Latitude: item.Latitude, SpeedKmh: item.SpeedKmh, SOCPercent: item.SOCPercent, TotalMileageKm: item.TotalMileageKm, DailyMileageKm: item.DailyMileageKm,
		})
		if row.LatestReceivedAt == "" || source.LatestReceivedAt > row.LatestReceivedAt {
			copyAccessPrimaryFields(&row, source)
		}
	}
	switch {
	case connectedCount == 0 && len(row.ActualProtocols) == 0:
		row.ConnectionState = "not_connected"
		row.OnlineState = "never_reported"
	case connectedCount == len(canonicalVehicleProtocols) && onlineCount == connectedCount:
		row.ConnectionState = "healthy"
		row.OnlineState = "online"
	case onlineCount == 0 && unknownCount > 0:
		row.ConnectionState = "incomplete"
		row.OnlineState = "unknown"
	case onlineCount == 0:
		row.ConnectionState = "offline"
		row.OnlineState = "offline"
	case connectedCount < len(canonicalVehicleProtocols):
		row.ConnectionState = "incomplete"
		row.OnlineState = "online"
	default:
		row.ConnectionState = "degraded"
		row.OnlineState = "online"
	}
	return row
}

func accessProtocolThreshold(config AccessThresholdConfig, protocol string) int {
	for _, override := range config.Protocols {
		if strings.EqualFold(override.Protocol, protocol) {
			return override.ThresholdSec
		}
	}
	return config.DefaultThresholdSec
}

func copyAccessPrimaryFields(target *AccessVehicleRow, source AccessVehicleRow) {
	target.Protocol = source.Protocol
	target.Provider = source.Provider
	target.Source = source.Source
	target.LatestEventAt = source.LatestEventAt
	target.LatestReceivedAt = source.LatestReceivedAt
	target.ReportIntervalSec = source.ReportIntervalSec
	target.DataDelaySec = source.DataDelaySec
	target.FreshnessSec = source.FreshnessSec
	target.ThresholdSec = source.ThresholdSec
	target.LatestMessageType = source.LatestMessageType
	target.LatestEventID = source.LatestEventID
	target.LatestError = source.LatestError
	target.ReportIntervalProof = source.ReportIntervalProof
	target.ReportSampleCount = source.ReportSampleCount
}

func buildAccessVehicleRow(item AccessEvidenceRow, config AccessThresholdConfig, now time.Time) AccessVehicleRow {
	threshold := config.DefaultThresholdSec
	for _, override := range config.Protocols {
		if strings.EqualFold(strings.TrimSpace(override.Protocol), strings.TrimSpace(item.Protocol)) {
			threshold = override.ThresholdSec
			break
		}
	}
	row := AccessVehicleRow{
		VIN:                 strings.TrimSpace(item.VIN),
		Plate:               strings.TrimSpace(item.Plate),
		OEM:                 strings.TrimSpace(item.OEM),
		Model:               strings.TrimSpace(item.Model),
		Company:             strings.TrimSpace(item.Company),
		Protocol:            strings.TrimSpace(item.Protocol),
		Provider:            strings.TrimSpace(item.Provider),
		Source:              strings.TrimSpace(item.Source),
		FirstSeenAt:         normalizeAccessTime(item.FirstSeenAt),
		LatestEventAt:       normalizeAccessTime(item.LatestEventAt),
		LatestReceivedAt:    normalizeAccessTime(item.LatestReceivedAt),
		ReportIntervalSec:   item.ReportIntervalSec,
		ThresholdSec:        threshold,
		LatestMessageType:   firstNonEmpty(strings.TrimSpace(item.LatestMessageType), accessMessageType(item.Protocol)),
		LatestEventID:       strings.TrimSpace(item.LatestEventID),
		LatestError:         strings.TrimSpace(item.LatestError),
		FirstSeenEvidence:   strings.TrimSpace(item.FirstSeenEvidence),
		FirstSeenSource:     strings.TrimSpace(item.FirstSeenSource),
		ReportIntervalProof: strings.TrimSpace(item.ReportIntervalProof),
		ReportSampleCount:   item.ReportSampleCount,
	}
	if row.FirstSeenEvidence == "" {
		row.FirstSeenEvidence = "现有实时快照不保存首次接入时间"
	}
	if row.ReportIntervalProof == "" {
		row.ReportIntervalProof = "需要连续上报样本后才能计算"
	}
	eventAt, eventOK := parseAccessTime(item.LatestEventAt)
	receivedAt, receivedOK := parseAccessTime(item.LatestReceivedAt)
	if eventOK && receivedOK {
		delay := int(receivedAt.Sub(eventAt).Seconds())
		row.DataDelaySec = &delay
		row.DelayAbnormal = delay < 0 || delay > config.DelayThresholdSec
		if delay < 0 && row.LatestError == "" {
			row.LatestError = "接收时间早于事件时间"
		}
	}
	latest, latestOK := receivedAt, receivedOK
	if !latestOK {
		latest, latestOK = parseAccessTime(item.LatestUpdatedAt)
	}
	switch {
	case strings.TrimSpace(item.Protocol) == "" && !latestOK && !eventOK:
		row.OnlineState = "never_reported"
	case !latestOK:
		row.OnlineState = "unknown"
		if row.LatestError == "" {
			row.LatestError = "缺少可解析的接收时间"
		}
	default:
		freshness := int(now.Sub(latest).Seconds())
		if freshness < 0 {
			freshness = 0
		}
		row.FreshnessSec = &freshness
		if freshness <= threshold {
			row.OnlineState = "online"
		} else {
			row.OnlineState = "offline"
		}
	}
	return row
}

func keepAccessRow(row AccessVehicleRow, query AccessQuery) bool {
	keyword := strings.ToLower(strings.TrimSpace(query.Keyword))
	if keyword != "" && !strings.Contains(strings.ToLower(row.VIN), keyword) && !strings.Contains(strings.ToLower(row.Plate), keyword) && !strings.Contains(strings.ToLower(row.OEM), keyword) && !strings.Contains(strings.ToLower(row.Model), keyword) && !strings.Contains(strings.ToLower(row.Company), keyword) {
		return false
	}
	if value := strings.TrimSpace(query.Protocol); value != "" && !containsString(row.ActualProtocols, value) {
		return false
	}
	if value := strings.TrimSpace(query.OEM); value != "" && !strings.EqualFold(value, row.OEM) {
		return false
	}
	if value := strings.ToLower(strings.TrimSpace(query.Model)); value != "" && !strings.Contains(strings.ToLower(row.Model), value) {
		return false
	}
	if value := strings.ToLower(strings.TrimSpace(query.Provider)); value != "" {
		matched := false
		for _, status := range row.ProtocolStatuses {
			if strings.Contains(strings.ToLower(status.Provider), value) {
				matched = true
				break
			}
		}
		if !matched {
			return false
		}
	}
	if !accessTimeMatches(row.FirstSeenAt, query.FirstSeenFrom, query.FirstSeenTo) || !accessTimeMatches(row.LatestReceivedAt, query.LatestSeenFrom, query.LatestSeenTo) {
		return false
	}
	if value := strings.TrimSpace(query.OnlineState); value != "" && value != "all" && value != row.OnlineState {
		return false
	}
	if value := strings.TrimSpace(query.ConnectionState); value != "" && value != "all" {
		if value == "attention" {
			if row.ConnectionState == "healthy" {
				return false
			}
		} else if value != row.ConnectionState {
			return false
		}
	}
	switch strings.TrimSpace(query.DelayState) {
	case "abnormal":
		return row.DelayAbnormal
	case "normal":
		return row.DataDelaySec != nil && !row.DelayAbnormal
	}
	return true
}

func validateAccessQuery(query AccessQuery) error {
	for _, item := range []struct{ name, from, to string }{{"首次接入", query.FirstSeenFrom, query.FirstSeenTo}, {"最新上报", query.LatestSeenFrom, query.LatestSeenTo}} {
		start, startOK := parseAccessFilterTime(item.from)
		end, endOK := parseAccessFilterTime(item.to)
		if strings.TrimSpace(item.from) != "" && !startOK || strings.TrimSpace(item.to) != "" && !endOK {
			return clientError{Code: "ACCESS_TIME_INVALID", Message: item.name + "时间格式无效"}
		}
		if startOK && endOK && start.After(end) {
			return clientError{Code: "ACCESS_TIME_RANGE_INVALID", Message: item.name + "开始时间不能晚于结束时间"}
		}
	}
	return nil
}

func parseAccessFilterTime(value string) (time.Time, bool) {
	if strings.TrimSpace(value) == "" {
		return time.Time{}, false
	}
	if parsed, ok := parseTrackRequestTime(value); ok {
		return parsed, true
	}
	return parseAccessTime(value)
}

func accessTimeMatches(value, from, to string) bool {
	if strings.TrimSpace(from) == "" && strings.TrimSpace(to) == "" {
		return true
	}
	actual, ok := parseAccessTime(value)
	if !ok {
		return false
	}
	if start, ok := parseAccessFilterTime(from); ok && actual.Before(start) {
		return false
	}
	if end, ok := parseAccessFilterTime(to); ok && actual.After(end) {
		return false
	}
	return true
}

func validateAccessThresholdUpdate(update AccessThresholdUpdate) error {
	if update.Version <= 0 {
		return clientError{Code: "ACCESS_THRESHOLD_VERSION_REQUIRED", Message: "阈值版本不能为空"}
	}
	if update.DefaultThresholdSec < 30 || update.DefaultThresholdSec > 86400 {
		return clientError{Code: "ACCESS_THRESHOLD_INVALID", Message: "全局在线阈值必须在 30 秒到 24 小时之间"}
	}
	if update.DelayThresholdSec < 1 || update.DelayThresholdSec > 3600 {
		return clientError{Code: "ACCESS_DELAY_THRESHOLD_INVALID", Message: "延迟阈值必须在 1 秒到 1 小时之间"}
	}
	if update.LongOfflineSec < update.DefaultThresholdSec || update.LongOfflineSec > 604800 {
		return clientError{Code: "ACCESS_LONG_OFFLINE_INVALID", Message: "长离线阈值必须不小于在线阈值且不超过 7 天"}
	}
	seen := map[string]struct{}{}
	for _, item := range update.Protocols {
		protocol := strings.ToUpper(strings.TrimSpace(item.Protocol))
		if protocol == "" || item.ThresholdSec < 30 || item.ThresholdSec > 86400 {
			return clientError{Code: "ACCESS_PROTOCOL_THRESHOLD_INVALID", Message: "协议阈值必须包含协议名且位于 30 秒到 24 小时之间"}
		}
		if _, exists := seen[protocol]; exists {
			return clientError{Code: "ACCESS_PROTOCOL_THRESHOLD_DUPLICATED", Message: "协议阈值不能重复"}
		}
		seen[protocol] = struct{}{}
	}
	return nil
}

func normalizeAccessThresholdConfig(config AccessThresholdConfig, now time.Time) AccessThresholdConfig {
	defaults := defaultAccessThresholds(now)
	if config.Version <= 0 {
		return defaults
	}
	if config.DefaultThresholdSec <= 0 {
		config.DefaultThresholdSec = defaults.DefaultThresholdSec
	}
	if config.DelayThresholdSec <= 0 {
		config.DelayThresholdSec = defaults.DelayThresholdSec
	}
	if config.LongOfflineSec <= 0 {
		config.LongOfflineSec = defaults.LongOfflineSec
	}
	if config.Protocols == nil {
		config.Protocols = []AccessProtocolThreshold{}
	}
	if config.Audit == nil {
		config.Audit = []AccessThresholdAudit{}
	}
	return config
}

func parseAccessTime(value string) (time.Time, bool) {
	value = strings.TrimSpace(value)
	if value == "" {
		return time.Time{}, false
	}
	for _, layout := range []string{time.RFC3339Nano, time.RFC3339} {
		if parsed, err := time.Parse(layout, value); err == nil {
			return parsed, true
		}
	}
	for _, layout := range []string{"2006-01-02 15:04:05.000", "2006-01-02 15:04:05"} {
		if parsed, err := time.ParseInLocation(layout, value, time.Local); err == nil {
			return parsed, true
		}
	}
	return time.Time{}, false
}

func normalizeAccessTime(value string) string {
	parsed, ok := parseAccessTime(value)
	if !ok {
		return ""
	}
	return parsed.Format(time.RFC3339)
}

func accessMessageType(protocol string) string {
	switch strings.ToUpper(strings.TrimSpace(protocol)) {
	case "GB32960":
		return "实时信息上报"
	case "JT808":
		return "位置信息汇报"
	case "YUTONG_MQTT":
		return "实时遥测"
	default:
		return ""
	}
}

func accessStateRank(state string) int {
	switch state {
	case "online":
		return 0
	case "offline":
		return 1
	case "never_reported":
		return 2
	default:
		return 3
	}
}

func reportedOnDay(value string, day time.Time) bool {
	parsed, ok := parseAccessTime(value)
	if !ok {
		return false
	}
	year, month, date := parsed.In(day.Location()).Date()
	wantYear, wantMonth, wantDate := day.Date()
	return year == wantYear && month == wantMonth && date == wantDate
}

func addAccessDistribution(items map[string]*AccessDistribution, name string, online bool) {
	item := items[name]
	if item == nil {
		item = &AccessDistribution{Name: name}
		items[name] = item
	}
	item.Total++
	if online {
		item.Online++
	}
}

func sortedAccessDistributions(items map[string]*AccessDistribution) []AccessDistribution {
	result := make([]AccessDistribution, 0, len(items))
	for _, item := range items {
		value := *item
		if value.Total > 0 {
			value.OnlineRate = float64(value.Online) / float64(value.Total) * 100
		}
		result = append(result, value)
	}
	sort.Slice(result, func(i, j int) bool {
		if result[i].Total != result[j].Total {
			return result[i].Total > result[j].Total
		}
		return result[i].Name < result[j].Name
	})
	return result
}
