package dialectsfcrm

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"strings"
	"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"
)

type fakeQuickActionMetadata struct {
	schema    *models.ObjectMetadata
	actions   []*models.ActionMetadata
	err       error
	actionErr error
}

func (*fakeQuickActionMetadata) ResolvePicklistValuesForRecordType(context.Context, string, string, string) (*models.ScopedPicklistValueSet, error) {
	return nil, nil
}

func (f *fakeQuickActionMetadata) Describe(context.Context, string) (*models.ObjectMetadata, error) {
	if f.err != nil {
		return nil, f.err
	}
	return f.schema, nil
}
func (f *fakeQuickActionMetadata) ListObjects(context.Context) ([]models.ObjectMetadata, error) {
	return nil, nil
}
func (f *fakeQuickActionMetadata) Relationships(context.Context, string) ([]*models.Relationship, error) {
	return nil, nil
}
func (f *fakeQuickActionMetadata) ChildRelationships(context.Context, string) ([]*models.ObjectMetadata, error) {
	return nil, nil
}
func (f *fakeQuickActionMetadata) KeyPrefix(context.Context, string) (string, bool) {
	return "", false
}
func (f *fakeQuickActionMetadata) ResolveLayout(context.Context, string, *models.UserSession, string) (*models.PageLayout, error) {
	return nil, nil
}

func (f *fakeQuickActionMetadata) ResolveRelatedListResolution(context.Context, string, string) (*contract.RelatedListResolution, bool) {
	return nil, false
}
func (f *fakeQuickActionMetadata) Actions(context.Context, string) ([]*models.ActionMetadata, error) {
	return f.actions, f.actionErr
}

func (f *fakeQuickActionMetadata) ActionByID(context.Context, string) (*models.ActionMetadata, error) {
	return nil, nil
}

type fakeQuickActionPolicy struct {
	access *contract.EffectiveObjectAccess
}

func (f *fakeQuickActionPolicy) Access(context.Context, *models.UserSession, string, contract.AccessRequest) (*contract.EffectiveObjectAccess, error) {
	return f.access, nil
}

func quickActionRequest() *dialect.Request {
	return &dialect.Request{
		User:   &models.UserSession{ID: "005xx0000000001AAA", ProfileID: "00exx0000000001AAA"},
		Params: map[string]string{"sobject": "Account", "actionName": "Escalate"},
		Body:   []byte(`{"inputs":[]}`),
	}
}

func TestListObjectQuickActionsProjectsExactWireDTOAndFiltersByPermission(t *testing.T) {
	targetObject := "Task"
	metadata := &fakeQuickActionMetadata{
		schema: &models.ObjectMetadata{APIName: "Account", Label: "Account"},
		actions: []*models.ActionMetadata{
			{ID: "quick-edit", ObjectAPIName: "Account", Name: "Edit", Label: "Edit", Type: "Standard", Icon: "utility:edit"},
			{ID: "quick-delete", ObjectAPIName: "Account", Name: "Delete", Label: "Delete", Type: "Standard", Icon: "utility:delete"},
			{ID: "quick-clone", ObjectAPIName: "Account", Name: "Clone", Label: "Clone", Type: "Standard", Icon: "utility:copy"},
			{
				ID:            "quick-flow",
				ObjectAPIName: "Account",
				Name:          "Escalate",
				Label:         "Escalate",
				Type:          "Flow",
				Icon:          "utility:flow",
				TargetObject:  &targetObject,
				Config: map[string]interface{}{
					"flowName": "Account_Escalate",
				},
			},
		},
	}
	policy := &fakeQuickActionPolicy{access: &contract.EffectiveObjectAccess{
		Object: contract.ObjectAccess{Read: true, Create: true, Update: true, Delete: false},
	}}

	resp, err := ListObjectQuickActionsNeutralHandler(context.Background(), quickActionRequest(), dialect.Deps{
		Contracts: contract.Deps{Metadata: metadata, Policy: policy},
	})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	want := `{"data":[{"id":"quick-edit","objectApiName":"Account","name":"Edit","label":"Edit","type":"Standard","icon":"utility:edit"},{"id":"quick-clone","objectApiName":"Account","name":"Clone","label":"Clone","type":"Standard","icon":"utility:copy"},{"id":"quick-flow","objectApiName":"Account","name":"Escalate","label":"Escalate","type":"Flow","icon":"utility:flow","targetObject":"Task","config":{"flowName":"Account_Escalate"}}]}`
	assertBody(t, "quick actions", resp.Body, want, want)

	raw := mustJSON(t, resp.Body)
	for _, forbidden := range []string{models.ObjectTypeMetadataKey, "attributes", "internal_only"} {
		if json.Valid([]byte(raw)) && containsJSONKey(raw, forbidden) {
			t.Fatalf("quick action response leaked %q: %s", forbidden, raw)
		}
	}
}

func TestListGlobalQuickActionsReturnsEmptyArray(t *testing.T) {
	resp, err := ListGlobalQuickActionsNeutralHandler(context.Background(), quickActionRequest(), dialect.Deps{})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	want := mustJSON(t, []QuickActionDescribe{})
	assertBody(t, "global quick actions", resp.Body, want, want)
}

func TestListObjectQuickActionsNotFoundAndForbidden(t *testing.T) {
	req := quickActionRequest()
	req.Params["sobject"] = "Missing"
	resp, err := ListObjectQuickActionsNeutralHandler(context.Background(), req, dialect.Deps{
		Contracts: contract.Deps{
			Metadata: &fakeQuickActionMetadata{},
			Policy:   &fakeQuickActionPolicy{access: &contract.EffectiveObjectAccess{Object: contract.ObjectAccess{Read: true}}},
		},
	})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	wantNotFound := mustJSON(t, wire.SFErrorResponse(wire.SFCodeNotFound, "Object not found"))
	assertBody(t, "not found", resp.Body, wantNotFound, wantNotFound)

	resp, err = ListObjectQuickActionsNeutralHandler(context.Background(), quickActionRequest(), dialect.Deps{
		Contracts: contract.Deps{
			Metadata: &fakeQuickActionMetadata{schema: &models.ObjectMetadata{APIName: "Account"}},
			Policy:   &fakeQuickActionPolicy{access: &contract.EffectiveObjectAccess{Object: contract.ObjectAccess{Read: false}}},
		},
	})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	wantForbidden := mustJSON(t, wire.SFErrorResponse(wire.SFCodeForbidden, "Insufficient access"))
	assertBody(t, "forbidden", resp.Body, wantForbidden, wantForbidden)
}

func TestListObjectQuickActionsSanitizesActionLookupErrors(t *testing.T) {
	sensitive := "dial tcp 10.0.0.12:3306 password=super-secret SELECT * FROM _System_Action"
	resp, err := ListObjectQuickActionsNeutralHandler(context.Background(), quickActionRequest(), dialect.Deps{
		Contracts: contract.Deps{
			Metadata: &fakeQuickActionMetadata{
				schema:    &models.ObjectMetadata{APIName: "Account"},
				actionErr: errors.New(sensitive),
			},
			Policy: &fakeQuickActionPolicy{access: &contract.EffectiveObjectAccess{Object: contract.ObjectAccess{Read: true}}},
		},
	})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != http.StatusInternalServerError {
		t.Fatalf("status = %d, want 500; body=%#v", resp.Status, resp.Body)
	}
	want := mustJSON(t, wire.SFErrorResponse(wire.SFCodeInternalError, quickActionsGenericInternalErrorMessage))
	assertBody(t, "action lookup internal error", resp.Body, want, want)
	raw := mustJSON(t, resp.Body)
	for _, leaked := range []string{"super-secret", "SELECT * FROM", "10.0.0.12", "_System_Action"} {
		if strings.Contains(raw, leaked) {
			t.Fatalf("internal error response leaked %q: %s", leaked, raw)
		}
	}
}

func TestDescribeAndExecuteQuickActionPreserveLegacyEnvelopes(t *testing.T) {
	resp, err := DescribeQuickActionNeutralHandler(context.Background(), quickActionRequest(), dialect.Deps{})
	if err != nil {
		t.Fatalf("describe returned error: %v", err)
	}
	wantDescribe := mustJSON(t, wire.SFErrorResponse(wire.SFCodeNotFound, "Quick action not found: Escalate"))
	assertBody(t, "describe", resp.Body, wantDescribe, wantDescribe)

	resp, err = ExecuteQuickActionNeutralHandler(context.Background(), quickActionRequest(), dialect.Deps{})
	if err != nil {
		t.Fatalf("execute returned error: %v", err)
	}
	wantExecute := mustJSON(t, wire.SFErrorResponse(wire.SFCodeInvalidOperation, "Quick action execution not yet implemented: Escalate"))
	assertBody(t, "execute", resp.Body, wantExecute, wantExecute)
}

func TestToolingQuickActionDefinitionCreateMapsSalesforceRequest(t *testing.T) {
	admin := &fakeMetadataAuthoring{}
	resp, err := ToolingQuickActionDefinitionCreateNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{
			"DeveloperName": "Escalate",
			"MasterLabel": "Escalate Case",
			"SobjectType": "Case",
			"TargetSobjectType": "Task",
			"Type": "Create"
		}`),
	}, metadataAuthoringDeps(admin))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != http.StatusCreated {
		t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
	}
	if admin.createdAction == nil {
		t.Fatal("CreateAction was not called")
	}
	if admin.createdAction.Name != "Escalate" ||
		admin.createdAction.Label != "Escalate Case" ||
		admin.createdAction.ObjectAPIName != "Case" ||
		admin.createdAction.Type != "Create" ||
		admin.createdAction.TargetObject == nil ||
		*admin.createdAction.TargetObject != "Task" {
		t.Fatalf("created action = %#v", admin.createdAction)
	}
	want := `{"errors":[],"id":"","success":true}`
	assertBody(t, "tooling quick action created", resp.Body, want, want)
}

func TestToolingQuickActionDefinitionCreateMapsInternalRequest(t *testing.T) {
	admin := &fakeMetadataAuthoring{}
	resp, err := ToolingQuickActionDefinitionCreateNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{
			"id": "0Axx00000000001",
			"objectApiName": "Account",
			"name": "Account_Flow",
			"label": "Account Flow",
			"type": "Flow",
			"icon": "utility:flow",
			"targetObject": "Task",
			"config": {"flowName": "Account_Flow"}
		}`),
	}, metadataAuthoringDeps(admin))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != http.StatusCreated {
		t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
	}
	if admin.createdAction == nil {
		t.Fatal("CreateAction was not called")
	}
	if admin.createdAction.ID != "0Axx00000000001" ||
		admin.createdAction.ObjectAPIName != "Account" ||
		admin.createdAction.Name != "Account_Flow" ||
		admin.createdAction.Label != "Account Flow" ||
		admin.createdAction.Type != "Flow" ||
		admin.createdAction.Icon != "utility:flow" ||
		admin.createdAction.TargetObject == nil ||
		*admin.createdAction.TargetObject != "Task" ||
		admin.createdAction.Config["flowName"] != "Account_Flow" {
		t.Fatalf("created action = %#v", admin.createdAction)
	}
	want := `{"errors":[],"id":"0Axx00000000001","success":true}`
	assertBody(t, "tooling quick action created", resp.Body, want, want)
}

func TestToolingQuickActionDefinitionCreateValidatesSalesforceRequest(t *testing.T) {
	resp, err := ToolingQuickActionDefinitionCreateNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{"DeveloperName":"Escalate","Type":"Create"}`),
	}, metadataAuthoringDeps(&fakeMetadataAuthoring{}))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
	}
	want := mustJSON(t, wire.SFErrorWithFields(wire.SFCodeInvalidField, "validation error on field 'MasterLabel': MasterLabel is required", "MasterLabel"))
	assertBody(t, "tooling quick action validation", resp.Body, want, want)
}

func containsJSONKey(raw string, key string) bool {
	var value any
	if err := json.Unmarshal([]byte(raw), &value); err != nil {
		return false
	}
	return containsJSONKeyValue(value, key)
}

func containsJSONKeyValue(value any, key string) bool {
	switch v := value.(type) {
	case map[string]any:
		for k, child := range v {
			if k == key || containsJSONKeyValue(child, key) {
				return true
			}
		}
	case []any:
		for _, child := range v {
			if containsJSONKeyValue(child, key) {
				return true
			}
		}
	}
	return false
}
