package db

import (
	"context"
	"fmt"
	"testing"
	"time"

	"code.justin.tv/creator-collab/log"
	"code.justin.tv/live/plucky/api/rpc"
	"github.com/stretchr/testify/require"
	"google.golang.org/protobuf/types/known/timestamppb"
)

const service1 = "streamschedule"

func TestDB(t *testing.T) {
	db, err := New(log.NewDevelopmentLogger())
	require.NoError(t, err)
	require.NotNil(t, db)

	ctx := context.Background()

	trs, err := db.GetCachedTimeRanges(ctx, service1)
	require.NoError(t, err)
	require.Empty(t, trs)

	fetchedOccurrences, err := db.GetOccurrences(ctx, service1, TimeRange{
		Start: newTime(3),
		End:   newTime(6),
	})
	require.NoError(t, err)
	require.Empty(t, fetchedOccurrences)
}

func TestInsertingDuplicates(t *testing.T) {
	db, err := New(log.NewDevelopmentLogger())
	require.NoError(t, err)
	require.NotNil(t, db)

	ctx := context.Background()

	occ1 := newOccurrence(t, service1, "a", 1, 2)
	occs := []*rpc.Occurrence{occ1}
	cachedTimeRanges := &CachedTimeRanges{
		Real: []TimeRange{
			{Start: newTime(1), End: newTime(5)},
		},
		Safe: []TimeRange{
			{Start: newTime(1), End: newTime(5)},
		},
	}

	err = db.InsertOccurrences(ctx, service1, occs, cachedTimeRanges)
	require.NoError(t, err)

	err = db.InsertOccurrences(ctx, service1, occs, cachedTimeRanges)
	require.NoError(t, err)

	fetchedOccurrences, err := db.GetOccurrences(ctx, service1, TimeRange{
		Start: newTime(1),
		End:   newTime(2),
	})
	require.NoError(t, err)
	require.Equal(t, occs, fetchedOccurrences)
}

func TestGetTrendMap(t *testing.T) {
	db, err := New(log.NewDevelopmentLogger())
	require.NoError(t, err)
	require.NotNil(t, db)

	ctx := context.Background()

	occ1 := newOccurrence(t, service1, "a", 1, 1)
	occ2 := newOccurrence(t, service1, "a", 2, 1)
	occ3 := newOccurrence(t, service1, "a", 3, 2)
	occ4 := newOccurrence(t, service1, "a", 4, 3)
	occ5 := newOccurrence(t, service1, "a", 5, 3)
	occ6 := newOccurrence(t, service1, "a", 6, 3)
	occ1.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC))
	occ2.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 1, 1, 0, 0, time.UTC))
	occ3.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 2, 0, 0, 0, time.UTC))
	occ4.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 3, 0, 0, 0, time.UTC))
	occ5.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 3, 30, 0, 0, time.UTC))
	occ6.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 3, 59, 0, 0, time.UTC))

	occ7 := newOccurrence(t, service1, "b", 1, 2)
	occ7.Timestamp = timeToTimestamp(t, time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC))

	occs := []*rpc.Occurrence{occ1, occ2, occ3, occ4, occ5, occ6, occ7}
	queryRange := TimeRange{
		Start: newTime(1),
		End:   newTime(3),
	}
	cachedTimeRanges := &CachedTimeRanges{
		Real: []TimeRange{
			{
				Start: newTime(1),
				End:   newTime(3),
			},
		},
		Safe: []TimeRange{
			{
				Start: newTime(1),
				End:   newTime(3),
			},
		},
	}

	err = db.InsertOccurrences(ctx, service1, occs, cachedTimeRanges)
	require.NoError(t, err)

	trendPointMap, err := db.GetHourTrendPointMap(ctx, service1, queryRange)
	require.NoError(t, err)
	require.Len(t, trendPointMap, 2)
	require.Equal(t, map[string][]*rpc.TrendPoint{
		"a": {
			{
				Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 3, 0, 0, 0, time.UTC)),
				Count: 3,
			}, {
				Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 2, 0, 0, 0, time.UTC)),
				Count: 1,
			}, {
				Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)),
				Count: 2,
			},
		},
		"b": {
			{
				Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)),
				Count: 1,
			},
		},
	}, trendPointMap)

	trendPoints, err := db.GetDayTrendPoints(ctx, service1, "a", queryRange)
	require.NoError(t, err)
	require.Equal(t, []*rpc.TrendPoint{
		{
			Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 3, 0, 0, 0, time.UTC)),
			Count: 3,
		}, {
			Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 2, 0, 0, 0, time.UTC)),
			Count: 1,
		}, {
			Start: timeToTimestamp(t, time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC)),
			Count: 2,
		},
	}, trendPoints)
}

func TestInserts(t *testing.T) {
	db, err := New(log.NewDevelopmentLogger())
	require.NoError(t, err)
	require.NotNil(t, db)

	ctx := context.Background()

	occ1 := newOccurrence(t, service1, "a", 1, 2)
	occ2 := newOccurrence(t, service1, "a", 2, 3)
	occ3 := newOccurrence(t, service1, "b", 1, 4)
	{
		occs := []*rpc.Occurrence{
			occ1,
			occ2,
			occ3,
		}
		cachedTimeRanges := &CachedTimeRanges{
			Real: []TimeRange{
				{
					Start: newTime(1),
					End:   newTime(5),
				},
			},
			Safe: []TimeRange{
				{
					Start: newTime(1),
					End:   newTime(4),
				},
			},
		}

		err = db.InsertOccurrences(ctx, service1, occs, cachedTimeRanges)
		require.NoError(t, err)

		fetchedOccurrences, err := db.GetOccurrences(ctx, service1, TimeRange{
			Start: newTime(3),
			End:   newTime(6),
		})
		require.NoError(t, err)
		require.Equal(t, []*rpc.Occurrence{occ3, occ2}, fetchedOccurrences)

		fetchedOccurrences, err = db.GetOccurrencesByFingerprint(ctx, service1, "a", TimeRange{
			Start: newTime(3),
			End:   newTime(6),
		})
		require.NoError(t, err)
		require.Equal(t, []*rpc.Occurrence{occ2}, fetchedOccurrences)

		ctr, err := db.GetCachedTimeRanges(ctx, service1)
		require.NoError(t, err)
		require.Equal(t, cachedTimeRanges, ctr)
	}

	occ4 := newOccurrence(t, service1, "a", 3, 7)
	occ5 := newOccurrence(t, service1, "a", 4, 8)
	occ6 := newOccurrence(t, service1, "b", 5, 9)
	{
		occs := []*rpc.Occurrence{occ4, occ5, occ6}
		cachedTimeRanges := &CachedTimeRanges{
			Real: []TimeRange{
				{
					Start: newTime(1),
					End:   newTime(5),
				},
				{
					Start: newTime(7),
					End:   newTime(9),
				},
			},
			Safe: []TimeRange{
				{
					Start: newTime(1),
					End:   newTime(5),
				},
				{
					Start: newTime(7),
					End:   newTime(8),
				},
			},
		}

		err := db.InsertOccurrences(ctx, service1, occs, cachedTimeRanges)
		require.NoError(t, err)

		fetchedOccurrences, err := db.GetOccurrences(ctx, service1, TimeRange{
			Start: newTime(3),
			End:   newTime(8),
		})
		require.NoError(t, err)
		require.Equal(t, []*rpc.Occurrence{occ5, occ4, occ3, occ2}, fetchedOccurrences)

		trs, err := db.GetCachedTimeRanges(ctx, service1)
		require.NoError(t, err)
		require.Equal(t, cachedTimeRanges, trs)
	}
}

// TODO: Delete this?
func timeToTimestamp(_ *testing.T, time time.Time) *timestamppb.Timestamp {
	return timestamppb.New(time)
}

func newTime(day int) time.Time {
	return time.Date(2020, time.January, day, 0, 0, 0, 0, time.UTC)
}

func newOccurrence(_ *testing.T, service, fingerprint string, occurrenceNum, day int) *rpc.Occurrence {
	timestampTime := newTime(day)
	timestamp := timestamppb.New(timestampTime)

	return &rpc.Occurrence{
		OccurrenceId: fmt.Sprintf("%s_%d", fingerprint, occurrenceNum),
		Service:      service,
		Fingerprint:  fingerprint,
		Timestamp:    timestamp,
		Level:        "error",
		Message:      fmt.Sprintf("%s_%d_message", fingerprint, occurrenceNum),
		RawJson:      fmt.Sprintf("%s_%d_raw_json", fingerprint, occurrenceNum),
		StackTrace: []*rpc.StackTrace{
			{
				FullFilePath:  "fullFilePath",
				Line:          2,
				Method:        "method",
				ShortFilePath: "shortPath",
				BrazilPkgName: "brazil",
				Relevant:      true,
			},
		},
		RequestId:  "requestid",
		Operation:  "operation",
		Dependency: "dependency",
	}
}
