package controlplane

import (
	"errors"
	"strings"
	"testing"

	"github.com/go-sql-driver/mysql"
	"github.com/pingcap-inc/nexuscrm-connectors/testing/connectorfixture"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

// This exercises the activation transaction's ownership boundary. It does not
// assert that the public install entrypoint accepts an incomplete managed schema.
func TestProjectionRangeCutoverNonConnectorActivationTiDB(t *testing.T) {
	for _, category := range []packs.Category{
		packs.AppPack, packs.PlatformCore, packs.PlatformTenant, packs.PlatformPolicy,
		packs.PlatformConfig, packs.PlatformUX, packs.DevSample,
	} {
		t.Run(string(category), func(t *testing.T) {
			db := connectorfixture.OpenTiDB(t)
			connectorfixture.CreateSystemTables(t, db, "_System_InstalledPack", "_System_InstalledPackDependency")
			pack := minimalPack{name: "ordinary-pack", category: category}
			projectionCutoverExec(t, db, `INSERT INTO _System_InstalledPack (Id,Name,PackInstallId,Category,Status) VALUES ('pack-row',?,'ordinary-install',?,'installing')`, pack.Name(), string(category))
			if err := markInstalledPackActive(t.Context(), db, pack, "payload-hash"); err != nil {
				t.Fatal(err)
			}
			var status string
			if err := db.QueryRow(`SELECT Status FROM _System_InstalledPack WHERE Id='pack-row'`).Scan(&status); err != nil {
				t.Fatal(err)
			}
			if status != installedPackStatusActive {
				t.Fatalf("activation status=%s, want active", status)
			}
		})
	}
}

func TestProjectionRangeCutoverConnectorActivationTiDB(t *testing.T) {
	for _, scenario := range []string{"empty", "existing_checkpoint", "missing_protocol_schema", "legacy_history"} {
		t.Run(scenario, func(t *testing.T) {
			db := connectorfixture.OpenTiDB(t)
			connectorfixture.CreateSystemTables(t, db,
				"_System_InstalledPack", "_System_InstalledPackDependency",
				"_System_ConnectorCalloutProfile", "_System_ConnectorInboundRoute",
				"_System_ConnectorEvent", "_System_ConnectorWorkItem",
			)
			if scenario != "missing_protocol_schema" {
				connectorfixture.CreateSystemTables(t, db, "_System_ConnectorProjectionProtocol")
			}
			pack := minimalPack{name: rangeCutoverNamespace, category: packs.ConnectorPack}
			installID := rangeProofIdentity().InstalledPackID
			projectionCutoverExec(t, db, `INSERT INTO _System_InstalledPack (Id,Name,PackInstallId,Category,Status) VALUES ('pack-row',?,?,'connector-pack','installing')`, pack.Name(), installID)
			wantCutoff := int64(0)
			switch scenario {
			case "existing_checkpoint":
				wantCutoff = 9
				projectionCutoverExec(t, db, `INSERT INTO _System_ConnectorProjectionProtocol (InstalledPackId,ProtocolVersion,LegacyMaxOrdinal) VALUES (?,1,?)`, installID, wantCutoff)
			case "legacy_history":
				insertProjectionCutoverEvent(t, db, rangeProofEvent("legacy-input", 1))
			}
			eventsBefore := projectionCutoverRows(t, db, `SELECT * FROM _System_ConnectorEvent ORDER BY Id`)
			worksBefore := projectionCutoverRows(t, db, `SELECT * FROM _System_ConnectorWorkItem ORDER BY WorkId`)
			err := markInstalledPackActive(t.Context(), db, pack, "payload-hash")
			wantStatus := installedPackStatusActive
			switch scenario {
			case "missing_protocol_schema":
				var dbErr *mysql.MySQLError
				if !errors.As(err, &dbErr) || dbErr.Number != 1146 || !strings.Contains(dbErr.Message, "_System_ConnectorProjectionProtocol") {
					t.Fatalf("activation must reject missing protocol schema, got %v", err)
				}
				wantStatus = installedPackStatusInstalling
			case "legacy_history":
				if err == nil || !strings.Contains(err.Error(), "explicit fenced") {
					t.Fatalf("activation must require explicit cutover for legacy history, got %v", err)
				}
				wantStatus = installedPackStatusInstalling
			default:
				if err != nil {
					t.Fatal(err)
				}
			}
			var status string
			if err := db.QueryRow(`SELECT Status FROM _System_InstalledPack WHERE Id='pack-row'`).Scan(&status); err != nil {
				t.Fatal(err)
			}
			if status != wantStatus {
				t.Fatalf("activation status=%s, want %s", status, wantStatus)
			}
			if scenario != "missing_protocol_schema" {
				cutoff, found, err := readProjectionProtocol(t.Context(), db, installID)
				if err != nil || found != (wantStatus == installedPackStatusActive) || cutoff != wantCutoff {
					t.Fatalf("protocol cutoff=%d found=%v err=%v; want cutoff=%d found=%v", cutoff, found, err, wantCutoff, wantStatus == installedPackStatusActive)
				}
			}
			requireProjectionHistoryUnchanged(t, db, eventsBefore, worksBefore)
		})
	}
}
