package packs

import (
	"context"
	"encoding/json"
	"testing"

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

func TestDirectComponentExpectedProjectionsAreDetached(t *testing.T) {
	t.Run("object spec", func(t *testing.T) {
		createable := true
		component := &sourceBackedObjectSpecComponent{
			specs: []ObjectSpec{{
				APIName:    "Widget",
				ListFields: []string{"Name"},
				Fields: []FieldSpec{{
					APIName:     "OwnerId",
					Createable:  &createable,
					ReferenceTo: []string{"User"},
				}},
			}},
		}

		firstAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("ExpectedProjection() error = %v", err)
		}
		first, ok := firstAny.([]ObjectSpec)
		if !ok {
			t.Fatalf("projection type = %T, want []ObjectSpec", firstAny)
		}
		first[0].ListFields[0] = "Mutated"
		first[0].Fields[0].APIName = "Mutated__c"
		first[0].Fields[0].ReferenceTo[0] = "Mutated"
		*first[0].Fields[0].Createable = false

		secondAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("second ExpectedProjection() error = %v", err)
		}
		second, ok := secondAny.([]ObjectSpec)
		if !ok {
			t.Fatalf("second projection type = %T, want []ObjectSpec", secondAny)
		}
		field := second[0].Fields[0]
		if second[0].ListFields[0] != "Name" || field.APIName != "OwnerId" || field.ReferenceTo[0] != "User" || field.Createable == nil || !*field.Createable {
			t.Fatalf("second projection retained caller mutation: %#v", second)
		}
	})

	t.Run("connector callout profile", func(t *testing.T) {
		component := &connectorCalloutProfileComponent{
			packName: "connector-pack",
			definition: connectorCalloutProfileDefinition{
				ProfileName: "read",
				RetryPolicy: connectorCalloutProfileRetryPolicy{RetryableStatusCodes: []int{429}},
				BatchSpec: connectorCalloutProfileBatchSpec{
					ExtraBodyFields: map[string]json.RawMessage{
						"properties": json.RawMessage(`["name"]`),
					},
				},
				ObjectPathMap: map[string]string{"account": "/accounts"},
			},
		}

		firstAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("ExpectedProjection() error = %v", err)
		}
		first, ok := firstAny.(connectorCalloutProfileProjectionRow)
		if !ok {
			t.Fatalf("projection type = %T, want connectorCalloutProfileProjectionRow", firstAny)
		}
		first.RetryPolicy.RetryableStatusCodes[0] = 500
		first.BatchSpec.ExtraBodyFields["properties"][0] = '!'
		first.BatchSpec.ExtraBodyFields["added"] = json.RawMessage(`true`)
		first.ObjectPathMap["account"] = "/mutated"

		secondAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("second ExpectedProjection() error = %v", err)
		}
		second, ok := secondAny.(connectorCalloutProfileProjectionRow)
		if !ok {
			t.Fatalf("second projection type = %T, want connectorCalloutProfileProjectionRow", secondAny)
		}
		if second.RetryPolicy.RetryableStatusCodes[0] != 429 || string(second.BatchSpec.ExtraBodyFields["properties"]) != `["name"]` || len(second.BatchSpec.ExtraBodyFields) != 1 || second.ObjectPathMap["account"] != "/accounts" {
			t.Fatalf("second projection retained caller mutation: %#v", second)
		}
	})

	t.Run("email template", func(t *testing.T) {
		folderID := "folder"
		description := "description"
		component := &sourceBackedEmailTemplateComponent{
			projection: sourceBackedEmailTemplateProjection{
				DeveloperName: "Welcome",
				FolderID:      &folderID,
				Description:   &description,
			},
		}

		firstAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("ExpectedProjection() error = %v", err)
		}
		first, ok := firstAny.(sourceBackedEmailTemplateProjection)
		if !ok {
			t.Fatalf("projection type = %T, want sourceBackedEmailTemplateProjection", firstAny)
		}
		*first.FolderID = "mutated"
		*first.Description = "mutated"

		secondAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("second ExpectedProjection() error = %v", err)
		}
		second, ok := secondAny.(sourceBackedEmailTemplateProjection)
		if !ok {
			t.Fatalf("second projection type = %T, want sourceBackedEmailTemplateProjection", secondAny)
		}
		if second.FolderID == nil || *second.FolderID != "folder" || second.Description == nil || *second.Description != "description" {
			t.Fatalf("second projection retained caller mutation: %#v", second)
		}
	})

	t.Run("connector rate limit", func(t *testing.T) {
		description := "description"
		component := &connectorRateLimitComponent{
			packName:   "connector-pack",
			definition: connectorRateLimitDefinition{EndpointClass: "read", Description: &description},
		}

		firstAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("ExpectedProjection() error = %v", err)
		}
		first, ok := firstAny.(connectorRateLimitProjectionRow)
		if !ok {
			t.Fatalf("projection type = %T, want connectorRateLimitProjectionRow", firstAny)
		}
		*first.Description = "mutated"

		secondAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("second ExpectedProjection() error = %v", err)
		}
		second, ok := secondAny.(connectorRateLimitProjectionRow)
		if !ok {
			t.Fatalf("second projection type = %T, want connectorRateLimitProjectionRow", secondAny)
		}
		if second.Description == nil || *second.Description != "description" {
			t.Fatalf("second projection retained caller mutation: %#v", second)
		}
	})

	t.Run("webhook secret", func(t *testing.T) {
		description := "description"
		component := &webhookSecretComponent{
			packName:   "connector-pack",
			definition: webhookSecretDefinition{Name: "events", Description: &description},
		}

		firstAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("ExpectedProjection() error = %v", err)
		}
		first, ok := firstAny.(webhookSecretProjectionRow)
		if !ok {
			t.Fatalf("projection type = %T, want webhookSecretProjectionRow", firstAny)
		}
		*first.Description = "mutated"

		secondAny, err := component.ExpectedProjection()
		if err != nil {
			t.Fatalf("second ExpectedProjection() error = %v", err)
		}
		second, ok := secondAny.(webhookSecretProjectionRow)
		if !ok {
			t.Fatalf("second projection type = %T, want webhookSecretProjectionRow", secondAny)
		}
		if second.Description == nil || *second.Description != "description" {
			t.Fatalf("second projection retained caller mutation: %#v", second)
		}
	})
}

func TestConnectorInboundRouteDependenciesAreDetached(t *testing.T) {
	component := &connectorInboundRouteComponent{
		manifestComponent: ManifestComponent{DependsOn: []ComponentReference{{
			Kind:        ComponentKindStandardFlow,
			BusinessKey: "Before",
			Required:    true,
		}}},
	}

	first := component.DependsOn()
	first[0].BusinessKey = "Mutated"
	second := component.DependsOn()
	if second[0].BusinessKey != "Before" {
		t.Fatalf("second DependsOn() = %#v, want detached dependency", second)
	}

	empty := &connectorInboundRouteComponent{
		manifestComponent: ManifestComponent{DependsOn: []ComponentReference{}},
	}
	if dependencies := empty.DependsOn(); dependencies == nil || len(dependencies) != 0 {
		t.Fatalf("DependsOn() = %#v, want explicit non-nil empty slice", dependencies)
	}
}

func TestCustomFieldLiveProjectionIsDetached(t *testing.T) {
	createable := true
	component := &sourceBackedCustomFieldComponent{
		packName: "extension-pack",
		declared: declaredCustomFieldExtension{
			ObjectAPIName: "Issue",
			Field: FieldSpec{
				APIName:     "EscalationCode__c",
				Createable:  &createable,
				ReferenceTo: []string{"User"},
			},
		},
	}
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New() error = %v", err)
	}
	defer db.Close()
	for range 2 {
		mock.ExpectQuery("SELECT o\\.ApiName, f\\.ApiName").
			WithArgs("Issue", "EscalationCode__c").
			WillReturnRows(sqlmock.NewRows([]string{"ApiName", "ApiName", "OwnerKind", "OwnerPack", "IsCustomized"}).
				AddRow("Issue", "EscalationCode__c", "pack", "extension-pack", 0))
	}

	firstAny, err := component.LiveProjection(context.Background(), VerifyDeps{DB: db})
	if err != nil {
		t.Fatalf("LiveProjection() error = %v", err)
	}
	first, ok := firstAny.(declaredCustomFieldExtension)
	if !ok {
		t.Fatalf("projection type = %T, want declaredCustomFieldExtension", firstAny)
	}
	first.Field.ReferenceTo[0] = "Mutated"
	*first.Field.Createable = false

	secondAny, err := component.LiveProjection(context.Background(), VerifyDeps{DB: db})
	if err != nil {
		t.Fatalf("second LiveProjection() error = %v", err)
	}
	second, ok := secondAny.(declaredCustomFieldExtension)
	if !ok {
		t.Fatalf("second projection type = %T, want declaredCustomFieldExtension", secondAny)
	}
	if second.Field.ReferenceTo[0] != "User" || second.Field.Createable == nil || !*second.Field.Createable {
		t.Fatalf("second projection retained caller mutation: %#v", second)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}
