package bootstrap

import (
	"database/sql/driver"
	"encoding/json"
	"sort"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/backend/internal/controlplane/packspec"
	"github.com/nexuscrm/shared/pkg/constants"
)

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

	policies := packspec.StandardObjectPolicies()
	apiNames := make([]string, 0, len(policies))
	for apiName := range policies {
		apiNames = append(apiNames, apiName)
	}
	sort.Strings(apiNames)

	rows := sqlmock.NewRows([]string{
		constants.FieldAPIName,
		constants.FieldSysObject_SharingModel,
		constants.FieldSysObject_EnableHierarchySharing,
		constants.FieldSysObject_IsCustomized,
		constants.FieldSysObject_AccessPolicy,
	})
	for _, apiName := range apiNames {
		policy := policies[apiName]

		var accessPolicy driver.Value
		if !strings.EqualFold(apiName, "casecomment") && !strings.EqualFold(apiName, "opportunitycontactrole") {
			if policy.AccessPolicy != nil {
				raw, err := json.Marshal(policy.AccessPolicy)
				if err != nil {
					t.Fatalf("marshal AccessPolicy for %s: %v", apiName, err)
				}
				accessPolicy = string(raw)
			}
		}

		rows.AddRow(apiName, policy.SharingModel, policy.EnableHierarchySharing, false, accessPolicy)
	}

	mock.ExpectQuery("SELECT `ApiName`, `SharingModel`, COALESCE\\(`EnableHierarchySharing`, 0\\), COALESCE\\(`IsCustomized`, 0\\), `AccessPolicy` FROM `_System_Object` WHERE `TableType` = \\? AND COALESCE\\(`IsDeleted`, 0\\) = 0").
		WithArgs(string(constants.TableTypeStandardObject)).
		WillReturnRows(rows)

	result := &AssertionResult{Violations: []AssertionViolation{}}
	assertManagedAccessPolicyConsistency(db, result)

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}

	var derivedDrifts []string
	for _, violation := range result.Violations {
		if violation.Category != "ManagedAccessPolicyDrift" || !strings.Contains(violation.Description, "AccessPolicy drift") {
			continue
		}
		derivedDrifts = append(derivedDrifts, strings.ToLower(violation.Object))
	}
	sort.Strings(derivedDrifts)

	want := []string{"casecomment", "opportunitycontactrole"}
	if len(derivedDrifts) != len(want) {
		t.Fatalf("expected %d derived-object AccessPolicy drift violations, got %d: %#v", len(want), len(derivedDrifts), derivedDrifts)
	}
	for i, apiName := range want {
		if derivedDrifts[i] != apiName {
			t.Fatalf("expected derived AccessPolicy drift for %s, got %#v", apiName, derivedDrifts)
		}
	}
}

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

	policies := packspec.StandardObjectPolicies()
	policy, ok := policies["casecomment"]
	if !ok || policy.AccessPolicy == nil {
		t.Fatal("expected managed CaseComment access policy to exist")
	}

	rows := sqlmock.NewRows([]string{
		constants.FieldAPIName,
		constants.FieldSysObject_SharingModel,
		constants.FieldSysObject_EnableHierarchySharing,
		constants.FieldSysObject_IsCustomized,
		constants.FieldSysObject_AccessPolicy,
	})
	apiNames := make([]string, 0, len(policies))
	for apiName := range policies {
		apiNames = append(apiNames, apiName)
	}
	sort.Strings(apiNames)
	for _, apiName := range apiNames {
		current := policies[apiName]
		var accessPolicy driver.Value
		if current.AccessPolicy != nil {
			raw, err := json.Marshal(current.AccessPolicy)
			if err != nil {
				t.Fatalf("marshal AccessPolicy for %s: %v", apiName, err)
			}
			accessPolicy = string(raw)
		}
		if apiName == "casecomment" {
			accessPolicy = `{"fieldEdit":{"inherit":"objectEdit"},"fieldRead":{"inherit":"objectRead"},"mode":"derived","objectCreate":{"kind":"objectPermission","object":"Case","operation":"Edit"},"objectDelete":{"allOf":[{"kind":"objectPermission","object":"Case","operation":"Edit"},{"anyOf":[{"kind":"objectPermission","object":"Case","operation":"ModifyAllRecords"},{"allOf":[{"apiName":"EditCaseComments","kind":"systemPermission"},{"field":"CreatedById","kind":"recordFieldEqualsCurrentUser"}]}]}]},"objectEdit":{"allOf":[{"kind":"objectPermission","object":"Case","operation":"Edit"},{"anyOf":[{"kind":"objectPermission","object":"Case","operation":"ModifyAllRecords"},{"allOf":[{"apiName":"EditCaseComments","kind":"systemPermission"},{"field":"CreatedById","kind":"recordFieldEqualsCurrentUser"}]}]}]},"objectRead":{"kind":"objectPermission","object":"Case","operation":"Read"}}`
		}
		rows.AddRow(apiName, current.SharingModel, current.EnableHierarchySharing, false, accessPolicy)
	}

	mock.ExpectQuery("SELECT `ApiName`, `SharingModel`, COALESCE\\(`EnableHierarchySharing`, 0\\), COALESCE\\(`IsCustomized`, 0\\), `AccessPolicy` FROM `_System_Object` WHERE `TableType` = \\? AND COALESCE\\(`IsDeleted`, 0\\) = 0").
		WithArgs(string(constants.TableTypeStandardObject)).
		WillReturnRows(rows)

	result := &AssertionResult{Violations: []AssertionViolation{}}
	assertManagedAccessPolicyConsistency(db, result)

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("ExpectationsWereMet: %v", err)
	}
	for _, violation := range result.Violations {
		if violation.Category == "ManagedAccessPolicyDrift" {
			t.Fatalf("expected no drift when JSON key order differs, got violation: %+v", violation)
		}
	}
}
