package tests

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

var tests Tests

// Load all shared tests
func init() {
	tests = make(map[string]Test)

	tests["database_commands"] = Test{
		queries: []*Query{
			&Query{
				name:    "create database should succeed",
				command: `CREATE DATABASE db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "create database with retention duration should succeed",
				command: `CREATE DATABASE db0_r WITH DURATION 24h REPLICATION 2 NAME db0_r_policy`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "create database with retention policy should fail with invalid name",
				command: `CREATE DATABASE db1 WITH NAME "."`,
				exp:     `{"results":[{"statement_id":0,"error":"invalid name"}]}`,
				once:    true,
			},
			&Query{
				name:    "create database should error with some unquoted names",
				command: `CREATE DATABASE 0xdb0`,
				exp:     `{"error":"error parsing query: found 0xdb0, expected identifier at line 1, char 17"}`,
			},
			&Query{
				name:    "create database should error with invalid characters",
				command: `CREATE DATABASE "."`,
				exp:     `{"results":[{"statement_id":0,"error":"invalid name"}]}`,
			},
			&Query{
				name:    "create database with retention duration should error with bad retention duration",
				command: `CREATE DATABASE db0 WITH DURATION xyz`,
				exp:     `{"error":"error parsing query: found xyz, expected duration at line 1, char 35"}`,
			},
			&Query{
				name:    "create database with retention replication should error with bad retention replication number",
				command: `CREATE DATABASE db0 WITH REPLICATION xyz`,
				exp:     `{"error":"error parsing query: found xyz, expected integer at line 1, char 38"}`,
			},
			&Query{
				name:    "create database with retention name should error with missing retention name",
				command: `CREATE DATABASE db0 WITH NAME`,
				exp:     `{"error":"error parsing query: found EOF, expected identifier at line 1, char 31"}`,
			},
			&Query{
				name:    "show database should succeed",
				command: `SHOW DATABASES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["db0"],["db0_r"]]}]}]}`,
			},
			&Query{
				name:    "create database should not error with existing database",
				command: `CREATE DATABASE db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "create database should create non-existing database",
				command: `CREATE DATABASE db1`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "create database with retention duration should error if retention policy is different",
				command: `CREATE DATABASE db1 WITH DURATION 24h`,
				exp:     `{"results":[{"statement_id":0,"error":"retention policy conflicts with an existing policy"}]}`,
			},
			&Query{
				name:    "create database should error with bad retention duration",
				command: `CREATE DATABASE db1 WITH DURATION xyz`,
				exp:     `{"error":"error parsing query: found xyz, expected duration at line 1, char 35"}`,
			},
			&Query{
				name:    "show database should succeed",
				command: `SHOW DATABASES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["db0"],["db0_r"],["db1"]]}]}]}`,
			},
			&Query{
				name:    "drop database db0 should succeed",
				command: `DROP DATABASE db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "drop database db0_r should succeed",
				command: `DROP DATABASE db0_r`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "drop database db1 should succeed",
				command: `DROP DATABASE db1`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "drop database should not error if it does not exists",
				command: `DROP DATABASE db1`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "drop database should not error with non-existing database db1",
				command: `DROP DATABASE db1`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "show database should have no results",
				command: `SHOW DATABASES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"]}]}]}`,
			},
			&Query{
				name:    "create database with shard group duration should succeed",
				command: `CREATE DATABASE db0 WITH SHARD DURATION 61m`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "create database with shard group duration and duration should succeed",
				command: `CREATE DATABASE db1 WITH DURATION 60m SHARD DURATION 30m`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
		},
	}

	tests["drop_and_recreate_database"] = Test{
		db: "db0",
		rp: "rp0",
		writes: Writes{
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
		},
		queries: []*Query{
			&Query{
				name:    "Drop database after data write",
				command: `DROP DATABASE db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "Recreate database",
				command: `CREATE DATABASE db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "Recreate retention policy",
				command: `CREATE RETENTION POLICY rp0 ON db0 DURATION 365d REPLICATION 1 DEFAULT`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "Show measurements after recreate",
				command: `SHOW MEASUREMENTS`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Query data after recreate",
				command: `SELECT * FROM cpu`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
		},
	}

	tests["drop_database_isolated"] = Test{
		db: "db0",
		rp: "rp0",
		writes: Writes{
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
		},
		queries: []*Query{
			&Query{
				name:    "Query data from 1st database",
				command: `SELECT * FROM cpu`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["time","host","region","val"],"values":[["2000-01-01T00:00:00Z","serverA","uswest",23.2]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Query data from 1st database with GROUP BY *",
				command: `SELECT * FROM cpu GROUP BY *`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","tags":{"host":"serverA","region":"uswest"},"columns":["time","val"],"values":[["2000-01-01T00:00:00Z",23.2]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Drop other database",
				command: `DROP DATABASE db1`,
				once:    true,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "Query data from 1st database and ensure it's still there",
				command: `SELECT * FROM cpu`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["time","host","region","val"],"values":[["2000-01-01T00:00:00Z","serverA","uswest",23.2]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Query data from 1st database and ensure it's still there with GROUP BY *",
				command: `SELECT * FROM cpu GROUP BY *`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","tags":{"host":"serverA","region":"uswest"},"columns":["time","val"],"values":[["2000-01-01T00:00:00Z",23.2]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
		},
	}

	tests["delete_series"] = Test{
		db: "db0",
		rp: "rp0",
		writes: Writes{
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=100 %d`, mustParseTime(time.RFC3339Nano, "2000-01-02T00:00:00Z").UnixNano())},
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=200 %d`, mustParseTime(time.RFC3339Nano, "2000-01-03T00:00:00Z").UnixNano())},
			&Write{db: "db1", data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
		},
		queries: []*Query{
			&Query{
				name:    "Show series is present",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["cpu,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Delete series",
				command: `DELETE FROM cpu WHERE time < '2000-01-03T00:00:00Z'`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
				once:    true,
			},
			&Query{
				name:    "Show series still exists",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["cpu,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Make sure last point still exists",
				command: `SELECT * FROM cpu`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["time","host","region","val"],"values":[["2000-01-03T00:00:00Z","serverA","uswest",200]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Make sure data wasn't deleted from other database.",
				command: `SELECT * FROM cpu`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["time","host","region","val"],"values":[["2000-01-01T00:00:00Z","serverA","uswest",23.2]]}]}]}`,
				params:  url.Values{"db": []string{"db1"}},
			},
		},
	}

	tests["drop_and_recreate_series"] = Test{
		db: "db0",
		rp: "rp0",
		writes: Writes{
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
			&Write{db: "db1", data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
		},
		queries: []*Query{
			&Query{
				name:    "Show series is present",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["cpu,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Drop series after data write",
				command: `DROP SERIES FROM cpu`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
				once:    true,
			},
			&Query{
				name:    "Show series is gone",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Make sure data wasn't deleted from other database.",
				command: `SELECT * FROM cpu`,
				exp:     `{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["time","host","region","val"],"values":[["2000-01-01T00:00:00Z","serverA","uswest",23.2]]}]}]}`,
				params:  url.Values{"db": []string{"db1"}},
			},
		},
	}
	tests["drop_and_recreate_series_retest"] = Test{
		db: "db0",
		rp: "rp0",
		writes: Writes{
			&Write{data: fmt.Sprintf(`cpu,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano())},
		},
		queries: []*Query{
			&Query{
				name:    "Show series is present again after re-write",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["cpu,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
		},
	}

	tests["drop_series_from_regex"] = Test{
		db: "db0",
		rp: "rp0",
		writes: Writes{
			&Write{data: strings.Join([]string{
				fmt.Sprintf(`a,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano()),
				fmt.Sprintf(`aa,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano()),
				fmt.Sprintf(`b,host=serverA,region=uswest val=23.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano()),
				fmt.Sprintf(`c,host=serverA,region=uswest val=30.2 %d`, mustParseTime(time.RFC3339Nano, "2000-01-01T00:00:00Z").UnixNano()),
			}, "\n")},
		},
		queries: []*Query{
			&Query{
				name:    "Show series is present",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["a,host=serverA,region=uswest"],["aa,host=serverA,region=uswest"],["b,host=serverA,region=uswest"],["c,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Drop series after data write",
				command: `DROP SERIES FROM /a.*/`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
				once:    true,
			},
			&Query{
				name:    "Show series is gone",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["b,host=serverA,region=uswest"],["c,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Drop series from regex that matches no measurements",
				command: `DROP SERIES FROM /a.*/`,
				exp:     `{"results":[{"statement_id":0}]}`,
				params:  url.Values{"db": []string{"db0"}},
				once:    true,
			},
			&Query{
				name:    "make sure DROP SERIES doesn't delete anything when regex doesn't match",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["b,host=serverA,region=uswest"],["c,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Drop series with WHERE field should error",
				command: `DROP SERIES FROM c WHERE val > 50.0`,
				exp:     `{"results":[{"statement_id":0,"error":"shard 1: fields not supported in WHERE clause during deletion"}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "make sure DROP SERIES with field in WHERE didn't delete data",
				command: `SHOW SERIES`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["key"],"values":[["b,host=serverA,region=uswest"],["c,host=serverA,region=uswest"]]}]}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
			&Query{
				name:    "Drop series with WHERE time should error",
				command: `DROP SERIES FROM c WHERE time > now() - 1d`,
				exp:     `{"results":[{"statement_id":0,"error":"DROP SERIES doesn't support time in WHERE clause"}]}`,
				params:  url.Values{"db": []string{"db0"}},
			},
		},
	}

	tests["retention_policy_commands"] = Test{
		db: "db0",
		queries: []*Query{
			&Query{
				name:    "create retention policy with invalid name should return an error",
				command: `CREATE RETENTION POLICY "." ON db0 DURATION 1d REPLICATION 1`,
				exp:     `{"results":[{"statement_id":0,"error":"invalid name"}]}`,
				once:    true,
			},
			&Query{
				name:    "create retention policy should succeed",
				command: `CREATE RETENTION POLICY rp0 ON db0 DURATION 1h REPLICATION 1`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policy should succeed",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rp0","1h0m0s","1h0m0s",1,false]]}]}]}`,
			},
			&Query{
				name:    "alter retention policy should succeed",
				command: `ALTER RETENTION POLICY rp0 ON db0 DURATION 2h REPLICATION 3 DEFAULT`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policy should have new altered information",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rp0","2h0m0s","1h0m0s",3,true]]}]}]}`,
			},
			&Query{
				name:    "show retention policy should still show policy",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rp0","2h0m0s","1h0m0s",3,true]]}]}]}`,
			},
			&Query{
				name:    "create a second non-default retention policy",
				command: `CREATE RETENTION POLICY rp2 ON db0 DURATION 1h REPLICATION 1`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policy should show both",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rp0","2h0m0s","1h0m0s",3,true],["rp2","1h0m0s","1h0m0s",1,false]]}]}]}`,
			},
			&Query{
				name:    "dropping non-default retention policy succeed",
				command: `DROP RETENTION POLICY rp2 ON db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "create a third non-default retention policy",
				command: `CREATE RETENTION POLICY rp3 ON db0 DURATION 1h REPLICATION 1 SHARD DURATION 30m`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "create retention policy with default on",
				command: `CREATE RETENTION POLICY rp3 ON db0 DURATION 1h REPLICATION 1 SHARD DURATION 30m DEFAULT`,
				exp:     `{"results":[{"statement_id":0,"error":"retention policy conflicts with an existing policy"}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policy should show both with custom shard",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rp0","2h0m0s","1h0m0s",3,true],["rp3","1h0m0s","1h0m0s",1,false]]}]}]}`,
			},
			&Query{
				name:    "dropping non-default custom shard retention policy succeed",
				command: `DROP RETENTION POLICY rp3 ON db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policy should show just default",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rp0","2h0m0s","1h0m0s",3,true]]}]}]}`,
			},
			&Query{
				name:    "Ensure retention policy with unacceptable retention cannot be created",
				command: `CREATE RETENTION POLICY rp4 ON db0 DURATION 1s REPLICATION 1`,
				exp:     `{"results":[{"statement_id":0,"error":"retention policy duration must be at least 1h0m0s"}]}`,
				once:    true,
			},
			&Query{
				name:    "Check error when deleting retention policy on non-existent database",
				command: `DROP RETENTION POLICY rp1 ON mydatabase`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			&Query{
				name:    "Ensure retention policy for non existing db is not created",
				command: `CREATE RETENTION POLICY rp0 ON nodb DURATION 1h REPLICATION 1`,
				exp:     `{"results":[{"statement_id":0,"error":"database not found: nodb"}]}`,
				once:    true,
			},
			&Query{
				name:    "drop rp0",
				command: `DROP RETENTION POLICY rp0 ON db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
			},
			// INF Shard Group Duration will normalize to the Retention Policy Duration Default
			&Query{
				name:    "create retention policy with inf shard group duration",
				command: `CREATE RETENTION POLICY rpinf ON db0 DURATION INF REPLICATION 1 SHARD DURATION 0s`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			// 0s Shard Group Duration will normalize to the Replication Policy Duration
			&Query{
				name:    "create retention policy with 0s shard group duration",
				command: `CREATE RETENTION POLICY rpzero ON db0 DURATION 1h REPLICATION 1 SHARD DURATION 0s`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			// 1s Shard Group Duration will normalize to the MinDefaultRetentionPolicyDuration
			&Query{
				name:    "create retention policy with 1s shard group duration",
				command: `CREATE RETENTION POLICY rponesecond ON db0 DURATION 2h REPLICATION 1 SHARD DURATION 1s`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policy: validate normalized shard group durations are working",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["rpinf","0s","168h0m0s",1,false],["rpzero","1h0m0s","1h0m0s",1,false],["rponesecond","2h0m0s","1h0m0s",1,false]]}]}]}`,
			},
		},
	}

	tests["retention_policy_auto_create"] = Test{
		queries: []*Query{
			&Query{
				name:    "create database should succeed",
				command: `CREATE DATABASE db0`,
				exp:     `{"results":[{"statement_id":0}]}`,
				once:    true,
			},
			&Query{
				name:    "show retention policies should return auto-created policy",
				command: `SHOW RETENTION POLICIES ON db0`,
				exp:     `{"results":[{"statement_id":0,"series":[{"columns":["name","duration","shardGroupDuration","replicaN","default"],"values":[["autogen","0s","168h0m0s",1,true]]}]}]}`,
			},
		},
	}

}

func (tests Tests) load(t *testing.T, key string) Test {
	test, ok := tests[key]
	if !ok {
		t.Fatalf("no test %q", key)
	}

	return test.duplicate()
}
