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

package db

import (
	"context"
	"errors"
	"testing"
	"time"

	sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
	mockdb "github.com/MeshCore-Beacon/beacon-server/db/sqlc/mock"
	"github.com/jackc/pgx/v5/pgtype"
	"go.uber.org/mock/gomock"
)

func TestListTraceTags_Empty(t *testing.T) {
	ctrl := gomock.NewController(t)
	mock := mockdb.NewMockQuerier(ctrl)

	mock.EXPECT().
		ListTraceTags(gomock.Any(), gomock.Any()).
		Return([]sqlc.ListTraceTagsRow{}, nil)

	store := &Store{q: mock}
	items, err := store.ListTraceTags(context.Background(), []string{"YVR"}, "", "", time.Time{}, time.Time{}, time.Time{}, 10)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(items) != 0 {
		t.Errorf("expected 0 items, got %d", len(items))
	}
}

func TestListTraceTags_WithPayload(t *testing.T) {
	ctrl := gomock.NewController(t)
	mock := mockdb.NewMockQuerier(ctrl)

	firstHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
	lastHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000001000), Valid: true}
	payload := []byte(`{"pathHashes":["aabb","ccdd"],"snrValues":[10,20]}`)

	mock.EXPECT().
		ListTraceTags(gomock.Any(), gomock.Any()).
		Return([]sqlc.ListTraceTagsRow{
			{
				TraceTag:     "trace-001",
				FirstHeardAt: firstHeard,
				LastHeardAt:  lastHeard,
				PacketCount:  3,
				IataCount:    1,
				TraceType:    "trace",
				BestPayload:  payload,
			},
		}, nil)

	store := &Store{q: mock}
	items, err := store.ListTraceTags(context.Background(), []string{"YVR"}, "", "", time.Time{}, time.Time{}, time.Time{}, 10)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(items) != 1 {
		t.Fatalf("expected 1 item, got %d", len(items))
	}
	if items[0].TraceTag != "trace-001" {
		t.Errorf("expected TraceTag trace-001, got %s", items[0].TraceTag)
	}
	if len(items[0].PathHashes) != 2 {
		t.Errorf("expected 2 path hashes, got %d", len(items[0].PathHashes))
	}
	if items[0].SNRValues[0] != 10 {
		t.Errorf("expected SNR 10, got %f", items[0].SNRValues[0])
	}
}

func TestGetTraceByTag_Empty(t *testing.T) {
	ctrl := gomock.NewController(t)
	mock := mockdb.NewMockQuerier(ctrl)

	mock.EXPECT().
		GetPacketsByTraceTag(gomock.Any(), "trace-001").
		Return([]sqlc.GetPacketsByTraceTagRow{}, nil)

	store := &Store{q: mock}
	detail, err := store.GetTraceByTag(context.Background(), "trace-001")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if detail != nil {
		t.Errorf("expected nil for empty result, got %v", detail)
	}
}

func TestGetTraceByTag_WithPacket(t *testing.T) {
	ctrl := gomock.NewController(t)
	mock := mockdb.NewMockQuerier(ctrl)

	firstHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
	lastHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000001000), Valid: true}
	// aabbccdd is valid hex for the packet hash
	parsedPayload := []byte(`{"pathHashes":["aabb"],"snrValues":[15.0],"flags":0}`)

	scopeName := "default"
	mock.EXPECT().
		GetPacketsByTraceTag(gomock.Any(), "trace-001").
		Return([]sqlc.GetPacketsByTraceTagRow{
			{
				PacketHashHex: "aabbccdd",
				RouteType:     1,
				ScopeName:     &scopeName,
				FirstHeardAt:  firstHeard,
				LastHeardAt:   lastHeard,
				ParsedPayload: parsedPayload,
				Iatas:         []string{"YVR"},
			},
		}, nil)

	mock.EXPECT().
		ResolvePathHashesP2(gomock.Any(), sqlc.ResolvePathHashesP2Params{
			Iata:    "YVR",
			Column2: [][]byte{{0xaa, 0xbb}},
		}).
		Return([]sqlc.ResolvePathHashesP2Row{}, nil)

	store := &Store{q: mock}
	detail, err := store.GetTraceByTag(context.Background(), "trace-001")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if detail == nil {
		t.Fatal("expected detail, got nil")
	}
	if detail.TraceTag != "trace-001" {
		t.Errorf("expected TraceTag trace-001, got %s", detail.TraceTag)
	}
	if len(detail.Packets) != 1 {
		t.Fatalf("expected 1 packet, got %d", len(detail.Packets))
	}
	if len(detail.Packets[0].RawPath) != 1 {
		t.Errorf("expected 1 raw hop, got %d", len(detail.Packets[0].RawPath))
	}
}

func TestGetTraceByTag_DBError(t *testing.T) {
	ctrl := gomock.NewController(t)
	mock := mockdb.NewMockQuerier(ctrl)

	mock.EXPECT().
		GetPacketsByTraceTag(gomock.Any(), "trace-001").
		Return(nil, errors.New("db error"))

	store := &Store{q: mock}
	_, err := store.GetTraceByTag(context.Background(), "trace-001")
	if err == nil {
		t.Fatal("expected error, got nil")
	}
}
