package dialectsfcrm

import (
	"bytes"
	"context"
	"errors"
	"log"
	"net/http"
	"strings"
	"testing"

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

func TestMetadataAuthoringMissingContractFailsClosed(t *testing.T) {
	resp, err := MetadataFieldCreateNeutralHandler(context.Background(), &dialect.Request{}, dialect.Deps{})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	assertSFError(t, resp, http.StatusInternalServerError, wire.SFCodeInternalError)
}

func TestMetadataFieldUpdatePreservesPatchPresence(t *testing.T) {
	admin := &fakeMetadataAuthoring{}
	resp, err := MetadataFieldUpdateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"apiName": "Account", "fieldApiName": "Rating"},
		Body:   []byte(`{"required":false,"unique":true,"trackHistory":false,"calculatedFormula":"1+1"}`),
	}, metadataAuthoringDeps(admin))
	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)
	}
	if admin.updateObject != "account" || admin.updateField != "Rating" {
		t.Fatalf("update target = %s.%s, want account.Rating", admin.updateObject, admin.updateField)
	}
	if !admin.presence.RequiredProvided || !admin.presence.UniqueProvided || !admin.presence.TrackHistoryProvided {
		t.Fatalf("presence = %+v, want all three keys marked provided", admin.presence)
	}
	if admin.updates.Formula == nil || *admin.updates.Formula != "1+1" {
		t.Fatalf("formula = %v, want calculatedFormula threaded to Formula", admin.updates.Formula)
	}
}

func TestToolingCustomFieldCreateNormalizesFormulaAndUsesAuthoringContract(t *testing.T) {
	admin := &fakeMetadataAuthoring{}
	resp, err := ToolingCustomFieldCreateNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{
			"FullName":"Invoice__c.Margin__c",
			"Metadata":{
				"label":"Margin",
				"type":"Formula",
				"calculatedFormula":"Revenue__c - Cost__c",
				"returnType":"Currency",
				"formulaStorageMode":"stored"
			}
		}`),
	}, 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.createFieldObject != "Invoice__c" {
		t.Fatalf("object = %q, want Invoice__c", admin.createFieldObject)
	}
	field := admin.createdField
	if field.APIName != "Margin__c" || field.Label != "Margin" {
		t.Fatalf("field identity = %+v, want Margin__c/Margin", field)
	}
	if field.Type != constants.FieldTypeFormula {
		t.Fatalf("field type = %q, want formula", field.Type)
	}
	if field.Formula == nil || !strings.Contains(*field.Formula, "Revenue__c") {
		t.Fatalf("formula = %v, want calculated formula", field.Formula)
	}
	if field.ReturnType == nil || *field.ReturnType != constants.FieldTypeCurrency {
		t.Fatalf("returnType = %v, want currency", field.ReturnType)
	}
	if field.FormulaStorageMode == nil || *field.FormulaStorageMode != "stored" {
		t.Fatalf("storageMode = %v, want stored", field.FormulaStorageMode)
	}
}

func TestToolingProfileLayoutCreateUsesAuthoringContract(t *testing.T) {
	admin := &fakeMetadataAuthoring{}
	resp, err := ToolingProfileLayoutCreateNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{"ProfileId":"00e000000000001AAA","ObjectApiName":"Account","layout_id":"layout-account-main"}`),
	}, 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)
	}
	assignment := admin.createdLayoutAssignment
	if assignment == nil || assignment.ProfileID == nil || *assignment.ProfileID != "00e000000000001AAA" ||
		assignment.ObjectAPIName != "Account" ||
		assignment.LayoutID != "layout-account-main" {
		t.Fatalf("assignment = %#v, want ProfileId/ObjectApiName/layout_id from payload", assignment)
	}
	if assignment.RouteSource == nil || *assignment.RouteSource != models.LayoutAssignmentRouteSourceAdminAuthored {
		t.Fatalf("routeSource = %#v, want admin-authored", assignment.RouteSource)
	}
	want := `{"errors":[],"id":"00e000000000001AAA-Account-layout-account-main","success":true}`
	assertBody(t, "tooling profile layout", resp.Body, want, want)
}

func TestToolingProfileLayoutCreateRequiresPayloadFields(t *testing.T) {
	resp, err := ToolingProfileLayoutCreateNeutralHandler(context.Background(), &dialect.Request{
		Body: []byte(`{"ProfileId":"00e000000000001AAA","ObjectApiName":"Account"}`),
	}, metadataAuthoringDeps(&fakeMetadataAuthoring{}))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	assertSFError(t, resp, http.StatusBadRequest, wire.SFCodeInvalidField)
}

func TestToolingProfileLayoutCreatePreservesInvalidReferenceWireError(t *testing.T) {
	resp, err := ToolingProfileLayoutCreateNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Body:    []byte(`{"ProfileId":"00e000000000001AAA","ObjectApiName":"Account","layout_id":"layout-stale"}`),
	}, metadataAuthoringDeps(&fakeMetadataAuthoring{
		assignErr: sharederrors.NewInvalidReferenceError(`layoutId "layout-stale" does not reference a live page layout`, "layoutId"),
	}))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	want := `[{"errorCode":"INVALID_CROSS_REFERENCE_KEY","message":"layoutId \"layout-stale\" does not reference a live page layout","fields":["layoutId"]}]`
	assertBody(t, "invalid profile layout reference", resp.Body, want, want)
	if resp.Status != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
	}
}

func TestToolingProfileLayoutCreateSanitizesServerErrorsAndLogsCause(t *testing.T) {
	cases := []struct {
		name string
		err  error
		want string
	}{
		{
			name: "raw error",
			err:  errors.New("sql: exec ProfileLayout upsert failed password=secret"),
			want: "password=secret",
		},
		{
			name: "server authoring error",
			err: &contract.MetadataAuthoringError{
				Kind:    contract.MetadataAuthoringErrorServer,
				Message: "repo_layout ExecContext failed on _System_ProfileLayout password=secret",
			},
			want: "_System_ProfileLayout",
		},
		{
			name: "internal authoring error",
			err: &contract.MetadataAuthoringError{
				Kind:    contract.MetadataAuthoringErrorInternal,
				Message: "rollback ProfileLayout assignment failed password=secret",
			},
			want: "rollback ProfileLayout",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			var logs bytes.Buffer
			restore := captureMetadataAuthoringLogs(t, &logs)
			defer restore()

			resp, err := ToolingProfileLayoutCreateNeutralHandler(context.Background(), &dialect.Request{
				Version: "v63.0",
				Body:    []byte(`{"ProfileId":"00e000000000001AAA","ObjectApiName":"Account","layout_id":"layout-account-main"}`),
			}, metadataAuthoringDeps(&fakeMetadataAuthoring{assignErr: tc.err}))
			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 := `[{"errorCode":"INTERNAL_ERROR","message":"An internal server error occurred. Contact your system administrator."}]`
			assertBody(t, "profile layout server error", resp.Body, want, want)
			raw := mustJSON(t, resp.Body)
			for _, leaked := range []string{"password=secret", "_System_ProfileLayout", "ProfileLayout assignment", "exec ProfileLayout"} {
				if strings.Contains(raw, leaked) {
					t.Fatalf("response leaked %q: %s", leaked, raw)
				}
			}
			if !strings.Contains(logs.String(), tc.want) {
				t.Fatalf("logs = %q, want %q", logs.String(), tc.want)
			}
		})
	}
}

func metadataAuthoringDeps(admin contract.MetadataAuthoring) dialect.Deps {
	return dialect.Deps{Contracts: contract.Deps{MetadataAuthoring: admin}}
}

func captureMetadataAuthoringLogs(t *testing.T, logs *bytes.Buffer) func() {
	t.Helper()
	oldWriter := log.Writer()
	oldFlags := log.Flags()
	log.SetOutput(logs)
	log.SetFlags(0)
	return func() {
		log.SetOutput(oldWriter)
		log.SetFlags(oldFlags)
	}
}

type fakeMetadataAuthoring struct {
	updateObject string
	updateField  string
	updates      models.FieldMetadata
	presence     contract.FieldPatchPresence

	createFieldObject string
	createdField      models.FieldMetadata

	createdAction *models.ActionMetadata

	createdLayoutAssignment *models.LayoutAssignment
	assignErr               error
}

func (*fakeMetadataAuthoring) ListSchemasForAuthoring(context.Context) []*models.ObjectMetadata {
	return nil
}

func (*fakeMetadataAuthoring) GetEffectiveSchemaForAuthoring(context.Context, string, *models.UserSession) *models.ObjectMetadata {
	return nil
}

func (*fakeMetadataAuthoring) SharingParentRelationship(context.Context, string) *models.Relationship {
	return nil
}

func (*fakeMetadataAuthoring) CreateSchema(context.Context, contract.MetadataSchemaCreateRequest) (*models.ObjectMetadata, error) {
	return nil, nil
}

func (*fakeMetadataAuthoring) UpdateSchema(context.Context, string, *models.ObjectMetadata) error {
	return nil
}

func (*fakeMetadataAuthoring) DeleteSchema(context.Context, string) error {
	return nil
}

func (*fakeMetadataAuthoring) RefreshAuthoringMetadataCache(context.Context) error {
	return nil
}

func (f *fakeMetadataAuthoring) CreateField(_ context.Context, objectAPIName string, field *models.FieldMetadata) error {
	f.createFieldObject = objectAPIName
	if field != nil {
		f.createdField = *field
	}
	return nil
}

func (*fakeMetadataAuthoring) BatchCreateFields(context.Context, string, []models.FieldMetadata) ([]*models.FieldMetadata, error) {
	return nil, nil
}

func (f *fakeMetadataAuthoring) UpdateField(_ context.Context, objectAPIName string, fieldAPIName string, updates *models.FieldMetadata, presence contract.FieldPatchPresence) error {
	f.updateObject = objectAPIName
	f.updateField = fieldAPIName
	if updates != nil {
		f.updates = *updates
	}
	f.presence = presence
	return nil
}

func (*fakeMetadataAuthoring) DeleteField(context.Context, string, string) error {
	return nil
}

func (*fakeMetadataAuthoring) ListValidationRules(context.Context, string) []models.ValidationRule {
	return nil
}

func (*fakeMetadataAuthoring) CreateValidationRule(context.Context, *models.ValidationRule) error {
	return nil
}

func (*fakeMetadataAuthoring) UpdateValidationRule(context.Context, string, *models.ValidationRule) error {
	return nil
}

func (*fakeMetadataAuthoring) DeleteValidationRule(context.Context, string) error {
	return nil
}
func (f *fakeMetadataAuthoring) CreateAction(_ context.Context, action *models.ActionMetadata) error {
	if action != nil {
		copied := *action
		f.createdAction = &copied
	}
	return nil
}

func (f *fakeMetadataAuthoring) CreateLayoutAssignment(_ context.Context, assignment *models.LayoutAssignment) error {
	if f.assignErr != nil {
		return f.assignErr
	}
	if assignment != nil {
		copied := *assignment
		f.createdLayoutAssignment = &copied
	}
	return nil
}
