// Copyright (C) 2020 Giuseppe Lavagetto <joe@wikimedia.org>
// Copyright (C) 2020 Wikimedia Foundation, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"net/url"
	"testing"
	"time"
)

// Ensure we can interpret a correctly-formatted resource_change event
func TestNewResourceChangeFromJSON(t *testing.T) {
	eventData := []byte(`{
		"$schema": "/resource_change/1.0.0",
		"meta": {
			"id": "aaaaaaaa-bbbb-bbbb-bbbb-123456789012",
			"dt": "2020-04-30T11:37:53.351Z",
			"stream": "purge",
			"uri": "https://it.wikipedia.org/wiki/Francesco_Totti"
		},
		"root_event": {
			"dt": "2020-04-24T09:00:00Z",
			"signature": ""
		}
	}`)
	event, err := NewResourceChangeFromJSON(&eventData)
	if err != nil {
		t.Errorf("Error loading a good event from json: %v", err)
	}
	url := *event.GetURL()
	if url != "https://it.wikipedia.org/wiki/Francesco_Totti" {
		t.Errorf("The url found in the event is %s (https://it.wikipedia.org/wiki/Francesco_Totti) expected", url)
	}
	rootTime := event.GetTS()
	ts := rootTime.Format("2006-01-02")
	if ts != "2020-04-24" {
		t.Errorf("The timestamp found does not correspond: expected 2020-04-24, got %s", ts)
	}
}

// Ensure we can interpret a stripped-down resource_change event
func TestNewResourceChangeFromJSONSparse(t *testing.T) {
	eventData := []byte(`{
		"$schema": "/resource_change/1.0.0",
		"meta": {
			"dt": "2020-04-30T11:37:53Z",
			"stream": "purge",
			"uri": "https://it.wikipedia.org/wiki/Francesco_Totti"
		}
	}`)
	event, err := NewResourceChangeFromJSON(&eventData)
	if err != nil {
		t.Errorf("Error loading event: %v", err)
	}
	url := *event.GetURL()
	if url != "https://it.wikipedia.org/wiki/Francesco_Totti" {
		t.Errorf("The url found in the event is %s (https://it.wikipedia.org/wiki/Francesco_Totti) expected", url)
	}
	rootTime := event.GetTS()
	ts := rootTime.Format("2006-01-02")
	if ts != "2020-04-30" {
		t.Errorf("The timestamp found does not correspond: expected 2020-04-30, got %s", ts)
	}
}

func TestNewResourceChangeFromJSONNoUrl(t *testing.T) {
	eventData := []byte(`{
		"$schema": "/resource_change/1.0.0",
		"meta": {
			"dt": "2020-04-30T11:37:53Z",
			"stream": "purge"
		}
	}`)
	_, err := NewResourceChangeFromJSON(&eventData)
	if err == nil {
		t.Errorf("No error returned when loading an event without a URL")
	}
}

// We want to be resilient to bad formatting of datetimes. When we encounter a badly-formatted date
// we should log it, and use the current timestamp as a fallback instead.
func TestNewResourceChangeFromJSONBadDt(t *testing.T) {
	eventData := []byte(`{
		"$schema": "/resource_change/1.0.0",
		"meta": {
			"dt": "2020-04-30 11:37:53",
			"stream": "purge",
			"uri": "https://it.wikipedia.org/wiki/Francesco_Totti"
		}
	}`)
	ev, err := NewResourceChangeFromJSON(&eventData)
	if err != nil {
		t.Errorf("Parsing a bad date causes an error: %v", err)
	}
	if time.Since(ev.GetTS()) > time.Duration(5)*time.Hour {
		t.Errorf("Parsing a bad date didn't yield the current time.")
	}
}

// We want to test that sorting happens according to the same rules we use for varnish
func TestNewResourceChangeUrlSort(t *testing.T) {
	testData := map[string]string{
		`/foo/bar?t=0&b=0&p=0&c=5`: `/foo/bar?b=0&c=5&p=0&t=0`,
		`/foo/bar?coa=0&co=0`:      `/foo/bar?co=0&coa=0`,
		`/foo/bar?a=0&&&&&`:        `/foo/bar?a=0`,
		`/foo/bar?&a=0&&&&&z&w&x&`: `/foo/bar?a=0&w&x&z`,
		`/foo/bar?&`:               `/foo/bar?`,
		`/foo/bar?t=0`:             `/foo/bar?t=0`,
		`/foo/bar`:                 `/foo/bar`,
		`/w/api.php?action=parse&page=Varnish&prop=wikitext&prop=parsetree&format=json`:      `/w/api.php?action=parse&format=json&page=Varnish&prop=wikitext&prop=parsetree`,
		`/search-redirect.php?family=Wikipedia&language=en&search=Varnish&language=de&go=Go`: `/search-redirect.php?family=Wikipedia&go=Go&language=en&language=de&search=Varnish`,
		`/foo?foo[]=c&foo[]=b&foo[0]=a`: `/foo?foo[]=c&foo[]=b&foo[0]=a`,
		`/w/index.php?title=Varnish&action=edit&preloadparams[]=c&preloadparams[0]=b&preloadparams[]=a`:     `/w/index.php?action=edit&preloadparams[]=c&preloadparams[0]=b&preloadparams[]=a&title=Varnish`,
		`/w/index.php?title=Varnish&action=edit&preloadparams%5B%5D=c&preloadparams[0]=b&preloadparams[]=a`: `/w/index.php?action=edit&preloadparams%5B%5D=c&preloadparams[0]=b&preloadparams[]=a&title=Varnish`,
		`/blah?foo[]=a&foo%5B%5D=b&fooa=q&foo=c&foo%5b%5d=d`:                                                `/blah?foo[]=a&foo%5B%5D=b&foo=c&foo%5b%5d=d&fooa=q`,
	}
	for input, expected := range testData {
		inputUri := fmt.Sprintf("http://es.wikipedia.org%s", input)
		expectedUri := fmt.Sprintf("http://es.wikipedia.org%s", expected)
		eventData := []byte(fmt.Sprintf(`{
			"$schema": "/resource_change/1.0.0",
			"meta": {
				"dt": "2020-04-30T11:37:53Z",
				"stream": "purge",
				"uri": "%s"
			}
		}`, inputUri))
		ev, err := NewResourceChangeFromJSON(&eventData)
		if err != nil {
			t.Errorf("Parsing url %s generates an error: %v", inputUri, err)
		}
		if *ev.Event.URI != expectedUri {
			t.Errorf("Error: expected \n'%s', got \n'%s'", expectedUri, *ev.Event.URI)
		}
	}
}

// Simple benchmark of json decoding
func BenchmarkDecodeResourceChange(b *testing.B) {
	eventData := []byte(`{
		"$schema": "/resource_change/1.0.0",
		"meta": {
			"dt": "2020-04-30T11:37:53Z",
			"stream": "purge",
			"uri": "https://it.wikipedia.org/wiki/Francesco_Totti"
		},
		"root_event": {"signature": "abcd", "dt": "2020-04-29T11:37:53+02:00"}
	}`)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		rc, _ := NewResourceChangeFromJSON(&eventData)
		rc.GetTS()
		rc.GetURL()
	}
}

// Benchmark querysorting
func BenchmarkQuerySort(b *testing.B) {
	uri := "https://en.wikipedia.org/w/api.php?action=parse&page=Varnish&prop=wikitext&prop=parsetree&format=json"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		QuerySort(uri)
	}
}

// Reference url parsing using the standard library.
func BenchmarkQuerySortLibraries(b *testing.B) {
	uri := "https://en.wikipedia.org/w/api.php?action=parse&page=Varnish&prop=wikitext&prop=parsetree&format=json"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		url.Parse(uri)
	}
}
