package packs

import (
	"bytes"
	"context"
	"encoding/json"
	"regexp"
	"strings"
	"testing"

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

const legacyFieldMappingPolicyPayload = `[
  {
    "sourceObject": "Lead",
    "targetObject": "Opportunity",
    "actionType": "convert",
    "mappings": [
      {
        "sourceField": "Lead_Source_Details__c",
        "targetField": "Lead_Source_Detail__c",
        "valueMap": {
          "LinkedIn - Display": "Linkedin - Display"
        },
        "onUnmappedValue": "passthrough"
      }
    ]
  }
]`

func TestParseFieldMappingGroupsPayloadPreservesValuePolicy(t *testing.T) {
	groups, err := parseFieldMappingGroupsPayload([]byte(legacyFieldMappingPolicyPayload))
	if err != nil {
		t.Fatalf("parseFieldMappingGroupsPayload: %v", err)
	}

	roundTrip, err := json.Marshal(groups)
	if err != nil {
		t.Fatalf("json.Marshal: %v", err)
	}
	if !bytes.Contains(roundTrip, []byte(`"valueMap":{"LinkedIn - Display":"Linkedin - Display"}`)) {
		t.Fatalf("parsed field mapping dropped valueMap: %s", roundTrip)
	}
	if !bytes.Contains(roundTrip, []byte(`"onUnmappedValue":"passthrough"`)) {
		t.Fatalf("parsed field mapping dropped onUnmappedValue: %s", roundTrip)
	}
}

func TestParseFieldMappingGroupsPayloadRejectsInvalidPolicy(t *testing.T) {
	tests := []struct {
		name    string
		payload string
		want    string
	}{
		{
			name: "unknown mapping field",
			payload: strings.Replace(
				legacyFieldMappingPolicyPayload,
				`"onUnmappedValue": "passthrough"`,
				`"onUnmappedValue": "passthrough", "futurePolicy": true`,
				1,
			),
			want: "unknown field",
		},
		{
			name: "non-scalar mapped value",
			payload: strings.Replace(
				legacyFieldMappingPolicyPayload,
				`"LinkedIn - Display": "Linkedin - Display"`,
				`"LinkedIn - Display": ["Linkedin - Display"]`,
				1,
			),
			want: "must be a string, number, or boolean scalar",
		},
		{
			name: "unknown unmapped policy",
			payload: strings.Replace(
				legacyFieldMappingPolicyPayload,
				`"onUnmappedValue": "passthrough"`,
				`"onUnmappedValue": "drop"`,
				1,
			),
			want: "OnUnmappedValue \"drop\" is not valid",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, err := parseFieldMappingGroupsPayload([]byte(tc.payload))
			if err == nil {
				t.Fatal("expected invalid policy payload to be rejected")
			}
			if !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("error %q does not contain %q", err, tc.want)
			}
		})
	}
}

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

	groups, err := parseFieldMappingGroupsPayload([]byte(legacyFieldMappingPolicyPayload))
	if err != nil {
		t.Fatalf("parseFieldMappingGroupsPayload: %v", err)
	}

	mock.ExpectExec(`(?s)UPDATE _System_ObjectFieldMapping.*SET Source =`).
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec(`(?s)INSERT INTO _System_ObjectFieldMapping.*ValueMap.*OnUnmappedValue.*ON DUPLICATE KEY UPDATE.*ValueMap.*OnUnmappedValue`).
		WithArgs(
			"ofm_salesforce-crm-standard_lead_opportunity_convert_lead_source_details__c_lead_source_detail__c",
			"Lead", "Opportunity", "convert", "Lead_Source_Details__c", "Lead_Source_Detail__c",
			nil, nil, `{"LinkedIn - Display":"Linkedin - Display"}`,
			0, 0, "salesforce-crm-standard", "passthrough",
			sqlmock.AnyArg(), sqlmock.AnyArg(),
		).
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec(`(?s)UPDATE _System_ObjectFieldMapping.*SET IsDeleted = 1`).
		WillReturnResult(sqlmock.NewResult(0, 0))

	if err := upsertFieldMappingGroupsForOwnerV2(
		context.Background(), db, groups, "salesforce-crm-standard",
	); err != nil {
		t.Fatalf("upsertFieldMappingGroupsForOwnerV2: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}

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

	groups, err := parseFieldMappingGroupsPayload([]byte(legacyFieldMappingPolicyPayload))
	if err != nil {
		t.Fatalf("parseFieldMappingGroupsPayload: %v", err)
	}

	liveColumns := []string{
		"TargetField", "DefaultValue", "SourceLookupField", "ValueMap", "OnUnmappedValue",
		"SortOrder", "IsCustomized", "IsActive", "IsDeleted",
	}
	mock.ExpectQuery(regexp.QuoteMeta("SELECT TargetField, DefaultValue, SourceLookupField, ValueMap, OnUnmappedValue, SortOrder, IsCustomized, IsActive, IsDeleted")).
		WithArgs("salesforce-crm-standard", "Lead", "Opportunity", "convert", "Lead_Source_Details__c").
		WillReturnRows(sqlmock.NewRows(liveColumns).
			AddRow("Lead_Source_Detail__c", nil, nil, nil, nil, 0, false, true, false))
	mock.ExpectQuery(regexp.QuoteMeta("AND Direction = 'legacy'")).
		WithArgs("salesforce-crm-standard", "Lead", "Opportunity", "convert").
		WillReturnRows(sqlmock.NewRows([]string{"SourceField"}).AddRow("Lead_Source_Details__c"))

	err = verifyFieldMappingGroupsForOwnerV2(
		context.Background(), db, groups, "salesforce-crm-standard",
	)
	if err == nil {
		t.Fatal("expected field mapping policy drift, got nil")
	}
	if !strings.Contains(err.Error(), "ValueMap =") || !strings.Contains(err.Error(), "OnUnmappedValue =") {
		t.Fatalf("drift error must identify both policy columns, got: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations unmet: %v", err)
	}
}
