package packs

import (
	"context"
	"database/sql/driver"
	"strings"
	"testing"

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

const systemPermissionGrantsKindForTest = ComponentKind("system_permission_grants")

func TestSourceBackedSystemPermissionGrantsPayloadValidation(t *testing.T) {
	tests := []struct {
		name    string
		payload string
		want    string
	}{
		{
			name: "unknown top-level field",
			payload: `{
				"permissionApiName": "CreateCustomizeFilters",
				"grantTo": [{"profileName": "Sales", "permissionSet": "profile_owned"}],
				"unexpected": true
			}`,
			want: `unknown field "unexpected"`,
		},
		{
			name: "unsupported target type",
			payload: `{
				"permissionApiName": "CreateCustomizeFilters",
				"grantTo": [{"profileName": "Sales", "permissionSet": "named"}]
			}`,
			want: `unsupported permissionSet "named"`,
		},
		{
			name: "duplicate assertion",
			payload: `{
				"permissionApiName": "CreateCustomizeFilters",
				"grantTo": [
					{"profileName": "Sales", "permissionSet": "profile_owned"},
					{"profileName": " Sales ", "permissionSet": "PROFILE_OWNED"}
				]
			}`,
			want: `duplicate assertion`,
		},
		{
			name: "empty permission name",
			payload: `{
				"permissionApiName": " ",
				"grantTo": [{"profileName": "Sales", "permissionSet": "profile_owned"}]
			}`,
			want: `permissionApiName is required`,
		},
		{
			name: "empty target profile",
			payload: `{
				"permissionApiName": "CreateCustomizeFilters",
				"grantTo": [{"profileName": " ", "permissionSet": "profile_owned"}]
			}`,
			want: `profileName is required`,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, err := NewSourceBackedPack(testSystemPermissionGrantsSource(tc.payload))
			if err == nil {
				t.Fatalf("NewSourceBackedPack() error = nil, want %q", tc.want)
			}
			if !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("NewSourceBackedPack() error = %v, want substring %q", err, tc.want)
			}
		})
	}
}

func TestSourceBackedSystemPermissionGrantsRegistryCapabilityCategoryAndPhase(t *testing.T) {
	kind := systemPermissionGrantsKindForTest

	if !DefaultSourceBackedComponentKindRegistry().Supports(kind) {
		t.Fatalf("DefaultSourceBackedComponentKindRegistry() does not support %s", kind)
	}
	capability, ok := DefaultSourceBackedComponentCapabilityRegistry().CapabilityFor(kind)
	if !ok {
		t.Fatalf("DefaultSourceBackedComponentCapabilityRegistry() missing %s", kind)
	}
	if !capability.Installable {
		t.Fatalf("%s capability is not installable", kind)
	}
	if !capability.SupportsManageability(ManageabilityDefaultsOnly) || len(capability.Manageabilities) != 1 {
		t.Fatalf("%s manageabilities = %#v, want defaults_only only", kind, capability.Manageabilities)
	}
	if _, ok := appPackInstallableComponentKinds()[kind]; !ok {
		t.Fatalf("%s is not allowed for AppPack manifests", kind)
	}
	if _, ok := connectorPackOnlyKinds()[kind]; ok {
		t.Fatalf("%s must not be connector-pack-only", kind)
	}

	contract, ok := ComponentKindContractFor(kind)
	if !ok {
		t.Fatalf("%s contract missing", kind)
	}
	if got, want := contract.Phase, InstallPlanPhaseSecurity; got != want {
		t.Fatalf("%s phase = %q, want %q", kind, got, want)
	}
	if got, want := contract.OwnerDiscriminator, ComponentOwnerStrategyRuntimeDataExcluded; got != want {
		t.Fatalf("%s owner strategy = %q, want %q", kind, got, want)
	}
	if len(contract.WriteSet) != 1 || contract.WriteSet[0].Table != "_System_PermissionSetPermission" {
		t.Fatalf("%s write set = %#v, want _System_PermissionSetPermission only", kind, contract.WriteSet)
	}
	if contract.sourceBuildHandler == nil {
		t.Fatalf("%s source-backed build handler missing", kind)
	}
}

func TestSourceBackedSystemPermissionGrantsRejectsBusinessKeyPayloadMismatch(t *testing.T) {
	source := testPackSourceWithComponents([]ManifestComponent{
		{
			Kind:          systemPermissionGrantsKindForTest,
			BusinessKey:   "OtherPermission",
			Manageability: ManageabilityDefaultsOnly,
			Payload:       []byte(validSystemPermissionGrantPayload()),
		},
	})

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack() error = nil, want business-key/payload mismatch rejection")
	}
	if !strings.Contains(err.Error(), `businessKey "OtherPermission" must match permissionApiName "CreateCustomizeFilters"`) {
		t.Fatalf("NewSourceBackedPack() error = %v, want business-key mismatch", err)
	}
}

func TestSourceBackedSystemPermissionGrantsInstallPlanOrdersAfterPermissionCatalog(t *testing.T) {
	source := testPackSourceWithComponents([]ManifestComponent{
		{
			Kind:          systemPermissionGrantsKindForTest,
			BusinessKey:   "CreateCustomizeFilters",
			Manageability: ManageabilityDefaultsOnly,
			Payload: []byte(`{
				"permissionApiName": "CreateCustomizeFilters",
				"grantTo": [{"profileName": "Sales", "permissionSet": "profile_owned"}],
				"expectedPermissionOwnerPack": "platform-policy"
			}`),
		},
		{
			Kind:          ComponentKindPermission,
			BusinessKey:   "CreateCustomizeFilters",
			Manageability: ManageabilityDefaultsOnly,
			Payload:       []byte(`[{"apiName":"CreateCustomizeFilters","label":"Create Customize Filters","category":"System","description":"Create custom list filters"}]`),
		},
	})

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}
	plan, err := pack.InstallPlan()
	if err != nil {
		t.Fatalf("InstallPlan: %v", err)
	}
	security := plan.ComponentsForPhase(InstallPlanPhaseSecurity)
	if len(security) < 2 {
		t.Fatalf("security phase components = %d, want permission and system_permission_grants", len(security))
	}
	if security[0].Kind() != ComponentKindPermission || security[1].Kind() != systemPermissionGrantsKindForTest {
		t.Fatalf("security phase order = %s then %s, want permission then system_permission_grants", security[0].Kind(), security[1].Kind())
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "Sales", "ps-sales"),
	)
	expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}),
	)
	mock.ExpectExec("INSERT IGNORE INTO `_System_PermissionSetPermission`").
		WithArgs(sqlmock.AnyArg(), "ps-sales", "CreateCustomizeFilters").
		WillReturnResult(sqlmock.NewResult(1, 1))

	if err := component.Apply(context.Background(), ApplyDeps{DB: db}); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "Sales", "ps-sales"),
	)
	expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}).AddRow("CreateCustomizeFilters", 1),
	)

	if err := component.Apply(context.Background(), ApplyDeps{DB: db}); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "Sales", "ps-sales"),
	)
	expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}).AddRow("CreateCustomizeFilters", 0),
	)

	if err := component.Apply(context.Background(), ApplyDeps{DB: db}); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectSystemPermissionGrantCatalogRows(mock, "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
			AddRow("createCustomizeFilters", "platform-policy", 1, ""),
	)

	err = component.(componentCustomVerifier).VerifyComponent(context.Background(), VerifyDeps{DB: db})
	if err == nil {
		t.Fatal("VerifyComponent() error = nil, want exact ApiName mismatch")
	}
	if !strings.Contains(err.Error(), `ApiName = "createCustomizeFilters"`) {
		t.Fatalf("VerifyComponent() error = %v, want exact ApiName mismatch", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "sales", "ps-sales"),
	)

	err = component.(componentCustomVerifier).VerifyComponent(context.Background(), VerifyDeps{DB: db})
	if err == nil {
		t.Fatal("VerifyComponent() error = nil, want exact Profile.Name mismatch")
	}
	if !strings.Contains(err.Error(), `Profile.Name = "sales"`) {
		t.Fatalf("VerifyComponent() error = %v, want exact Profile.Name mismatch", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "Sales", "ps-sales"),
	)
	expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}).AddRow("createCustomizeFilters", 1),
	)

	err = component.(componentCustomVerifier).VerifyComponent(context.Background(), VerifyDeps{DB: db})
	if err == nil {
		t.Fatal("VerifyComponent() error = nil, want exact junction PermissionApiName mismatch")
	}
	if !strings.Contains(err.Error(), `PermissionApiName = "createCustomizeFilters"`) {
		t.Fatalf("VerifyComponent() error = %v, want exact junction PermissionApiName mismatch", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestSourceBackedSystemPermissionGrantsVerifyChecksCatalogOwnerRequiresProfileAndGrant(t *testing.T) {
	tests := []struct {
		name      string
		catalog   *sqlmock.Rows
		profiles  *sqlmock.Rows
		junction  *sqlmock.Rows
		wantError string
	}{
		{
			name:      "missing permission",
			catalog:   sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}),
			wantError: "permission CreateCustomizeFilters is missing",
		},
		{
			name: "inactive permission",
			catalog: sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
				AddRow("CreateCustomizeFilters", "platform-policy", 0, ""),
			wantError: "IsActive = 0",
		},
		{
			name: "owner mismatch",
			catalog: sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
				AddRow("CreateCustomizeFilters", "other-pack", 1, ""),
			wantError: `OwnerPack = "other-pack"`,
		},
		{
			name: "requires permission fail closed",
			catalog: sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
				AddRow("CreateCustomizeFilters", "platform-policy", 1, "ViewSetup"),
			wantError: `RequiresPermission = "ViewSetup"`,
		},
		{
			name: "missing profile owned permission set",
			catalog: sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
				AddRow("CreateCustomizeFilters", "platform-policy", 1, ""),
			profiles:  sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}),
			wantError: "resolves to 0 active profile-owned PermissionSet",
		},
		{
			name: "ambiguous profile owned permission set",
			catalog: sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
				AddRow("CreateCustomizeFilters", "platform-policy", 1, ""),
			profiles: sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
				AddRow("prof-sales-a", "Sales", "ps-sales-a").
				AddRow("prof-sales-b", "Sales", "ps-sales-b"),
			wantError: "resolves to 2 active profile-owned PermissionSet",
		},
		{
			name: "missing junction row",
			catalog: sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
				AddRow("CreateCustomizeFilters", "platform-policy", 1, ""),
			profiles: sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
				AddRow("prof-sales", "Sales", "ps-sales"),
			junction:  sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}),
			wantError: "grant is missing",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			component := buildTestSystemPermissionGrantsComponent(t, validSystemPermissionGrantPayload())
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock.New: %v", err)
			}
			defer db.Close()

			expectSystemPermissionGrantCatalogRows(mock, "CreateCustomizeFilters", tc.catalog)
			if tc.profiles != nil {
				expectSystemPermissionGrantProfileResolution(mock, "Sales", tc.profiles)
			}
			if tc.junction != nil {
				expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters", tc.junction)
			}

			err = component.(componentCustomVerifier).VerifyComponent(context.Background(), VerifyDeps{DB: db})
			if err == nil {
				t.Fatalf("VerifyComponent() error = nil, want %q", tc.wantError)
			}
			if !strings.Contains(err.Error(), tc.wantError) {
				t.Fatalf("VerifyComponent() error = %v, want substring %q", err, tc.wantError)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "Sales", "ps-sales"),
	)
	expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}).AddRow("CreateCustomizeFilters", 1),
	)

	if err := component.(componentCustomVerifier).VerifyComponent(context.Background(), VerifyDeps{DB: db}); err != nil {
		t.Fatalf("VerifyComponent: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectSystemPermissionGrantCatalog(mock, "CreateCustomizeFilters", "platform-policy", 1, "")
	expectSystemPermissionGrantProfileResolution(mock, "Sales",
		sqlmock.NewRows([]string{"ProfileId", "ProfileName", "PermissionSetId"}).
			AddRow("prof-sales", "Sales", "ps-sales"),
	)
	expectSystemPermissionGrantExistingRow(mock, "ps-sales", "CreateCustomizeFilters",
		sqlmock.NewRows([]string{"PermissionApiName", "IsEnabled"}).AddRow("CreateCustomizeFilters", 0),
	)

	if err := component.(componentCustomVerifier).VerifyComponent(context.Background(), VerifyDeps{DB: db}); err != nil {
		t.Fatalf("VerifyComponent: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func buildTestSystemPermissionGrantsComponent(t *testing.T, payload string) PackComponent {
	t.Helper()
	pack, err := NewSourceBackedPack(testSystemPermissionGrantsSource(payload))
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}
	for _, component := range pack.Components() {
		if component.Kind() == systemPermissionGrantsKindForTest {
			return component
		}
	}
	t.Fatalf("system_permission_grants component not built; components = %#v", pack.Components())
	return nil
}

func validSystemPermissionGrantPayload() string {
	return `{
		"permissionApiName": "CreateCustomizeFilters",
		"grantTo": [{"profileName": "Sales", "permissionSet": "profile_owned"}],
		"expectedPermissionOwnerPack": "platform-policy",
		"reason": "grant list filter customization to Sales"
	}`
}

func testSystemPermissionGrantsSource(payload string) *ResolvedPackSource {
	return testPackSourceWithComponents([]ManifestComponent{
		{
			Kind:          systemPermissionGrantsKindForTest,
			BusinessKey:   "CreateCustomizeFilters",
			Manageability: ManageabilityDefaultsOnly,
			Payload:       []byte(payload),
		},
	})
}

func testPackSourceWithComponents(components []ManifestComponent) *ResolvedPackSource {
	return &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/test-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "test-pack",
			DisplayName:           "Test Pack",
			Category:              AppPack,
			Version:               "1.0.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.0.0", MaxExclusive: "999.0.0"},
			Components:            components,
		},
		Files: map[string][]byte{},
	}
}

func expectSystemPermissionGrantCatalog(mock sqlmock.Sqlmock, apiName, ownerPack string, isActive int, requires string) {
	expectSystemPermissionGrantCatalogRows(mock, apiName,
		sqlmock.NewRows([]string{"ApiName", "OwnerPack", "IsActive", "RequiresPermission"}).
			AddRow(apiName, ownerPack, isActive, requires),
	)
}

func expectSystemPermissionGrantCatalogRows(mock sqlmock.Sqlmock, apiName string, rows *sqlmock.Rows) {
	mock.ExpectQuery(`SELECT ApiName, COALESCE\(OwnerPack, ''\), IsActive, COALESCE\(RequiresPermission, ''\)\s+FROM _System_Permission\s+WHERE ApiName = \?`).
		WithArgs(apiName).
		WillReturnRows(rows)
}

func expectSystemPermissionGrantProfileResolution(mock sqlmock.Sqlmock, profileName string, rows *sqlmock.Rows) {
	mock.ExpectQuery(`SELECT p\.Id, p\.Name, ps\.Id\s+FROM Profile p\s+JOIN PermissionSet ps ON ps\.ProfileId = p\.Id`).
		WithArgs(profileName).
		WillReturnRows(rows)
}

func expectSystemPermissionGrantExistingRow(mock sqlmock.Sqlmock, permissionSetID, apiName string, rows *sqlmock.Rows) {
	mock.ExpectQuery(`SELECT PermissionApiName, IsEnabled\s+FROM _System_PermissionSetPermission\s+WHERE PermissionSetId = \? AND PermissionApiName = \?`).
		WithArgs(driver.Value(permissionSetID), driver.Value(apiName)).
		WillReturnRows(rows)
}
