package process_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/weaveworks/common/mtime"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/report"
)

type mockWalker struct {
	processes []process.Process
}

func (m *mockWalker) Walk(f func(process.Process, process.Process)) error {
	for _, p := range m.processes {
		f(p, process.Process{})
	}
	return nil
}

var processes = []process.Process{
	{PID: 1, PPID: 0, Name: "init"},
	{PID: 2, PPID: 1, Name: "bash"},
	{PID: 3, PPID: 1, Name: "apache", Threads: 2},
	{PID: 4, PPID: 2, Name: "ping", Cmdline: "ping foo.bar.local"},
	{PID: 5, PPID: 1, Cmdline: "tail -f /var/log/syslog"},
}

func testReporter(t *testing.T, noCommandLineArguments bool, test func(report.Report)) {
	walker := &mockWalker{processes: processes}
	getDeltaTotalJiffies := func() (uint64, float64, error) { return 0, 0., nil }
	now := time.Now()
	mtime.NowForce(now)
	defer mtime.NowReset()

	rpt, err := process.NewReporter(walker, "", getDeltaTotalJiffies, noCommandLineArguments).Report()
	if err != nil {
		t.Error(err)
	}
	test(rpt)
}

func TestInit(t *testing.T) {
	test := func(rpt report.Report) {
		node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "1")]
		if !ok {
			t.Errorf("Expected report to include the pid 1 init")
		}
		if name, ok := node.Latest.Lookup(process.Name); !ok || name != processes[0].Name {
			t.Errorf("Expected %q got %q", processes[0].Name, name)
		}
	}
	testReporter(t, false, test)
}

func TestProcesses(t *testing.T) {
	test := func(rpt report.Report) {
		node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "2")]
		if !ok {
			t.Errorf("Expected report to include the pid 2 bash")
		}
		if name, ok := node.Latest.Lookup(process.Name); !ok || name != processes[1].Name {
			t.Errorf("Expected %q got %q", processes[1].Name, name)
		}
		if ppid, ok := node.Latest.Lookup(process.PPID); !ok || ppid != fmt.Sprint(processes[1].PPID) {
			t.Errorf("Expected %d got %q", processes[1].PPID, ppid)
		}
		if memoryUsage, ok := node.Metrics[process.MemoryUsage]; !ok {
			t.Errorf("Expected memory usage metric, but not found")
		} else if sample, ok := memoryUsage.LastSample(); !ok {
			t.Errorf("Expected memory usage metric to have a sample, but there were none")
		} else if sample.Value != 0. {
			t.Errorf("Expected memory usage metric sample %f, got %f", 0., sample.Value)
		}
	}

	testReporter(t, false, test)
}

func TestThreadCounts(t *testing.T) {
	test := func(rpt report.Report) {
		node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "3")]
		if !ok {
			t.Errorf("Expected report to include the pid 3 apache")
		}
		if threads, ok := node.Latest.Lookup(process.Threads); !ok || threads != fmt.Sprint(processes[2].Threads) {
			t.Errorf("Expected %d got %q", processes[2].Threads, threads)
		}
	}
	testReporter(t, false, test)
}

func TestCmdline(t *testing.T) {
	test := func(rpt report.Report) {
		node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "4")]
		if !ok {
			t.Errorf("Expected report to include the pid 4 ping")
		}
		if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint(processes[3].Cmdline) {
			t.Errorf("Expected %q got %q", processes[3].Cmdline, cmdline)
		}
	}
	testReporter(t, false, test)
}

func TestAnonymous(t *testing.T) {
	test := func(rpt report.Report) {
		node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "5")]
		if !ok {
			t.Errorf("Expected report to include the pid 5 tail")
		}
		if name, ok := node.Latest.Lookup(process.Name); ok {
			t.Errorf("Expected no name, but got %q", name)
		}
		if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint(processes[4].Cmdline) {
			t.Errorf("Expected %q got %q", processes[4].Cmdline, cmdline)
		}
	}
	testReporter(t, false, test)
}

func TestCmdlineRemoval(t *testing.T) {
	test := func(rpt report.Report) {
		node, ok := rpt.Process.Nodes[report.MakeProcessNodeID("", "4")]
		if !ok {
			t.Errorf("Expected report to include the pid 4 ping")
		}
		if cmdline, ok := node.Latest.Lookup(process.Cmdline); !ok || cmdline != fmt.Sprint("ping") {
			t.Errorf("Expected %q got %q", "ping", cmdline)
		}
	}
	testReporter(t, true, test)
}

func BenchmarkReporter(t *testing.B) {
	walker := &mockWalker{processes: processes}
	getDeltaTotalJiffies := func() (uint64, float64, error) { return 0, 0., nil }
	reporter := process.NewReporter(walker, "", getDeltaTotalJiffies, false)
	t.ResetTimer()

	for i := 0; i < t.N; i++ {
		_, err := reporter.Report()
		if err != nil {
			t.Error(err)
		}
	}
}
