package controlplane

import (
	"context"
	"database/sql"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

func expectNavigationVisibilityStampNotPresent(mock sqlmock.Sqlmock) {
	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(navigationVisibilitySSoTMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"_"}))
}

func expectNavigationVisibilityStampPresent(mock sqlmock.Sqlmock) {
	mock.ExpectQuery(regexp.QuoteMeta("SELECT 1 FROM _System_Migration WHERE Id = ?")).
		WithArgs(navigationVisibilitySSoTMigrationID).
		WillReturnRows(sqlmock.NewRows([]string{"_"}).AddRow(1))
}

func expectNavigationVisibilityStampInsert(mock sqlmock.Sqlmock) {
	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO _System_Migration")).
		WithArgs(navigationVisibilitySSoTMigrationID, 1, sqlmock.AnyArg()).
		WillReturnResult(sqlmock.NewResult(1, 1))
}

func TestBackfillNavigationVisibilitySSoTProjectsLegacyAssignedProfilesAndTabs(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	expectNavigationVisibilityStampNotPresent(mock)
	mock.ExpectQuery(regexp.QuoteMeta("SELECT `Id` FROM `Profile`")).
		WillReturnRows(sqlmock.NewRows([]string{"Id"}).AddRow("p-admin").AddRow("p-sales"))
	mock.ExpectQuery(regexp.QuoteMeta("SELECT `Id`, `AssignedProfiles`, `IsDefault`, `NavigationItems` FROM `_System_App`")).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "AssignedProfiles", "IsDefault", "NavigationItems"}).
			AddRow("app-all", sql.NullString{}, true, sql.NullString{String: `[{"id":"nav-account","type":"object","objectApiName":"account","label":"Accounts"}]`, Valid: true}).
			AddRow("app-sales", sql.NullString{String: `["p-sales"]`, Valid: true}, true, sql.NullString{String: `[{"id":"nav-home","type":"page","label":"Home"}]`, Valid: true}))
	mock.ExpectQuery(regexp.QuoteMeta("SELECT `ApiName` FROM `_System_Object`")).
		WillReturnRows(sqlmock.NewRows([]string{"ApiName"}).AddRow("Account"))

	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileAppVisibility`")).
		WithArgs(sqlmock.AnyArg(), "p-admin", "app-all", true, true).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileAppVisibility`")).
		WithArgs(sqlmock.AnyArg(), "p-sales", "app-all", true, true).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectQuery("(?s)SELECT p.`Id` FROM `Profile` p.*ProfileAppVisibility.*pav.`AppId` = \\?.*pav.`IsVisible` = 1").
		WithArgs("app-all").
		WillReturnRows(sqlmock.NewRows([]string{"Id"}).AddRow("p-admin").AddRow("p-sales"))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileTabSetting`")).
		WithArgs(sqlmock.AnyArg(), "p-admin", "Account", "DefaultOn").
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileTabSetting`")).
		WithArgs(sqlmock.AnyArg(), "p-sales", "Account", "DefaultOn").
		WillReturnResult(sqlmock.NewResult(0, 1))

	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileAppVisibility`")).
		WithArgs(sqlmock.AnyArg(), "p-admin", "app-sales", false, false).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileAppVisibility`")).
		WithArgs(sqlmock.AnyArg(), "p-sales", "app-sales", true, true).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectQuery("(?s)SELECT p.`Id` FROM `Profile` p.*ProfileAppVisibility.*pav.`AppId` = \\?.*pav.`IsVisible` = 1").
		WithArgs("app-sales").
		WillReturnRows(sqlmock.NewRows([]string{"Id"}).AddRow("p-sales"))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileTabSetting`")).
		WithArgs(sqlmock.AnyArg(), "p-sales", "Home", "DefaultOn").
		WillReturnResult(sqlmock.NewResult(0, 1))
	expectNavigationVisibilityStampInsert(mock)

	appRows, tabRows, err := BackfillNavigationVisibilitySSoT(context.Background(), db)
	if err != nil {
		t.Fatalf("BackfillNavigationVisibilitySSoT: %v", err)
	}
	if appRows != 4 || tabRows != 3 {
		t.Fatalf("rows = app:%d tab:%d, want app:4 tab:3", appRows, tabRows)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sqlmock expectations: %v", err)
	}
}

func TestBackfillNavigationVisibilitySSoTSeedsTabsFromCanonicalVisibleAppRows(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	expectNavigationVisibilityStampNotPresent(mock)
	mock.ExpectQuery(regexp.QuoteMeta("SELECT `Id` FROM `Profile`")).
		WillReturnRows(sqlmock.NewRows([]string{"Id"}).AddRow("p-admin").AddRow("p-sales"))
	mock.ExpectQuery(regexp.QuoteMeta("SELECT `Id`, `AssignedProfiles`, `IsDefault`, `NavigationItems` FROM `_System_App`")).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "AssignedProfiles", "IsDefault", "NavigationItems"}).
			AddRow("app-sales", sql.NullString{String: `["p-sales"]`, Valid: true}, false, sql.NullString{String: `[{"id":"nav-home","type":"page","label":"Home"}]`, Valid: true}))
	mock.ExpectQuery(regexp.QuoteMeta("SELECT `ApiName` FROM `_System_Object`")).
		WillReturnRows(sqlmock.NewRows([]string{"ApiName"}))

	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileAppVisibility`")).
		WithArgs(sqlmock.AnyArg(), "p-admin", "app-sales", false, false).
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileAppVisibility`")).
		WithArgs(sqlmock.AnyArg(), "p-sales", "app-sales", true, false).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectQuery("(?s)SELECT p.`Id` FROM `Profile` p.*ProfileAppVisibility.*pav.`AppId` = \\?.*pav.`IsVisible` = 1").
		WithArgs("app-sales").
		WillReturnRows(sqlmock.NewRows([]string{"Id"}).AddRow("p-admin"))
	mock.ExpectExec(regexp.QuoteMeta("INSERT IGNORE INTO `ProfileTabSetting`")).
		WithArgs(sqlmock.AnyArg(), "p-admin", "Home", "DefaultOn").
		WillReturnResult(sqlmock.NewResult(0, 1))
	expectNavigationVisibilityStampInsert(mock)

	appRows, tabRows, err := BackfillNavigationVisibilitySSoT(context.Background(), db)
	if err != nil {
		t.Fatalf("BackfillNavigationVisibilitySSoT: %v", err)
	}
	if appRows != 2 || tabRows != 1 {
		t.Fatalf("rows = app:%d tab:%d, want app:2 tab:1", appRows, tabRows)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sqlmock expectations: %v", err)
	}
}

func TestBackfillNavigationVisibilitySSoTSkipsWhenStamped(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	expectNavigationVisibilityStampPresent(mock)
	appRows, tabRows, err := BackfillNavigationVisibilitySSoT(context.Background(), db)
	if err != nil {
		t.Fatalf("BackfillNavigationVisibilitySSoT: %v", err)
	}
	if appRows != 0 || tabRows != 0 {
		t.Fatalf("rows = app:%d tab:%d, want zero", appRows, tabRows)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sqlmock expectations: %v", err)
	}
}
