package oonimkall

import (
	"encoding/json"
	"errors"
	"testing"

	"github.com/ooni/probe-cli/v3/internal/model"
)

type eventlike struct {
	Key   string                 `json:"key"`
	Value map[string]interface{} `json:"value"`
}

func TestStartTaskGood(t *testing.T) {
	if testing.Short() {
		t.Skip("skip test in short mode")
	}
	task, err := StartTask(`{
		"log_level": "DEBUG",
		"name": "Example",
		"options": {
			"software_name": "oonimkall-test",
			"software_version": "0.1.0"
		},
		"state_dir": "testdata/state",
		"version": 1
	}`)
	if err != nil {
		t.Fatal(err)
	}
	for !task.IsDone() {
		eventstr := task.WaitForNextEvent()
		var event eventlike
		if err := json.Unmarshal([]byte(eventstr), &event); err != nil {
			t.Fatal(err)
		}
		if event.Key == "failure.startup" {
			t.Fatal("unexpected failure.startup event")
		}
	}
	// make sure we only see task_terminated at this point
	for {
		eventstr := task.WaitForNextEvent()
		var event eventlike
		if err := json.Unmarshal([]byte(eventstr), &event); err != nil {
			t.Fatal(err)
		}
		if event.Key == "task_terminated" {
			break
		}
		t.Fatalf("unexpected event.Key: %s", event.Key)
	}
}

func TestStartTaskInvalidJSON(t *testing.T) {
	task, err := StartTask(`{`)
	var syntaxerr *json.SyntaxError
	if !errors.As(err, &syntaxerr) {
		t.Fatal("not the expected error")
	}
	if task != nil {
		t.Fatal("task is not nil")
	}
}

func TestStartTaskCountBytesForExample(t *testing.T) {
	if testing.Short() {
		t.Skip("skip test in short mode")
	}
	task, err := StartTask(`{
		"name": "Example",
		"options": {
			"software_name": "oonimkall-test",
			"software_version": "0.1.0"
		},
		"state_dir": "testdata/state",
		"version": 1
	}`)
	if err != nil {
		t.Fatal(err)
	}
	var downloadKB, uploadKB float64
	for !task.IsDone() {
		eventstr := task.WaitForNextEvent()
		var event eventlike
		if err := json.Unmarshal([]byte(eventstr), &event); err != nil {
			t.Fatal(err)
		}
		switch event.Key {
		case "failure.startup":
			t.Fatal(eventstr)
		case "status.end":
			downloadKB = event.Value["downloaded_kb"].(float64)
			uploadKB = event.Value["uploaded_kb"].(float64)
		}
	}
	if downloadKB == 0 {
		t.Fatal("downloadKB is zero")
	}
	if uploadKB == 0 {
		t.Fatal("uploadKB is zero")
	}
}

func TestPrivacyAndScrubbing(t *testing.T) {
	if testing.Short() {
		t.Skip("skip test in short mode")
	}
	task, err := StartTask(`{
			"assets_dir": "../testdata/oonimkall/assets",
			"name": "Example",
			"options": {
				"software_name": "oonimkall-test",
				"software_version": "0.1.0"
			},
			"state_dir": "../testdata/oonimkall/state",
			"version": 1
		}`)
	if err != nil {
		t.Fatal(err)
	}
	var m *model.Measurement
	for !task.IsDone() {
		eventstr := task.WaitForNextEvent()
		var event eventlike
		if err := json.Unmarshal([]byte(eventstr), &event); err != nil {
			t.Fatal(err)
		}
		switch event.Key {
		case "failure.startup":
			t.Fatal(eventstr)
		case "measurement":
			v := []byte(event.Value["json_str"].(string))
			m = new(model.Measurement)
			if err := json.Unmarshal(v, &m); err != nil {
				t.Fatal(err)
			}
		}
	}
	if m == nil {
		t.Fatal("measurement is nil")
	}
	if m.ProbeASN == "AS0" || m.ProbeCC == "ZZ" || m.ProbeIP != "127.0.0.1" {
		t.Fatal("unexpected result")
	}
}

func TestNonblockWithFewEvents(t *testing.T) {
	if testing.Short() {
		t.Skip("skip test in short mode")
	}
	// This test tests whether we won't block for a small
	// number of events emitted by the task
	task, err := StartTask(`{
		"name": "Example",
		"options": {
			"software_name": "oonimkall-test",
			"software_version": "0.1.0"
		},
		"state_dir": "testdata/state",
		"version": 1
	}`)
	if err != nil {
		t.Fatal(err)
	}
	// Wait for the task thread to start
	<-task.isstarted
	// Wait for the task thread to complete
	<-task.isstopped
	var count int
	for !task.IsDone() {
		task.WaitForNextEvent()
		count++
	}
	if count < 5 {
		t.Fatal("too few events")
	}
}
