package main

import (
	"code.justin.tv/dta/necronomicon-user-api/app"
	"testing"
	"time"
	"github.com/yudai/gojsondiff"
	"encoding/json"
)

func setupDbTest(t *testing.T) (db *DB) {
	db = NewDB("sslmode=disable")
	_, err := db.conn.Exec(`
	TRUNCATE deployments, snapshots;
	ALTER SEQUENCE deployments_id_seq RESTART WITH 100;
	ALTER SEQUENCE snapshots_id_seq RESTART WITH 200;
	`)
	if err != nil {
		t.Fatal("Error connecting to test db: %s:", err)
		return
	}
	return
}

func TestNoSnapshotsFoundInEmptyTable(t *testing.T) {
	db := setupDbTest(t)
	snapshots, err := db.GetSnapshots(nil, nil, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 0 {
		t.Errorf("Found snapshots in an empty table")
	}
}

func CreateFireflySnapshots(t *testing.T) (db *DB, err error) {
	db = setupDbTest(t)
	_, err = db.CreateSnapshot(&SnapshotModel{User: "rivertam", Message: "miranda", Timestamp: time.Date(1990, 10, 10, 0, 0, 0, 0, time.UTC)})
	if err == nil {
		_, err = db.CreateSnapshot(&SnapshotModel{User: "book", Message: "grace", Timestamp: time.Date(1995, 1, 1, 0, 0, 0, 0, time.UTC)})
	}
	if err == nil {
		_, err = db.CreateSnapshot(&SnapshotModel{User: "mal", Message: "gorram", Timestamp: time.Date(2000, 2, 2, 0, 0, 0, 0, time.UTC)})
	}
	if err == nil {
		_, err = db.CreateSnapshot(&SnapshotModel{User: "jayne", Message: "damage", Timestamp: time.Date(2005, 3, 3, 0, 0, 0, 0, time.UTC)})
	}
	return
}

func TestSnapshotsFilterNoFromNoToEnoughLimit(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	snapshots, err := db.GetSnapshots(nil, nil, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 4 {
		t.Errorf("Had to find 4 snapshots, found %d instead", len(snapshots))
	}

}

func TestSnapshotsFilterNoFromNoToInsuficientLimit(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	snapshots, err := db.GetSnapshots(nil, nil, 3)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 3 {
		t.Errorf("Had to find 3 snapshots, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterNoFromToPast(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateTo := time.Date(1985, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(nil, &dateTo, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 0 {
		t.Errorf("Had to find 0 snapshots, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterNoFromToEnoughToFindOneSnapshot(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateTo := time.Date(1991, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(nil, &dateTo, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 1 {
		t.Errorf("Had to find 1 snapshot, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterLimitedSearchWithoutSnapshots(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateFrom := time.Date(1992, 1, 1, 0, 0, 0, 0, time.UTC)
	dateTo := time.Date(1993, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(&dateFrom, &dateTo, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 0 {
		t.Errorf("Had to find 0 snapshots, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterLimitedToFindOneSnapshot(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateFrom := time.Date(1992, 1, 1, 0, 0, 0, 0, time.UTC)
	dateTo := time.Date(1996, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(&dateFrom, &dateTo, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 1 {
		t.Errorf("Had to find 1 snapshot, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterLimitedToFindTwoSnapshots(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateFrom := time.Date(1985, 1, 1, 0, 0, 0, 0, time.UTC)
	dateTo := time.Date(1996, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(&dateFrom, &dateTo, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 2 {
		t.Errorf("Had to find 2 snapshots, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterFromPastWithManyResults(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateFrom := time.Date(1985, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(&dateFrom, nil, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 4 {
		t.Errorf("Had to find 4 snapshots, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterFromDateWithOneResult(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateFrom := time.Date(2004, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(&dateFrom, nil, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 1 {
		t.Errorf("Had to find 1 snapshot, found %d instead", len(snapshots))
	}
}

func TestSnapshotsFilterFromDateWithoutResults(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create test snapshots", err)
	}
	dateFrom := time.Date(2014, 1, 1, 0, 0, 0, 0, time.UTC)
	snapshots, err := db.GetSnapshots(&dateFrom, nil, 10)
	if err != nil {
		t.Errorf("Error getting snapshots", err)
	}
	if len(snapshots) != 0 {
		t.Errorf("Had to find 0 snapshots, found %d instead", len(snapshots))
	}
}

func CreateLotrSnapshots(t *testing.T) (db *DB, snapshotId int, err error) {
	var changes app.TwitchDtaNecronomiconUserapiSnapshotChangeCollection
	db = setupDbTest(t)
	if err != nil {
		t.Fatal("Error connecting to test db: %s:", err)
	}

	values1 := map[string]map[string]interface{}{"myapp": {"x": 1, "y": 2}}
	change1 := app.TwitchDtaNecronomiconUserapiSnapshotChange{
		Name:      "prod",
		Operation: "modified",
		Parent:    nil,
		Values:    values1}

	values2 := map[string]map[string]interface{}{"myapp": {"x": 15, "z": 16}}
	change2 := app.TwitchDtaNecronomiconUserapiSnapshotChange{
		Name:      "staging",
		Operation: "modified",
		Parent:    &change1.Name,
		Values:    values2}
	changes = app.TwitchDtaNecronomiconUserapiSnapshotChangeCollection{&change1, &change2}

	snapshotId, err = db.CreateSnapshot(&SnapshotModel{ID: 1, User: "frodo", Message: "sam", Timestamp: time.Now(), Payload: changes})
	if err == nil {
		_, err = db.CreateSnapshot(&SnapshotModel{ID: 2, User: "sauron", Message: "smeagol", Timestamp: time.Now()})
	}
	return
}

func TestGetSnapshot(t *testing.T) {
	db, snapshotId, err := CreateLotrSnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create snapshot: %s", err)
	}
	snapshot, err := db.GetSnapshot(snapshotId)
	if err != nil {
		t.Errorf("Couldn't get snapshot %d: %s", snapshotId, err)
	}
	prodValues := snapshot.State["prod"].Values["myapp"]
	if prodValues["x"].(float64) != 1 || prodValues["y"].(float64) != 2 || len(prodValues) != 2 {
		t.Errorf("Invalid prod values after first change set: %#v", prodValues["x"])
	}
	stagingValues := snapshot.State["staging"].Values["myapp"]
	if stagingValues["x"].(float64) != 15 || stagingValues["z"].(float64) != 16 || len(stagingValues) != 2 {
		t.Errorf("Invalid staging values after first change set: %#v", stagingValues)
	}
}

func TestGetMergedEnvironment(t *testing.T) {
	db, snapshotId, err := CreateLotrSnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create snapshot: %s", err)
	}
	snapshot, err := db.GetSnapshot(snapshotId)
	if err != nil {
		t.Errorf("Couldn't get snapshot: %s", err)
	}
	mergedValues, err := GetMergedEnvironment(snapshot, "staging")
	if err != nil {
		t.Errorf("Couldn't get merged environment: %s", err)
	}
	myAppVals := mergedValues.Values["myapp"]
	if len(myAppVals) != 3 || myAppVals["y"].(float64) != 2 || myAppVals["x"].(float64) != 15 || myAppVals["z"].(float64) != 16 {
		t.Errorf("Invalid merged values after first change set: %#v", mergedValues.Values["myapp"])
	}
}

func TestGetSnapshotMultipleWithoutResults(t *testing.T) {
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create snapshots: %s", err)
	}
	snapshots, err := db.GetSnapshotMultiple([]int{0, 99999999})
	if err != nil {
		t.Errorf("Couldn't get multiple snapshots: %s", err)
	}
	if len(snapshots) != 0 {
		t.Errorf("Expected 0 snapshots, found %d: %#v", len(snapshots), snapshots)
	}
}

func TestGetSnapshotMultipleWithResults(t *testing.T) {
	var ids []int
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create snapshots: %s", err)
	}
	snapshotsArr, err := db.GetSnapshots(nil, nil, 10)
	if err != nil {
		t.Errorf("Couldn't get snapshots: %s", err)
	}
	for _, snapshot := range snapshotsArr {
		ids = append(ids, snapshot.ID)
	}
	snapshots, err := db.GetSnapshotMultiple(ids)
	if err != nil {
		t.Errorf("Couldn't get multiple snapshots: %s", err)
	}
	if len(snapshots) != len(ids) {
		t.Errorf("Expected %d snapshots, found %d: %#v", len(ids), len(snapshots), snapshots)
	}
}

func TestGetSnapshotMultipleWithSomeMissingResults(t *testing.T) {
	var ids []int
	db, err := CreateFireflySnapshots(t)
	if err != nil {
		t.Errorf("Couldn't create snapshots: %s", err)
	}
	snapshotsArr, err := db.GetSnapshots(nil, nil, 10)
	if err != nil {
		t.Errorf("Couldn't get snapshots: %s", err)
	}
	for _, snapshot := range snapshotsArr {
		ids = append(ids, snapshot.ID)
	}
	expectedSnapshots := len(ids)
	ids = append(ids, 0)
	ids = append(ids, 99999999)
	snapshots, err := db.GetSnapshotMultiple(ids)
	if err != nil {
		t.Errorf("Couldn't get multiple snapshots: %s", err)
	}
	if len(snapshots) != expectedSnapshots {
		t.Errorf("Expected %d snapshots, found %d: %#v", expectedSnapshots, len(snapshots), snapshots)
	}
}

func stateFromJsonString(jsonString string) (state map[string]app.TwitchDtaNecronomiconUserapiSnapshotState, err error) {
	state = map[string]app.TwitchDtaNecronomiconUserapiSnapshotState{}
	err = json.Unmarshal([]byte(jsonString), &state)
	return
}

func compareToExpectedJsonValuesState(t *testing.T, state app.TwitchDtaNecronomiconUserapiSnapshotStatemap, jsonState string) {
	gotState := app.TwitchDtaNecronomiconUserapiSnapshotStatemap{}
	expectedState := app.TwitchDtaNecronomiconUserapiSnapshotStatemap{}
	err := json.Unmarshal([]byte(jsonState), &expectedState)
	if err != nil {
		t.Fatalf("Couldn't unmarshal test json into state: %+v", jsonState)
	}
	b, err := json.Marshal(state.Map)
	if err != nil {
		t.Fatalf("Couldn't marshal test json %+v", state.Map)
	}
	s := string(b)
	err = json.Unmarshal(b, &gotState)
	if err != nil {
		t.Fatalf("Couldn't unmarshal test json into state: %+v", string(b))
	}
	differ := gojsondiff.New()
	diff, err := differ.Compare([]byte(jsonState), b)
	if err != nil {
		t.Fatalf("Couldn't diff json strings '%s' and '%s'", jsonState, s)
	}
	if diff.Modified() {
		t.Errorf("Expected state '%+v' but got '%+v' instead.", jsonState, s)
	}
}

func TestGetChangedEnvironmentsInStatesFromEmptyState(t *testing.T) {
	newStateStr := `{"prod":{"values":{"app1": {"x": "s", "y": "w"}}},"staging":{"parent":"prod","values":{"app2": {"x": "z"}, "app1": {"y": "r", "z": "n"}}}}`
	currentState, err := stateFromJsonString(newStateStr)
	if err != nil {
		t.Fatalf("Couldn't unmarshal initial test value")
	}
	state, err := GetChangedEnvironmentsInStates(nil, currentState)
	if err != nil {
		t.Errorf("Error getting environment changes: %s", err)
	}
	compareToExpectedJsonValuesState(t, state, newStateStr)
}

func TestGetChangedEnvironmentsInStatesFromNonEmptyState(t *testing.T) {
	oldStateStr := `{"prod":{"values":{"app1": {"x": "s", "y": "w"}}},"staging":{"parent":"prod","values":{"app2": {"x": "z"}, "app1": {"y": "r", "z": "n"}}}}`
	newStateStr := `{"prod":{"values":{"app1": {"x": "s"}}},"staging":{"parent":"prod","values":{"app2": {"x": "z"}, "app1": {"y": "r", "z": "n"}}}}`
	oldState, err := stateFromJsonString(oldStateStr)
	if err != nil {
		t.Fatalf("Couldn't unmarshal initial test value")
	}
	currentState, err := stateFromJsonString(newStateStr)
	if err != nil {
		t.Fatalf("Couldn't unmarshal initial test value")
	}
	state, err := GetChangedEnvironmentsInStates(oldState, currentState)
	if err != nil {
		t.Errorf("Error getting environment changes: %s", err)
	}
	compareToExpectedJsonValuesState(t, state, `{"prod":{"values":{"app1":{"x":"s"}}}}`)
}

func TestGetChangedEnvironmentsInStatesFromNonEmptyStateChangingParent(t *testing.T) {
	oldStateStr := `{"prod":{"values":{"app1": {"x": "s", "y": "w"}}},"staging":{"parent":"prod","values":{"app2": {"x": "z"}, "app1": {"y": "r", "z": "n"}}}}`
	newStateStr := `{"prod":{"values":{"app1": {"x": "s", "y": "w"}}},"staging":{"values":{"app2": {"x": "z"}, "app1": {"y": "r", "z": "n"}}}}`
	oldState, err := stateFromJsonString(oldStateStr)
	if err != nil {
		t.Fatalf("Couldn't unmarshal initial test value")
	}
	currentState, err := stateFromJsonString(newStateStr)
	if err != nil {
		t.Fatalf("Couldn't unmarshal initial test value")
	}
	state, err := GetChangedEnvironmentsInStates(oldState, currentState)
	if err != nil {
		t.Errorf("Error getting environment changes: %s", err)
	}
	compareToExpectedJsonValuesState(t, state, `{"staging":{"values":{"app1":{"y":"r","z":"n"},"app2":{"x":"z"}}}}`)
}


func TestGetLatestDeploymentWithoutDeployments(t *testing.T) {
	db := setupDbTest(t)
	_, err := db.GetLatestDeployment()
	if err == nil {
		t.Errorf("Found a deployment when none was expected")
	}
}

func TestGetDeploymentsWithoutDeployments(t *testing.T) {
	db := setupDbTest(t)
	deployments, err := db.GetDeployments(nil, nil, 10)
	if err != nil {
		t.Errorf("Couldn't get deployments: %s", err)
	}
	if len(deployments) != 0 {
		t.Errorf("Expected no deployments, but got %d", len(deployments))
	}
}

func TestCreateAndGetDeployment(t *testing.T) {
	db, snapshotId, err := CreateLotrSnapshots(t)
	d := DeploymentModel{SnapshotID: snapshotId}
	deploymentId, err := db.CreateDeployment(&d)
	if err != nil {
		t.Errorf("Couldn't create deployment: %s", err)
	}
	gotDeployment, err := db.GetDeployment(deploymentId)
	if err != nil {
		t.Errorf("Couldn't get deployment: %s", err)
	}
	if gotDeployment.SnapshotID != snapshotId {
		t.Errorf("Got an invalid deployment after creation: %#v", gotDeployment)
	}
}

func TestCreateAndGetDeployments(t *testing.T) {
	db, snapshotId, err := CreateLotrSnapshots(t)
	d := DeploymentModel{SnapshotID: snapshotId}
	_, err = db.CreateDeployment(&d)
	if err != nil {
		t.Errorf("Couldn't create deployment: %s", err)
	}
	deployments, err := db.GetDeployments(nil, nil, 10)
	if err != nil {
		t.Errorf("Couldn't get deployments: %s", err)
	}
	if len(deployments) != 1 {
		t.Errorf("Expected 1 deployment, but got %d: %#v", len(deployments), deployments)
	}
}
