// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

package db

import (
	"context"
	"encoding/json"
	"log"
	"time"

	sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
	"github.com/MeshCore-Beacon/beacon-server/internal/api"
	"github.com/jackc/pgx/v5/pgtype"
)

func (s *Store) GetStatsOverview(ctx context.Context, iatas []string) (*api.StatsOverview, error) {
	row, err := s.q.GetStatsOverview(ctx, iatas)
	if err != nil {
		return nil, err
	}
	return &api.StatsOverview{
		TotalPackets:      row.TotalPackets,
		TotalObservations: row.TotalObservations,
		ActiveObservers:   row.ActiveObservers,
		ActiveIATAs:       row.ActiveIatas,
		WindowHours:       24,
	}, nil
}

func (s *Store) GetStatsObservations(ctx context.Context, iatas []string, since time.Time) ([]api.ObservationPoint, error) {
	if since.IsZero() {
		since = time.Now().Add(-7 * 24 * time.Hour)
	}
	interval := time.Since(since)
	rows, err := s.q.GetHourlyStats(ctx, sqlc.GetHourlyStatsParams{
		Column1: iatas,
		Column2: pgtype.Interval{Microseconds: int64(interval.Hours()) * 3600 * 1e6, Valid: true},
	})
	if err != nil {
		return nil, err
	}
	points := make([]api.ObservationPoint, 0, len(rows))
	for _, v := range rows {
		points = append(points, api.ObservationPoint{
			Hour:             v.Hour.Time.UnixMilli(),
			IATA:             v.Iata,
			ObservationCount: v.ObservationCount,
			UniquePackets:    v.UniquePackets,
			ActiveObservers:  v.ActiveObservers,
		})
	}
	return points, nil
}

func (s *Store) GetStatsPayloadBreakdown(ctx context.Context, iatas []string, since time.Time) ([]api.PayloadBreakdownItem, error) {
	if since.IsZero() {
		since = time.Now().Add(-24 * time.Hour)
	}
	interval := time.Since(since)
	rows, err := s.q.GetStatsPayloadBreakdown(ctx, sqlc.GetStatsPayloadBreakdownParams{
		Column1: iatas,
		Column2: pgtype.Interval{Microseconds: int64(interval.Hours()) * 3600 * 1e6, Valid: true},
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.PayloadBreakdownItem, 0, len(rows))
	for _, v := range rows {
		if v.PayloadType == nil {
			continue
		}
		items = append(items, api.PayloadBreakdownItem{
			PayloadType:     *v.PayloadType,
			PayloadTypeName: api.PayloadTypeName(*v.PayloadType),
			Count:           v.Count,
		})
	}
	return items, nil
}

func (s *Store) GetStatsTopNodes(ctx context.Context, iatas []string, limit int32) ([]api.TopNode, error) {
	rows, err := s.q.GetTopNodes(ctx, sqlc.GetTopNodesParams{
		Column1: iatas,
		Limit:   limit,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.TopNode, 0, len(rows))
	for _, v := range rows {
		var count int64
		if v.ObservationCount != nil {
			count = *v.ObservationCount
		}
		items = append(items, api.TopNode{
			NodeID:           v.NodeID,
			NodeName:         v.Name,
			NodeType:         v.NodeType,
			NodeTypeName:     api.NodeTypeName(v.NodeType),
			IATA:             v.Iata,
			ObservationCount: count,
			LastHeard:        v.LastHeard.Time.UnixMilli(),
		})
	}
	return items, nil
}

func (s *Store) GetStatsTopObservers(ctx context.Context, iatas []string, since time.Time, limit int32) ([]api.TopObserver, error) {
	if since.IsZero() {
		since = time.Now().Add(-24 * time.Hour)
	}
	interval := time.Since(since)
	rows, err := s.q.GetStatsTopObservers(ctx, sqlc.GetStatsTopObserversParams{
		Column1: pgtype.Interval{Microseconds: int64(interval.Hours()) * 3600 * 1e6, Valid: true},
		Column2: iatas,
		Limit:   limit,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.TopObserver, 0, len(rows))
	for _, v := range rows {
		items = append(items, api.TopObserver{
			ObserverID:       v.ID,
			DisplayName:      v.DisplayName,
			ObserverType:     v.ObserverType,
			IATA:             v.Iata,
			ObservationCount: v.ObservationCount,
		})
	}
	return items, nil
}

func (s *Store) GetStatsTopAdvertisers(ctx context.Context, iatas []string, since time.Time, limit int32) ([]api.TopAdvertiser, error) {
	if since.IsZero() {
		since = time.Now().Add(-24 * time.Hour)
	}
	interval := time.Since(since)
	rows, err := s.q.GetStatsTopAdvertisers(ctx, sqlc.GetStatsTopAdvertisersParams{
		Column1: pgtype.Interval{Microseconds: int64(interval.Hours()) * 3600 * 1e6, Valid: true},
		Column2: iatas,
		Limit:   limit,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.TopAdvertiser, 0, len(rows))
	for _, v := range rows {
		items = append(items, api.TopAdvertiser{
			NodeID:            v.ID,
			NodeName:          v.Name,
			NodeType:          v.NodeType,
			NodeTypeName:      api.NodeTypeName(v.NodeType),
			IATA:              v.Iata,
			AdvertCount:       v.AdvertCount,
			FloodAdvertCount:  v.FloodAdvertCount,
			DirectAdvertCount: v.DirectAdvertCount,
			LastHeard:         v.LastHeard.Time.UnixMilli(),
		})
	}
	return items, nil
}

// GetStatsClockDrift returns repeaters/room servers whose current advert-derived clock
// drift exceeds the Store's configured threshold, worst first.
func (s *Store) GetStatsClockDrift(ctx context.Context, iatas []string, limit int32) ([]api.ClockDriftEntry, error) {
	thresholdSeconds := int32(s.clockDriftThreshold / time.Second)
	rows, err := s.q.GetStatsClockDrift(ctx, sqlc.GetStatsClockDriftParams{
		Column1: thresholdSeconds,
		Column2: iatas,
		Limit:   limit,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.ClockDriftEntry, 0, len(rows))
	for _, v := range rows {
		entry := api.ClockDriftEntry{
			NodeID:            v.ID,
			NodeName:          v.Name,
			NodeType:          v.NodeType,
			NodeTypeName:      api.NodeTypeName(v.NodeType),
			ClockDriftSeconds: int(*v.DeviceClockDriftSeconds),
			ClockCheckedAt:    v.LastAdvertAt.Time.UnixMilli(),
		}
		if len(v.Iatas) > 0 {
			if err := json.Unmarshal(v.Iatas, &entry.IATAs); err != nil {
				log.Printf("store: failed to unmarshal clock drift node iatas: %v", err)
				entry.IATAs = []api.NodeIATA{}
			}
		}
		items = append(items, entry)
	}
	return items, nil
}

func (s *Store) GetStatsTopTalkers(ctx context.Context, iatas []string, since time.Time, limit int32) ([]api.TopTalker, error) {
	if since.IsZero() {
		since = time.Now().Add(-24 * time.Hour)
	}
	interval := time.Since(since)
	rows, err := s.q.GetStatsTopTalkers(ctx, sqlc.GetStatsTopTalkersParams{
		Column1: pgtype.Interval{Microseconds: int64(interval.Hours()) * 3600 * 1e6, Valid: true},
		Column2: iatas,
		Limit:   limit,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.TopTalker, 0, len(rows))
	for _, v := range rows {
		var senderName string
		if v.SenderName != nil {
			senderName = *v.SenderName
		}
		items = append(items, api.TopTalker{
			SenderName:   senderName,
			MessageCount: v.MessageCount,
			LastSent:     v.LastSent.Time.UnixMilli(),
		})
	}
	return items, nil
}

func (s *Store) GetRadioPresets(ctx context.Context, preset string, iatas []string) ([]api.RadioPreset, error) {
	rows, err := s.q.GetRadioPresets(ctx, sqlc.GetRadioPresetsParams{
		Column1: preset,
		Column2: iatas,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.RadioPreset, 0, len(rows))
	for _, v := range rows {
		items = append(items, api.RadioPreset{
			Preset:     v.Preset,
			IATA:       v.Iata,
			SourceType: v.SourceType,
			Count:      v.Count,
		})
	}
	return items, nil
}

func (s *Store) GetScopeStats(ctx context.Context) ([]api.ScopeStats, error) {
	rows, err := s.q.GetScopeStats(ctx)
	if err != nil {
		return nil, err
	}
	items := make([]api.ScopeStats, 0, len(rows))
	for _, r := range rows {
		items = append(items, api.ScopeStats{
			Name:          r.Name,
			PacketCount:   r.PacketCount,
			ObserverCount: r.ObserverCount,
			NodeCount:     r.NodeCount,
		})
	}
	return items, nil
}

func (s *Store) GetStatsNodeTypes(ctx context.Context, iatas []string) ([]api.NodeTypeCount, error) {
	rows, err := s.q.GetStatsNodeTypes(ctx, iatas)
	if err != nil {
		return nil, err
	}
	result := make([]api.NodeTypeCount, 0, len(rows))
	for _, r := range rows {
		result = append(result, api.NodeTypeCount{
			NodeType:     r.NodeType,
			NodeTypeName: api.NodeTypeName(r.NodeType),
			Count:        r.Count,
		})
	}
	return result, nil
}

func (s *Store) RefreshHourlyStats(ctx context.Context) error {
	return s.q.RefreshHourlyStats(ctx)
}

func (s *Store) RefreshTopNodes(ctx context.Context) error {
	return s.q.RefreshTopNodes(ctx)
}

func (s *Store) RefreshPayloadBreakdown(ctx context.Context) error {
	return s.q.RefreshPayloadBreakdown(ctx)
}

func (s *Store) RefreshTopTalkers(ctx context.Context) error {
	return s.q.RefreshTopTalkers(ctx)
}

func (s *Store) RefreshTopAdvertisers(ctx context.Context) error {
	return s.q.RefreshTopAdvertisers(ctx)
}

func (s *Store) RefreshTopObservers(ctx context.Context) error {
	return s.q.RefreshTopObservers(ctx)
}

func (s *Store) RefreshRadioPresets(ctx context.Context) error {
	return s.q.RefreshRadioPresets(ctx)
}
