package datastore

import (
	"context"
	"database/sql"
	"fmt"
	"testing"
	"time"

	mysql "github.com/go-sql-driver/mysql"
)

func newAutoSemanticStore(t *testing.T) *Store {
	t.Helper()
	cfg, err := mysql.ParseDSN(testDSN)
	if err != nil {
		t.Fatal(err)
	}
	databaseName := fmt.Sprintf("drive9_auto_semantic_%d", time.Now().UnixNano())
	adminCfg := *cfg
	adminCfg.DBName = ""
	adminDB, err := sql.Open("mysql", adminCfg.FormatDSN())
	if err != nil {
		t.Fatal(err)
	}
	if _, err := adminDB.Exec("CREATE DATABASE `" + databaseName + "`"); err != nil {
		_ = adminDB.Close()
		t.Skipf("test TiDB user cannot create isolated auto-schema database: %v", err)
	}
	t.Cleanup(func() {
		_, _ = adminDB.Exec("DROP DATABASE `" + databaseName + "`")
		_ = adminDB.Close()
	})

	tenantCfg := *cfg
	tenantCfg.DBName = databaseName
	store, err := Open(tenantCfg.FormatDSN())
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = store.Close() })
	for _, statement := range []string{
		`CREATE TABLE inodes (inode_id VARCHAR(64) PRIMARY KEY)`,
		`CREATE TABLE semantic (
			inode_id VARCHAR(64) PRIMARY KEY,
			content_text LONGTEXT,
			description LONGTEXT,
			embedding BLOB,
			embedding_revision BIGINT,
			description_embedding BLOB,
			description_embedding_revision BIGINT
		)`,
	} {
		if _, err := store.DB().Exec(statement); err != nil {
			t.Fatal(err)
		}
	}
	return store
}

func TestUpdateSemanticTxSupportsAutoSchemaWithoutGenerationColumns(t *testing.T) {
	store := newAutoSemanticStore(t)
	if _, err := store.DB().Exec(`INSERT INTO semantic
		(inode_id, content_text, description, embedding, embedding_revision, description_embedding, description_embedding_revision)
		VALUES ('inode-update', 'old content', 'old description', X'01', 7, X'02', 7)`); err != nil {
		t.Fatal(err)
	}
	if err := store.InTx(context.Background(), func(tx *sql.Tx) error {
		return store.UpdateSemanticTx(tx, "inode-update", "new content", "new description")
	}); err != nil {
		t.Fatalf("update semantic against auto schema: %v", err)
	}
	var contentText, description string
	var embeddingNull, embeddingRevisionNull, descriptionEmbeddingNull, descriptionEmbeddingRevisionNull bool
	if err := store.DB().QueryRow(`SELECT content_text, description,
		embedding IS NULL, embedding_revision IS NULL,
		description_embedding IS NULL, description_embedding_revision IS NULL
		FROM semantic WHERE inode_id = 'inode-update'`).Scan(
		&contentText, &description, &embeddingNull, &embeddingRevisionNull,
		&descriptionEmbeddingNull, &descriptionEmbeddingRevisionNull,
	); err != nil {
		t.Fatal(err)
	}
	if contentText != "new content" || description != "new description" ||
		!embeddingNull || !embeddingRevisionNull || !descriptionEmbeddingNull || !descriptionEmbeddingRevisionNull {
		t.Fatalf("semantic state not updated and invalidated: content=%q description=%q nulls=%v/%v/%v/%v",
			contentText, description, embeddingNull, embeddingRevisionNull, descriptionEmbeddingNull, descriptionEmbeddingRevisionNull)
	}
}

func TestClearFileEmbeddingStateTxSupportsAutoSchemaWithoutGenerationColumns(t *testing.T) {
	store := newAutoSemanticStore(t)
	if _, err := store.DB().Exec(`INSERT INTO inodes (inode_id) VALUES ('inode-clear')`); err != nil {
		t.Fatal(err)
	}
	if _, err := store.DB().Exec(`INSERT INTO semantic
		(inode_id, embedding, embedding_revision) VALUES ('inode-clear', X'01', 7)`); err != nil {
		t.Fatal(err)
	}
	if err := store.InTx(context.Background(), func(tx *sql.Tx) error {
		return store.ClearFileEmbeddingStateTx(tx, "inode-clear")
	}); err != nil {
		t.Fatalf("clear embedding against auto schema: %v", err)
	}
	var embeddingNull, embeddingRevisionNull bool
	if err := store.DB().QueryRow(`SELECT embedding IS NULL, embedding_revision IS NULL
		FROM semantic WHERE inode_id = 'inode-clear'`).Scan(&embeddingNull, &embeddingRevisionNull); err != nil {
		t.Fatal(err)
	}
	if !embeddingNull || !embeddingRevisionNull {
		t.Fatalf("embedding state not cleared: embedding_null=%v revision_null=%v", embeddingNull, embeddingRevisionNull)
	}
}
