package dialectsfcrm

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

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestFlowToolingPatchIntentOwnsSparseWireShape(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name                 string
		target               string
		raw                  string
		wantDefinitionAction contract.FlowAdminDefinitionPatchAction
		wantVersionAction    contract.FlowAdminVersionPatchAction
		wantDescription      string
		wantDescriptionSet   bool
		wantMetadataRaw      string
		wantActiveVersion    int
		wantErrCode          string
		wantErrFields        []string
	}{
		{
			name:          "definition required field null rejected",
			target:        "definition",
			raw:           `{"MasterLabel":null}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"MasterLabel"},
		},
		{
			name:          "definition metadata master label null rejected",
			target:        "definition",
			raw:           `{"Metadata":{"masterLabel":null}}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"Metadata.masterLabel"},
		},
		{
			name:          "definition metadata active version number null rejected",
			target:        "definition",
			raw:           `{"Metadata":{"activeVersionNumber":null}}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"Metadata.activeVersionNumber"},
		},
		{
			name:          "definition metadata negative active version number rejected",
			target:        "definition",
			raw:           `{"Metadata":{"activeVersionNumber":-1}}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"Metadata.activeVersionNumber"},
		},
		{
			name:          "definition immutable fields rejected",
			target:        "definition",
			raw:           `{"ActiveVersionId":"301000000000099AAA"}`,
			wantErrCode:   wire.SFCodeInvalidFieldForInsertUpdate,
			wantErrFields: []string{"ActiveVersionId"},
		},
		{
			name:                 "definition active version routes to activate intent",
			target:               "definition",
			raw:                  `{"Metadata":{"activeVersionNumber":2}}`,
			wantDefinitionAction: contract.FlowAdminDefinitionPatchActionActivateVersion,
			wantActiveVersion:    2,
		},
		{
			name:                 "definition active version zero routes to deactivate intent",
			target:               "definition",
			raw:                  `{"Metadata":{"activeVersionNumber":0}}`,
			wantDefinitionAction: contract.FlowAdminDefinitionPatchActionDeactivate,
		},
		{
			name:              "version status active routes to lifecycle",
			target:            "version",
			raw:               `{"Status":"Active"}`,
			wantVersionAction: contract.FlowAdminVersionPatchActionActivate,
		},
		{
			name:          "version invalid status rejected",
			target:        "version",
			raw:           `{"Status":"Paused"}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"Status"},
		},
		{
			name:          "version definition id smuggling rejected",
			target:        "version",
			raw:           `{"DefinitionId":"300000000000099AAA"}`,
			wantErrCode:   wire.SFCodeInvalidFieldForInsertUpdate,
			wantErrFields: []string{"DefinitionId"},
		},
		{
			name:          "version null metadata rejected",
			target:        "version",
			raw:           `{"Metadata":null}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"Metadata"},
		},
		{
			name:              "version metadata patch is carried as raw intent",
			target:            "version",
			raw:               `{"Metadata":{"description":"after"}}`,
			wantVersionAction: contract.FlowAdminVersionPatchActionUpdate,
			wantMetadataRaw:   `{"description":"after"}`,
		},
		{
			name:               "version null description becomes explicit empty update",
			target:             "version",
			raw:                `{"Description":null}`,
			wantVersionAction:  contract.FlowAdminVersionPatchActionUpdate,
			wantDescriptionSet: true,
		},
		{
			name:          "version nested null metadata rejected",
			target:        "version",
			raw:           `{"Metadata":{"start":{"object":null}}}`,
			wantErrCode:   wire.SFCodeInvalidField,
			wantErrFields: []string{"Metadata.start.object"},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			switch tc.target {
			case "definition":
				intent, resp, err := flowToolingDefinitionPatchIntent([]byte(tc.raw))
				assertFlowToolingPatchError(t, resp, err, tc.wantErrCode, tc.wantErrFields)
				if tc.wantErrCode != "" {
					return
				}
				if intent.Action != tc.wantDefinitionAction {
					t.Fatalf("definition action = %q, want %q", intent.Action, tc.wantDefinitionAction)
				}
				if intent.ActiveVersionNumber != tc.wantActiveVersion {
					t.Fatalf("active version = %d, want %d", intent.ActiveVersionNumber, tc.wantActiveVersion)
				}
			case "version":
				intent, resp, err := flowToolingVersionPatchIntent([]byte(tc.raw))
				assertFlowToolingPatchError(t, resp, err, tc.wantErrCode, tc.wantErrFields)
				if tc.wantErrCode != "" {
					return
				}
				if intent.Action != tc.wantVersionAction {
					t.Fatalf("version action = %q, want %q", intent.Action, tc.wantVersionAction)
				}
				if intent.DescriptionProvided != tc.wantDescriptionSet {
					t.Fatalf("DescriptionProvided = %v, want %v", intent.DescriptionProvided, tc.wantDescriptionSet)
				}
				if intent.Description != tc.wantDescription {
					t.Fatalf("Description = %q, want %q", intent.Description, tc.wantDescription)
				}
				if tc.wantMetadataRaw != "" && !bytes.Equal(bytes.TrimSpace(intent.MetadataPatchRaw), []byte(tc.wantMetadataRaw)) {
					t.Fatalf("MetadataPatchRaw = %s, want %s", intent.MetadataPatchRaw, tc.wantMetadataRaw)
				}
			default:
				t.Fatalf("unknown target %q", tc.target)
			}
		})
	}
}

func TestFlowToolingPatchHandlerPassesResolvedIntent(t *testing.T) {
	t.Parallel()

	defAuthoring := &fakeFlowAuthoring{}
	resp, err := UpdateFlowDefinitionNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"id": "300000000000001AAA"},
		Body:    []byte(`{"Metadata":{"activeVersionNumber":2}}`),
		User:    &models.UserSession{ID: "005000000000001AAA"},
	}, flowDeps(nil, defAuthoring))
	if err != nil {
		t.Fatalf("definition handler error = %v", err)
	}
	if resp.Status != 204 {
		t.Fatalf("definition status = %d, want 204; body=%#v", resp.Status, resp.Body)
	}
	if defAuthoring.patchFlowDefinitionIntent.Action != contract.FlowAdminDefinitionPatchActionActivateVersion ||
		defAuthoring.patchFlowDefinitionIntent.ActiveVersionNumber != 2 {
		t.Fatalf("definition intent = %+v, want activate version 2", defAuthoring.patchFlowDefinitionIntent)
	}

	versionAuthoring := &fakeFlowAuthoring{}
	resp, err = UpdateFlowVersionNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"id": "301000000000001AAA"},
		Body:    []byte(`{"Metadata":{"description":"after"}}`),
		User:    &models.UserSession{ID: "005000000000001AAA"},
	}, flowDeps(nil, versionAuthoring))
	if err != nil {
		t.Fatalf("version handler error = %v", err)
	}
	if resp.Status != 204 {
		t.Fatalf("version status = %d, want 204; body=%#v", resp.Status, resp.Body)
	}
	if versionAuthoring.patchFlowVersionIntent.Action != contract.FlowAdminVersionPatchActionUpdate ||
		!bytes.Contains(versionAuthoring.patchFlowVersionIntent.MetadataPatchRaw, []byte(`"description":"after"`)) {
		t.Fatalf("version intent = %+v, want metadata update", versionAuthoring.patchFlowVersionIntent)
	}
}

func assertFlowToolingPatchError(t *testing.T, resp *dialect.Response, err error, wantCode string, wantFields []string) {
	t.Helper()
	if wantCode == "" {
		if resp != nil {
			t.Fatalf("response = %+v, want nil", resp)
		}
		if err != nil {
			t.Fatalf("error = %v, want nil", err)
		}
		return
	}
	if resp == nil {
		resp = flowToolingErrorResponse(&dialect.Request{Version: "v63.0"}, "patch flow", err)
	}
	if resp == nil {
		t.Fatal("error response = nil")
	}
	data, marshalErr := json.Marshal(resp.Body)
	if marshalErr != nil {
		t.Fatalf("marshal error body: %v", marshalErr)
	}
	var sfErrors []wire.SFError
	if unmarshalErr := json.Unmarshal(data, &sfErrors); unmarshalErr != nil {
		t.Fatalf("unmarshal SF errors: %v body=%s", unmarshalErr, data)
	}
	if len(sfErrors) != 1 {
		t.Fatalf("SF error count = %d, want 1 body=%s", len(sfErrors), data)
	}
	if sfErrors[0].ErrorCode != wantCode {
		t.Fatalf("errorCode = %q, want %q body=%s", sfErrors[0].ErrorCode, wantCode, data)
	}
	assertExactStrings(t, sfErrors[0].Fields, wantFields)
}

func assertExactStrings(t *testing.T, got []string, want []string) {
	t.Helper()
	if len(got) != len(want) {
		t.Fatalf("slice len = %d, want %d (got=%v want=%v)", len(got), len(want), got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("slice[%d] = %q, want %q (full=%v)", i, got[i], want[i], got)
		}
	}
}
