package packs

import (
	"strings"
	"testing"

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

func expectListFieldsRegistryRows(mock sqlmock.Sqlmock, rows *sqlmock.Rows) {
	mock.ExpectQuery("SELECT `ApiName`, `ListFields`, COALESCE\\(`ListFieldsCustomized`, 0\\), COALESCE\\(`IsDeleted`, 0\\) FROM `_System_Object`").
		WillReturnRows(rows)
}

func TestResolveListFieldsValueOwnership(t *testing.T) {
	tests := []struct {
		name       string
		manage     Manageability
		customized bool
		want       ObjectSpecValueDecision
	}{
		{
			name:       "locked customized still exact",
			manage:     ManageabilityLocked,
			customized: true,
			want:       ObjectSpecValueExactRequired,
		},
		{
			name:       "defaults only customized allows override",
			manage:     ManageabilityDefaultsOnly,
			customized: true,
			want:       ObjectSpecValueLiveOverrideAllowed,
		},
		{
			name:       "defaults only uncustomized exact",
			manage:     ManageabilityDefaultsOnly,
			customized: false,
			want:       ObjectSpecValueExactRequired,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := ResolveListFieldsValueOwnership(tt.manage, tt.customized); got != tt.want {
				t.Fatalf("ResolveListFieldsValueOwnership(%s, %t) = %v, want %v", tt.manage, tt.customized, got, tt.want)
			}
		})
	}
}

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

	expectListFieldsRegistryRows(mock, sqlmock.NewRows([]string{"ApiName", "ListFields", "ListFieldsCustomized", "IsDeleted"}).
		AddRow("Issue", `["Admin"]`, 1, 0))

	err = VerifyPackObjectListFields(db, "crm", []ObjectSpec{{
		APIName:    "Issue",
		ListFields: []string{"Subject"},
	}}, ManageabilityLocked)
	if err == nil {
		t.Fatal("VerifyPackObjectListFields = nil, want locked ListFields drift")
	}
	if !strings.Contains(err.Error(), "ListFields") {
		t.Fatalf("VerifyPackObjectListFields error = %v, want ListFields drift", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	expectListFieldsRegistryRows(mock, sqlmock.NewRows([]string{"ApiName", "ListFields", "ListFieldsCustomized", "IsDeleted"}).
		AddRow("Issue", `["Admin"]`, 1, 0))

	if err := VerifyPackObjectListFields(db, "crm", []ObjectSpec{{
		APIName:    "Issue",
		ListFields: []string{"Subject"},
	}}, ManageabilityDefaultsOnly); err != nil {
		t.Fatalf("VerifyPackObjectListFields = %v, want defaults_only customized override allowed", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

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

	specs := objectSpecsForListFieldsVerify([]ObjectSpec{{
		APIName:    "Issue",
		ListFields: []string{"Subject"},
	}}, ManageabilityDefaultsOnly, map[string]struct{}{"issue": {}})
	if specs[0].ListFields != nil {
		t.Fatalf("filtered ListFields = %#v, want nil for standalone list_fields override", specs[0].ListFields)
	}

	expectListFieldsRegistryRows(mock, sqlmock.NewRows([]string{"ApiName", "ListFields", "ListFieldsCustomized", "IsDeleted"}).
		AddRow("Issue", `["Status"]`, 0, 0))

	if err := VerifyPackObjectListFields(db, "crm", specs, ManageabilityDefaultsOnly); err != nil {
		t.Fatalf("VerifyPackObjectListFields = %v, want standalone override to own live value", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestObjectSpecsForListFieldsVerifyKeepsLockedObjectSpecListFields(t *testing.T) {
	specs := objectSpecsForListFieldsVerify([]ObjectSpec{{
		APIName:    "Issue",
		ListFields: []string{"Subject"},
	}}, ManageabilityLocked, map[string]struct{}{"issue": {}})
	if specs[0].ListFields == nil {
		t.Fatal("filtered ListFields = nil, want locked object_spec listFields preserved")
	}
}

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

	expectListFieldsRegistryRows(mock, sqlmock.NewRows([]string{"ApiName", "ListFields", "ListFieldsCustomized", "IsDeleted"}).
		AddRow("Issue", ``, 0, 0))

	if err := VerifyPackObjectListFields(db, "crm", []ObjectSpec{{
		APIName:    "Issue",
		ListFields: []string{},
	}}, ManageabilityLocked); err != nil {
		t.Fatalf("VerifyPackObjectListFields = %v, want blank live to match explicit empty", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestValidateInstallablePackSourceRejectsListFieldsOnSplitObjectSpec(t *testing.T) {
	root := writePackFixture(t, "list-fields-split-object", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "objects-a",
      "manageability": "locked",
      "path": "objects/a.json"
    },
    {
      "kind": "object_spec",
      "businessKey": "objects-b",
      "manageability": "locked",
      "path": "objects/b.json"
    }
  ]`, `"dependencies": [],`))
	writeFile(t, root, "objects/a.json", `[
  {
    "apiName": "Issue",
    "label": "Issue",
    "tableType": "custom_object",
    "category": "jira",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "Issue object",
    "listFields": ["Subject"],
    "fields": [
      {"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}
    ]
  }
]`)
	writeFile(t, root, "objects/b.json", `[
  {
    "apiName": "Issue",
    "label": "Issue fields",
    "tableType": "custom_object",
    "category": "jira",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "Issue field extension",
    "fields": [
      {"apiName": "Status", "label": "Status", "type": "picklist"}
    ]
  }
]`)

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted listFields on split object_spec authoring")
	}
	foundDiagnostic := false
	for _, diagnostic := range report.Diagnostics {
		if diagnostic.Code == "component.object_spec.list_fields_split_object" {
			foundDiagnostic = true
			break
		}
	}
	if !foundDiagnostic {
		t.Fatalf("diagnostics = %+v, want component.object_spec.list_fields_split_object", report.Diagnostics)
	}
	got := report.Error()
	for _, want := range []string{"Issue", "objects-a", "objects-b", "one object_spec authoring surface"} {
		if !strings.Contains(got, want) {
			t.Fatalf("diagnostic = %s, want %q", got, want)
		}
	}
}
