package db

import (
	"regexp"
	"strings"
	"testing"
)

// A TEXT column that an index is built on must be listed in varcharColumns.
//
// MySQL cannot index a TEXT column without a prefix length: the migration dies
// with error 1170 — and it dies again on every startup afterwards, leaving the
// operator with a logbook that will not connect and no way forward from the
// interface. The translator only emits VARCHAR for the names in varcharColumns,
// and that list is maintained by hand.
//
// So: read the migrations, work out which columns are TEXT, find which are
// indexed, and insist the list covers the overlap. Integer columns are indexed
// perfectly well and are none of this test's business.
func TestIndexedTextColumnsAreVarchar(t *testing.T) {
	files, err := migrationsFS.ReadDir("migrations")
	if err != nil {
		t.Fatalf("read migrations: %v", err)
	}

	// name → declared type, from "ADD COLUMN name TYPE" and from the column
	// lines inside a CREATE TABLE body.
	declared := map[string]string{}
	reAdd := regexp.MustCompile(`(?i)ADD\s+COLUMN\s+` + "`" + `?(\w+)` + "`" + `?\s+(\w+)`)
	reCol := regexp.MustCompile(`(?im)^\s*` + "`" + `?(\w+)` + "`" + `?\s+(TEXT|VARCHAR|INTEGER|INT|REAL|BLOB|DATETIME|BOOLEAN)\b`)
	reIdx := regexp.MustCompile(`(?i)CREATE\s+INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?\S+\s+ON\s+\S+\s*\(([^)]*)\)`)

	var indexed []struct{ file, col string }
	for _, f := range files {
		if f.IsDir() || !strings.HasSuffix(f.Name(), ".sql") {
			continue
		}
		body, err := migrationsFS.ReadFile("migrations/" + f.Name())
		if err != nil {
			t.Fatalf("read %s: %v", f.Name(), err)
		}
		src := string(body)
		for _, m := range reAdd.FindAllStringSubmatch(src, -1) {
			declared[strings.ToLower(m[1])] = strings.ToUpper(m[2])
		}
		for _, m := range reCol.FindAllStringSubmatch(src, -1) {
			if _, seen := declared[strings.ToLower(m[1])]; !seen {
				declared[strings.ToLower(m[1])] = strings.ToUpper(m[2])
			}
		}
		for _, m := range reIdx.FindAllStringSubmatch(src, -1) {
			for _, col := range strings.Split(m[1], ",") {
				col = strings.TrimSpace(col)
				if col == "" || strings.Contains(col, "(") {
					continue // a prefix length or an expression is MySQL-safe already
				}
				col = strings.Trim(col, "`\"")
				if i := strings.IndexAny(col, " \t"); i > 0 {
					col = col[:i] // "col DESC"
				}
				indexed = append(indexed, struct{ file, col string }{f.Name(), strings.ToLower(col)})
			}
		}
	}

	if len(indexed) == 0 || len(declared) == 0 {
		t.Fatal("nothing parsed out of the migrations — this test has stopped checking anything")
	}

	textIndexed := 0
	for _, ix := range indexed {
		typ, known := declared[ix.col]
		if !known || typ != "TEXT" {
			continue // an integer index, or a column this test could not type
		}
		textIndexed++
		if !varcharColumns[ix.col] {
			t.Errorf("%s indexes the TEXT column %q, which is not in varcharColumns — "+
				"on MySQL that migration fails with error 1170 and the logbook stops connecting for good",
				ix.file, ix.col)
		}
	}
	if textIndexed == 0 {
		t.Fatal("no indexed TEXT column found — the parsing has drifted and this test checks nothing")
	}
	t.Logf("%d indexed TEXT column(s) checked against varcharColumns", textIndexed)
}
