package ui

import (
	"sort"
	"testing"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/gkampitakis/go-snaps/snaps"
	"github.com/stretchr/testify/require"
	"github.com/wagoodman/go-partybus"
	"github.com/wagoodman/go-progress"

	"github.com/anchore/bubbly/bubbles/taskprogress"
	"github.com/anchore/grype/grype/event"
	"github.com/anchore/grype/grype/event/monitor"
	"github.com/anchore/grype/grype/vulnerability"
)

func TestHandler_handleVulnerabilityScanningStarted(t *testing.T) {
	tests := []struct {
		name       string
		eventFn    func(*testing.T) partybus.Event
		iterations int
	}{
		{
			name: "vulnerability scanning in progress",
			eventFn: func(t *testing.T) partybus.Event {
				return partybus.Event{
					Type:  event.VulnerabilityScanningStarted,
					Value: getVulnerabilityMonitor(false),
				}
			},
		},
		{
			name: "vulnerability scanning complete",
			eventFn: func(t *testing.T) partybus.Event {
				return partybus.Event{
					Type:  event.VulnerabilityScanningStarted,
					Value: getVulnerabilityMonitor(true),
				}
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := tt.eventFn(t)
			handler := New(DefaultHandlerConfig())
			handler.WindowSize = tea.WindowSizeMsg{
				Width:  100,
				Height: 80,
			}

			models, _ := handler.Handle(e)
			require.Len(t, models, 2)

			t.Run("task line", func(t *testing.T) {
				tsk, ok := models[0].(taskprogress.Model)
				require.True(t, ok)

				got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
					Time:     time.Now(),
					Sequence: tsk.Sequence(),
					ID:       tsk.ID(),
				})
				t.Log(got)
				snaps.MatchSnapshot(t, got)
			})

			t.Run("tree", func(t *testing.T) {
				log, ok := models[1].(vulnerabilityProgressTree)
				require.True(t, ok)
				got := runModel(t, log, tt.iterations, vulnerabilityProgressTreeTickMsg{
					Time:     time.Now(),
					Sequence: log.sequence,
					ID:       log.id,
				})
				t.Log(got)
				snaps.MatchSnapshot(t, got)
			})

		})
	}
}

func getVulnerabilityMonitor(completed bool) monitor.Matching {
	pkgs := &progress.Manual{}
	pkgs.SetTotal(-1)
	if completed {
		pkgs.Set(2000)
		pkgs.SetCompleted()
	} else {
		pkgs.Set(300)
	}

	vulns := &progress.Manual{}
	vulns.SetTotal(-1)
	if completed {
		vulns.Set(45)
		vulns.SetCompleted()
	} else {
		vulns.Set(40)
	}

	fixed := &progress.Manual{}
	fixed.SetTotal(-1)
	if completed {
		fixed.Set(35)
		fixed.SetCompleted()
	} else {
		fixed.Set(30)
	}

	ignored := &progress.Manual{}
	ignored.SetTotal(-1)
	if completed {
		ignored.Set(5)
		ignored.SetCompleted()
	} else {
		ignored.Set(4)
	}

	dropped := &progress.Manual{}
	dropped.SetTotal(-1)
	if completed {
		dropped.Set(3)
		dropped.SetCompleted()
	} else {
		dropped.Set(2)
	}

	bySeverityWriter := map[vulnerability.Severity]*progress.Manual{
		vulnerability.CriticalSeverity:   {},
		vulnerability.HighSeverity:       {},
		vulnerability.MediumSeverity:     {},
		vulnerability.LowSeverity:        {},
		vulnerability.NegligibleSeverity: {},
		vulnerability.UnknownSeverity:    {},
	}

	allSeverities := vulnerability.AllSeverities()
	sort.Sort(sort.Reverse(vulnerability.Severities(allSeverities)))

	var count int64 = 1
	for _, sev := range allSeverities {
		bySeverityWriter[sev].Add(count)
		count++
	}
	bySeverityWriter[vulnerability.UnknownSeverity].Add(count)

	bySeverity := map[vulnerability.Severity]progress.Monitorable{}

	for k, v := range bySeverityWriter {
		bySeverity[k] = v
	}

	return monitor.Matching{
		PackagesProcessed: pkgs,
		MatchesDiscovered: vulns,
		Fixed:             fixed,
		Ignored:           ignored,
		Dropped:           dropped,
		BySeverity:        bySeverity,
	}
}
