package libsq_test

import (
	"testing"

	_ "github.com/mattn/go-sqlite3"

	"github.com/neilotoole/sq/libsq"
	"github.com/neilotoole/sq/libsq/source/drivertype"
	"github.com/neilotoole/sq/testh"
)

const infoSchema = "information_schema"

// TestQuery_func tests miscellaneous functions that aren't
// tested elsewhere.
//
//nolint:exhaustive
func TestQuery_func(t *testing.T) {
	testCases := []queryTestCase{
		{
			name:         "max",
			in:           `@sakila | .actor | max(.actor_id)`,
			wantSQL:      `SELECT max("actor_id") AS "max(.actor_id)" FROM "actor"`,
			override:     driverMap{drivertype.MySQL: "SELECT max(`actor_id`) AS `max(.actor_id)` FROM `actor`"},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "max(.actor_id)"),
				assertSinkColValue(0, int64(200)),
			},
		},
		{
			name:         "min",
			in:           `@sakila | .actor | min(.actor_id)`,
			wantSQL:      `SELECT min("actor_id") AS "min(.actor_id)" FROM "actor"`,
			override:     driverMap{drivertype.MySQL: "SELECT min(`actor_id`) AS `min(.actor_id)` FROM `actor`"},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "min(.actor_id)"),
				assertSinkColValue(0, int64(1)),
			},
		},
		{
			name:         "avg",
			in:           `@sakila | .actor | avg(.actor_id)`,
			wantSQL:      `SELECT avg("actor_id") AS "avg(.actor_id)" FROM "actor"`,
			override:     driverMap{drivertype.MySQL: "SELECT avg(`actor_id`) AS `avg(.actor_id)` FROM `actor`"},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "avg(.actor_id)"),

				// FIXME: The driver impls handle avg() differently. Some return
				// float64, some int, some decimal (string). The SLQ impl of avg()
				// needs to be modified to returned a consistent type.
				// assertSinkColValue(0, float64(100.5)),
				//
				// See also:
				// - https://github.com/golang/go/issues/30870
				// - https://github.com/golang-sql/decomposer

			},
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			execQueryTestCase(t, tc)
		})
	}
}

func TestQuery_func_schema(t *testing.T) {
	testCases := []queryTestCase{
		{
			name:         "sqlserver-default",
			in:           `@sakila | schema()`,
			wantSQL:      `SELECT SCHEMA_NAME() AS "schema()"`,
			onlyFor:      []drivertype.Type{drivertype.MSSQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, "dbo"),
			},
		},
		{
			name: "sqlserver-alt-no-change",
			// SQL Server doesn't support changing the schema on a per-connection
			// basis. So we expect the default schema to be returned.
			in: `@sakila | schema()`,
			beforeRun: func(tc queryTestCase, th *testh.Helper, qc *libsq.QueryContext) {
				qc.Collection.Active().Schema = infoSchema
			},
			wantSQL:      `SELECT SCHEMA_NAME() AS "schema()"`,
			onlyFor:      []drivertype.Type{drivertype.MSSQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, "dbo"),
			},
		},
		{
			name:         "postgres-default",
			in:           `@sakila | schema()`,
			wantSQL:      `SELECT current_schema() AS "schema()"`,
			onlyFor:      []drivertype.Type{drivertype.Pg},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, "public"),
			},
		},
		{
			name: "postgres-alt",
			in:   `@sakila | schema()`,
			beforeRun: func(tc queryTestCase, th *testh.Helper, qc *libsq.QueryContext) {
				qc.Collection.Active().Schema = infoSchema
			},
			wantSQL:      `SELECT current_schema() AS "schema()"`,
			onlyFor:      []drivertype.Type{drivertype.Pg},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, infoSchema),
			},
		},
		{
			name:         "mysql-default",
			in:           `@sakila | schema()`,
			wantSQL:      "SELECT DATABASE() AS `schema()`",
			onlyFor:      []drivertype.Type{drivertype.MySQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, "sakila"),
			},
		},
		{
			name: "mysql-alt",
			in:   `@sakila | schema()`,
			beforeRun: func(tc queryTestCase, th *testh.Helper, qc *libsq.QueryContext) {
				qc.Collection.Active().Schema = infoSchema
			},
			wantSQL:      "SELECT DATABASE() AS `schema()`",
			onlyFor:      []drivertype.Type{drivertype.MySQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, infoSchema),
			},
		},
		{
			name:         "sqlite-default",
			in:           `@sakila | schema()`,
			wantSQL:      `SELECT (SELECT name FROM pragma_database_list ORDER BY seq limit 1) AS "schema()"`,
			onlyFor:      []drivertype.Type{drivertype.SQLite},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "schema()"),
				assertSinkColValue(0, "main"),
			},
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			execQueryTestCase(t, tc)
		})
	}
}

func TestQuery_func_catalog(t *testing.T) {
	testCases := []queryTestCase{
		{
			name:         "sqlserver-default",
			in:           `@sakila | catalog()`,
			wantSQL:      `SELECT DB_NAME() AS "catalog()"`,
			onlyFor:      []drivertype.Type{drivertype.MSSQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "catalog()"),
				assertSinkColValue(0, "sakila"),
			},
		},
		{
			name: "sqlserver-alt",
			in:   `@sakila | catalog()`,
			beforeRun: func(tc queryTestCase, th *testh.Helper, qc *libsq.QueryContext) {
				qc.Collection.Active().Catalog = "model"
			},
			wantSQL:      `SELECT DB_NAME() AS "catalog()"`,
			onlyFor:      []drivertype.Type{drivertype.MSSQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "catalog()"),
				assertSinkColValue(0, "model"),
			},
		},
		{
			name:         "postgres-default",
			in:           `@sakila | catalog()`,
			wantSQL:      `SELECT current_database() AS "catalog()"`,
			onlyFor:      []drivertype.Type{drivertype.Pg},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "catalog()"),
				assertSinkColValue(0, "sakila"),
			},
		},
		{
			name: "postgres-alt",
			in:   `@sakila | catalog()`,
			beforeRun: func(tc queryTestCase, th *testh.Helper, qc *libsq.QueryContext) {
				qc.Collection.Active().Catalog = "postgres"
			},
			wantSQL:      `SELECT current_database() AS "catalog()"`,
			onlyFor:      []drivertype.Type{drivertype.Pg},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "catalog()"),
				assertSinkColValue(0, "postgres"),
			},
		},
		{
			name:         "mysql",
			in:           `@sakila | catalog()`,
			wantSQL:      "SELECT (SELECT CATALOG_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = DATABASE() LIMIT 1) AS `catalog()`", //nolint:lll
			onlyFor:      []drivertype.Type{drivertype.MySQL},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "catalog()"),
				assertSinkColValue(0, "def"),
			},
		},
		{
			name: "sqlite",
			in:   `@sakila | catalog()`,
			// SQLite doesn't support catalogs, so we (somewhat arbitrarily)
			// return the string "default". This behavior may change
			// upon feedback.
			wantSQL:      `SELECT (SELECT 'default') AS "catalog()"`,
			onlyFor:      []drivertype.Type{drivertype.SQLite},
			wantRecCount: 1,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "catalog()"),
				assertSinkColValue(0, "default"),
			},
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			execQueryTestCase(t, tc)
		})
	}
}

//nolint:lll,exhaustive
func TestQuery_func_rownum(t *testing.T) {
	testCases := []queryTestCase{
		{
			name:    "plain",
			in:      `@sakila | .actor | rownum()`,
			wantSQL: `SELECT (row_number() OVER (ORDER BY 1)) AS "rownum()" FROM "actor"`,
			override: driverMap{
				drivertype.MSSQL: `SELECT (row_number() OVER (ORDER BY (SELECT NULL))) AS "rownum()" FROM "actor"`,
				// We don't test the MySQL override because it uses a randomly generated variable value. E.g.
				//  SELECT (@row_number_dw5ch2ss:=@row_number_dw5ch2ss + 1) AS `rownum()` FROM `actor`
				drivertype.MySQL: ``,
			},
			wantRecCount: 200,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "rownum()"),
				assertSinkCellValue(0, 0, int64(1)),
				assertSinkCellValue(199, 0, int64(200)),
			},
		},
		{
			name:    "plus_1",
			in:      `@sakila | .actor | rownum() + 1`,
			wantSQL: `SELECT (row_number() OVER (ORDER BY 1))+1 AS "rownum()+1" FROM "actor"`,
			override: driverMap{
				drivertype.MSSQL: `SELECT (row_number() OVER (ORDER BY (SELECT NULL)))+1 AS "rownum()+1" FROM "actor"`,
				drivertype.MySQL: "",
			},
			wantRecCount: 200,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "rownum()+1"),
				assertSinkCellValue(0, 0, int64(2)),
				assertSinkCellValue(199, 0, int64(201)),
			},
		},
		{
			name:    "minus_1_alias",
			in:      `@sakila | .actor | (rownum()-1):zero_index`,
			wantSQL: `SELECT ((row_number() OVER (ORDER BY 1))-1) AS "zero_index" FROM "actor"`,
			override: driverMap{
				drivertype.MSSQL: `SELECT ((row_number() OVER (ORDER BY (SELECT NULL)))-1) AS "zero_index" FROM "actor"`,
				drivertype.MySQL: "",
			},
			wantRecCount: 200,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "zero_index"),
				assertSinkCellValue(0, 0, int64(0)),
				assertSinkCellValue(199, 0, int64(199)),
			},
		},
		{
			name:         "column_orderby",
			in:           `@sakila | .actor | rownum(), .actor_id | order_by(.actor_id)`,
			wantSQL:      `SELECT (row_number() OVER (ORDER BY "actor_id")) AS "rownum()", "actor_id" FROM "actor" ORDER BY "actor_id"`,
			override:     driverMap{drivertype.MySQL: ""},
			wantRecCount: 200,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "rownum()"),
				assertSinkCellValue(0, 0, int64(1)),
				assertSinkCellValue(199, 0, int64(200)),
			},
		},
		{
			name:         "double_invocation",
			in:           `@sakila | .actor | rownum():index1, .actor_id, rownum():index2 | order_by(.actor_id)`,
			wantSQL:      `SELECT (row_number() OVER (ORDER BY "actor_id")) AS "index1", "actor_id", (row_number() OVER (ORDER BY "actor_id")) AS "index2" FROM "actor" ORDER BY "actor_id"`,
			override:     driverMap{drivertype.MySQL: ""},
			wantRecCount: 200,
			sinkFns: []SinkTestFunc{
				assertSinkColName(0, "index1"),
				assertSinkColName(2, "index2"),
				assertSinkCellValue(0, 0, int64(1)),
				assertSinkCellValue(0, 2, int64(1)),
				assertSinkCellValue(199, 0, int64(200)),
				assertSinkCellValue(199, 2, int64(200)),
			},
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			execQueryTestCase(t, tc)
		})
	}
}