package controlplane

import (
	"database/sql"
	"net"
	"os"
	"strings"
	"testing"

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

// The CLI accepts the driver's default DSN, which does not parse DATETIME into
// time.Time. Fence policy needs only nullness and expiry relative to DB time.
func TestProjectionRangeCutoverFenceDSNTiDB(t *testing.T) {
	db := projectionCutoverDatabase(t)
	var dbName string
	if err := db.QueryRow(`SELECT DATABASE()`).Scan(&dbName); err != nil {
		t.Fatal(err)
	}
	cfg := mysql.NewConfig()
	cfg.User = os.Getenv("TIDB_USER")
	if cfg.User == "" {
		cfg.User = "root"
	}
	cfg.Passwd = os.Getenv("TIDB_PASSWORD")
	host, port := os.Getenv("TIDB_HOST"), os.Getenv("TIDB_PORT")
	if host == "" {
		host = "127.0.0.1"
	}
	if port == "" {
		port = "4000"
	}
	cfg.Net, cfg.Addr, cfg.DBName = "tcp", net.JoinHostPort(host, port), dbName
	for _, parseTime := range []bool{false, true} {
		name := "default_dsn"
		if parseTime {
			name = "parse_time"
		}
		for _, scenario := range []string{"permanent_only", "expired_override", "live_active_override", "temporary_fence_only"} {
			t.Run(name+"/"+scenario, func(t *testing.T) {
				resetProjectionCutoverFixture(t, db)
				e := rangeProofEvent("completed-head", 7)
				insertProjectionCutoverEvent(t, db, e)
				insertProjectionCutoverWork(t, db, rangeProofWork("completed-head", e, "processed"))
				packID := e.identity.InstalledPackID
				wantError := ""
				switch scenario {
				case "expired_override":
					projectionCutoverExec(t, db, `INSERT INTO _System_ConnectorIngressControl (Id,Namespace,PackInstallId,Channel,Mode,ExpiresAt) VALUES ('extra',?,?,'webhook','active',DATE_SUB(UTC_TIMESTAMP(),INTERVAL 1 HOUR))`, rangeCutoverNamespace, packID)
				case "live_active_override":
					projectionCutoverExec(t, db, `INSERT INTO _System_ConnectorIngressControl (Id,Namespace,PackInstallId,Channel,Mode,ExpiresAt) VALUES ('extra',?,?,'webhook','active',DATE_ADD(UTC_TIMESTAMP(),INTERVAL 1 HOUR))`, rangeCutoverNamespace, packID)
					wantError = "active ingress override"
				case "temporary_fence_only":
					projectionCutoverExec(t, db, `UPDATE _System_ConnectorIngressControl SET ExpiresAt=DATE_ADD(UTC_TIMESTAMP(),INTERVAL 1 HOUR)`)
					wantError = "requires a non-expiring namespace ingress fence"
				}
				eventsBefore := projectionCutoverRows(t, db, `SELECT * FROM _System_ConnectorEvent ORDER BY Id`)
				worksBefore := projectionCutoverRows(t, db, `SELECT * FROM _System_ConnectorWorkItem ORDER BY WorkId`)
				cfg.ParseTime = parseTime
				cutoverDB, err := sql.Open("mysql", cfg.FormatDSN())
				if err != nil {
					t.Fatal(err)
				}
				t.Cleanup(func() { _ = cutoverDB.Close() })
				result, err := EnsureConnectorProjectionRangeProtocol(t.Context(), cutoverDB, packID)
				if wantError != "" {
					requireNoProjectionCutoverWrites(t, db, eventsBefore, worksBefore)
					if err == nil || !strings.Contains(err.Error(), wantError) {
						t.Fatalf("want %q, got %v", wantError, err)
					}
					return
				}
				if err != nil {
					t.Fatalf("valid fenced cutover failed: %v", err)
				}
				if result.LegacyMaxOrdinal != 7 || result.AlreadyApplied {
					t.Fatalf("unexpected result: %+v", result)
				}
				if cutoff, found, err := readProjectionProtocol(t.Context(), db, packID); err != nil || !found || cutoff != 7 {
					t.Fatalf("checkpoint=%d/%v/%v", cutoff, found, err)
				}
				requireProjectionHistoryUnchanged(t, db, eventsBefore, worksBefore)
			})
		}
	}
}
