package dialectsfcrm

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

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

const (
	testRollupServingAttestation = "nexuscrm-serving-v1-" +
		"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	testRollupServingAttestationFieldValue = `"` + testRollupServingAttestation + `"`
)

func validRollupServingAttestationHeader() map[string][]string {
	return map[string][]string{
		rollupServingAttestationRequestHeader: {testRollupServingAttestationFieldValue},
	}
}

func TestAdminRollupRecalculateNeutralHandlerSuccess(t *testing.T) {
	maintenance := &fakeRollupMaintenance{
		result: &contract.RollupRecalculateResult{Object: "Account", Recalculated: 7},
	}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
		Header: validRollupServingAttestationHeader(),
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	assertBody(t, "rollup-success", resp.Body, "",
		`{"done":false,"nextAfterId":null,"object":"Account","recalculated":7,"status":"recalculated"}`)
	if maintenance.request.Object != "Account" ||
		maintenance.request.ExpectedServingAttestation != testRollupServingAttestation {
		t.Fatalf("request = %+v", maintenance.request)
	}
}

func TestAdminRollupRecalculateNeutralHandlerIgnoresRepresentationValidators(t *testing.T) {
	maintenance := &fakeRollupMaintenance{
		result: &contract.RollupRecalculateResult{Object: "Account", Complete: true},
	}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
		Header: map[string][]string{
			rollupServingAttestationRequestHeader: {testRollupServingAttestationFieldValue},
			"If-Match":                            {`"unrelated-representation-validator"`},
			"If-Unmodified-Since":                 {"Thu, 01 Jan 1970 00:00:00 GMT"},
		},
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	if !maintenance.called || maintenance.request.ExpectedServingAttestation != testRollupServingAttestation {
		t.Fatalf("request = %+v, want only the action-specific attestation applied", maintenance.request)
	}
}

func TestAdminRollupRecalculateNeutralHandlerRequiresServingAttestation(t *testing.T) {
	for _, tc := range []struct {
		name   string
		header map[string][]string
	}{
		{name: "missing"},
		{
			name: "legacy If-Match does not substitute",
			header: map[string][]string{
				"If-Match": {testRollupServingAttestationFieldValue},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			maintenance := &fakeRollupMaintenance{
				result: &contract.RollupRecalculateResult{Object: "Account"},
			}
			resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
				Params: map[string]string{"object": "Account"},
				Header: tc.header,
			}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
			if err != nil {
				t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
			}
			if resp.Status != http.StatusPreconditionRequired {
				t.Fatalf("status = %d, want 428", resp.Status)
			}
			assertBody(t, "rollup-attestation-required", resp.Body, "", mustJSON(t,
				wire.SFErrorResponse(wire.SFCodeInvalidRequest,
					rollupServingAttestationRequestHeader+" is required for rollup maintenance; "+
						"GET /health through the same routing affinity and copy "+
						"NexusCRM-Serving-Attestation into this request field")))
			if maintenance.called {
				t.Fatal("maintenance was invoked without the required serving attestation")
			}
		})
	}
}

func TestAdminRollupRecalculateNeutralHandlerRejectsMalformedServingAttestation(t *testing.T) {
	tests := []struct {
		name   string
		header map[string][]string
	}{
		{
			name: "duplicate field lines",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {
					testRollupServingAttestationFieldValue,
					testRollupServingAttestationFieldValue,
				},
			},
		},
		{
			name: "comma list",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {
					testRollupServingAttestationFieldValue + ", " + testRollupServingAttestationFieldValue,
				},
			},
		},
		{
			name: "bare payload",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {testRollupServingAttestation},
			},
		},
		{
			name: "weak ETag syntax",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {"W/" + testRollupServingAttestationFieldValue},
			},
		},
		{
			name: "structured field parameters",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {testRollupServingAttestationFieldValue + ";x=?1"},
			},
		},
		{
			name: "tab outside string",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {"\t" + testRollupServingAttestationFieldValue},
			},
		},
		{
			name: "empty string",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {`""`},
			},
		},
		{
			name: "invalid escape",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {`"opaque\nvalue"`},
			},
		},
		{
			name: "unclosed string",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {`"opaque`},
			},
		},
		{
			name: "unescaped quote",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {`"opaque"value"`},
			},
		},
		{
			name: "control character",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {"\"opaque\x1fvalue\""},
			},
		},
		{
			name: "non ASCII",
			header: map[string][]string{
				rollupServingAttestationRequestHeader: {`"opaque-世界"`},
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			maintenance := &fakeRollupMaintenance{
				result: &contract.RollupRecalculateResult{Object: "Account"},
			}
			resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
				Params: map[string]string{"object": "Account"},
				Header: tc.header,
			}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
			if err != nil {
				t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
			}
			if resp.Status != http.StatusBadRequest {
				t.Fatalf("status = %d, want 400", resp.Status)
			}
			if !strings.Contains(mustJSON(t, resp.Body), rollupServingAttestationRequestHeader) {
				t.Fatalf("body = %s, want field name", mustJSON(t, resp.Body))
			}
			if maintenance.called {
				t.Fatal("maintenance was invoked with a malformed serving attestation")
			}
		})
	}
}

func TestRollupExpectedServingAttestationDecodesRFC9651String(t *testing.T) {
	for _, tc := range []struct {
		name       string
		fieldValue string
		want       string
	}{
		{
			name:       "outer spaces",
			fieldValue: "  " + testRollupServingAttestationFieldValue + " ",
			want:       testRollupServingAttestation,
		},
		{
			name:       "escaped quote and backslash",
			fieldValue: `"opaque\"quote\\backslash"`,
			want:       "opaque\"quote\\backslash",
		},
		{
			name:       "opaque printable ASCII",
			fieldValue: `"other namespace; uppercase=ABC%123"`,
			want:       "other namespace; uppercase=ABC%123",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := rollupExpectedServingAttestation(&dialect.Request{
				Header: map[string][]string{
					rollupServingAttestationRequestHeader: {tc.fieldValue},
				},
			})
			if err != nil {
				t.Fatalf("rollupExpectedServingAttestation: %v", err)
			}
			if got != tc.want {
				t.Fatalf("attestation = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestAdminRollupRecalculateNeutralHandlerDelegatesOpaqueAttestationComparison(t *testing.T) {
	maintenance := &fakeRollupMaintenance{err: &contract.RollupMaintenanceError{
		Kind:    contract.RollupMaintenanceErrorPreconditionFailed,
		Message: "serving attestation does not match the current server state",
	}}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
		Header: map[string][]string{
			rollupServingAttestationRequestHeader: {`"opaque-value-owned-only-by-l0"`},
		},
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusPreconditionFailed {
		t.Fatalf("status = %d, want 412", resp.Status)
	}
	if !maintenance.called {
		t.Fatal("maintenance was not invoked for a syntactically valid opaque attestation")
	}
	if maintenance.request.ExpectedServingAttestation != "opaque-value-owned-only-by-l0" {
		t.Fatalf("attestation = %q, want decoded opaque value",
			maintenance.request.ExpectedServingAttestation)
	}
}

// TestAdminRollupRecalculateNeutralHandlerBodylessPostStillWorks pins the
// backward-compatible path. This route accepted a POST with no body at all
// before it read any input; parsing must not turn that into a 400.
func TestAdminRollupRecalculateNeutralHandlerBodylessPostStillWorks(t *testing.T) {
	for _, tc := range []struct {
		name string
		body []byte
	}{
		{"absent", nil},
		{"empty", []byte{}},
		{"whitespace", []byte("  \n ")},
		{"empty object", []byte(`{}`)},
	} {
		t.Run(tc.name, func(t *testing.T) {
			maintenance := &fakeRollupMaintenance{
				result: &contract.RollupRecalculateResult{Object: "Account", Recalculated: 3, Complete: true},
			}
			resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
				Params: map[string]string{"object": "Account"},
				Header: validRollupServingAttestationHeader(),
				Body:   tc.body,
			}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
			if err != nil {
				t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
			}
			if resp.Status != http.StatusOK {
				t.Fatalf("status = %d, want 200 for a %s body", resp.Status, tc.name)
			}
			if maintenance.request.AfterID != "" || maintenance.request.MaxParents != 0 {
				t.Fatalf("request = %+v, want no paging options from a %s body", maintenance.request, tc.name)
			}
			assertBody(t, "rollup-"+tc.name, resp.Body, "",
				`{"done":true,"nextAfterId":null,"object":"Account","recalculated":3,"status":"recalculated"}`)
		})
	}
}

// TestAdminRollupRecalculateNeutralHandlerCarriesPagingOptions proves the two
// request fields reach the contract rather than being parsed and dropped.
func TestAdminRollupRecalculateNeutralHandlerCarriesPagingOptions(t *testing.T) {
	maintenance := &fakeRollupMaintenance{
		result: &contract.RollupRecalculateResult{
			Object: "Quote", Recalculated: 18, NextAfterID: "parent-060", Complete: false,
		},
	}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Quote"},
		Header: validRollupServingAttestationHeader(),
		Body:   []byte(`{"afterId":"  parent-042  ","maxParents":25}`),
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	if maintenance.request.AfterID != "parent-042" {
		t.Fatalf("request.AfterID = %q, want the trimmed cursor", maintenance.request.AfterID)
	}
	if maintenance.request.MaxParents != 25 {
		t.Fatalf("request.MaxParents = %d, want 25", maintenance.request.MaxParents)
	}
	assertBody(t, "rollup-partial", resp.Body, "",
		`{"done":false,"nextAfterId":"parent-060","object":"Quote","recalculated":18,"status":"recalculated"}`)
}

// TestAdminRollupRecalculateNeutralHandlerRejectsMalformedBody keeps a body that
// is present but unparseable distinct from an absent one.
func TestAdminRollupRecalculateNeutralHandlerRejectsMalformedBody(t *testing.T) {
	maintenance := &fakeRollupMaintenance{
		result: &contract.RollupRecalculateResult{Object: "Account"},
	}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
		Header: validRollupServingAttestationHeader(),
		Body:   []byte(`{"maxParents":`),
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400", resp.Status)
	}
	if maintenance.called {
		t.Fatal("maintenance was invoked despite a malformed body; a bad request must not start a scan")
	}
}

// TestAdminRollupRecalculateNeutralHandlerFailsClosedOnNilResult covers the
// defensive path. Before this route reported completion, synthesising an empty
// success there was harmless. Now it would have to claim either "done" — which
// stops a driver on an object that may still be drifted — or "not done" with no
// cursor, the ambiguous state the wire shape exists to prevent. Neither is
// truthful when the platform returned nothing, so the route fails closed.
func TestAdminRollupRecalculateNeutralHandlerFailsClosedOnNilResult(t *testing.T) {
	maintenance := &fakeRollupMaintenance{result: nil, err: nil}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
		Header: validRollupServingAttestationHeader(),
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusInternalServerError {
		t.Fatalf("status = %d, want 500 for a contract-violating nil result", resp.Status)
	}
	body := mustJSON(t, resp.Body)
	if strings.Contains(body, `"done"`) || strings.Contains(body, `"recalculated"`) {
		t.Fatalf("body = %s, want no completion claim about a scan the platform did not report", body)
	}
}

// TestAdminRollupRecalculateNeutralHandlerRejectsWrongTypedOption keeps a
// well-formed JSON body with a wrong-typed option on the rejection path rather
// than letting it decode to a silent zero.
func TestAdminRollupRecalculateNeutralHandlerRejectsWrongTypedOption(t *testing.T) {
	maintenance := &fakeRollupMaintenance{
		result: &contract.RollupRecalculateResult{Object: "Account"},
	}
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
		Header: validRollupServingAttestationHeader(),
		Body:   []byte(`{"maxParents":"25"}`),
	}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: maintenance}})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400 for a string maxParents", resp.Status)
	}
	if maintenance.called {
		t.Fatal("maintenance was invoked with an undecodable option")
	}
}

func TestAdminRollupRecalculateNeutralHandlerUnavailable(t *testing.T) {
	resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"object": "Account"},
	}, dialect.Deps{})
	if err != nil {
		t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
	}
	if resp.Status != http.StatusServiceUnavailable {
		t.Fatalf("status = %d, want 503", resp.Status)
	}
	assertBody(t, "rollup-unavailable", resp.Body, "", `{"error":"rollup service not initialized"}`)
}

func TestAdminRollupRecalculateNeutralHandlerErrors(t *testing.T) {
	tests := []struct {
		name       string
		err        error
		wantStatus int
		wantBody   string
	}{
		{
			name:       "precondition required",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorPreconditionRequired, Message: "serving attestation is required for rollup maintenance"},
			wantStatus: http.StatusPreconditionRequired,
			wantBody: mustJSON(t, wire.SFErrorResponse(wire.SFCodeInvalidRequest,
				rollupServingAttestationRequestHeader+" is required for rollup maintenance; "+
					"GET /health through the same routing affinity and copy "+
					"NexusCRM-Serving-Attestation into this request field")),
		},
		{
			name:       "precondition failed",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorPreconditionFailed, Message: "serving attestation does not match the current server state"},
			wantStatus: http.StatusPreconditionFailed,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeInvalidRequest, "serving attestation does not match the current server state")),
		},
		{
			name:       "not found",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorNotFound, Message: "Object with ID 'Unknown' not found"},
			wantStatus: http.StatusNotFound,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeNotFound, "Object with ID 'Unknown' not found")),
		},
		{
			name:       "validation fields",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorValidation, Message: "invalid filter", Fields: []string{"Filter"}},
			wantStatus: http.StatusBadRequest,
			wantBody:   mustJSON(t, wire.SFErrorWithFields(wire.SFCodeInvalidRequest, "invalid filter", "Filter")),
		},
		{
			name:       "validation rule",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorValidationRule, Message: "Description is required"},
			wantStatus: http.StatusBadRequest,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeFieldCustomValidationException, "Description is required")),
		},
		{
			name:       "field denied",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorFieldDenied, Message: "Unable to create/update fields: Amount.", Fields: []string{"Amount"}},
			wantStatus: http.StatusBadRequest,
			wantBody:   mustJSON(t, wire.SFErrorWithFields(wire.SFCodeInvalidFieldForInsertUpdate, "Unable to create/update fields: Amount.", "Amount")),
		},
		{
			name:       "permission",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorPermission, Message: "permission denied: cannot read Account"},
			wantStatus: http.StatusForbidden,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeForbidden, "permission denied: cannot read Account")),
		},
		{
			name:       "lock contention",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorLockContention, Message: "could not acquire row lock after retries; the record is being modified by another transaction"},
			wantStatus: http.StatusConflict,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeUnableToLockRow, "could not acquire row lock after retries; the record is being modified by another transaction")),
		},
		{
			name: "domain invalid reference",
			err: &contract.RollupMaintenanceError{
				Kind:  contract.RollupMaintenanceErrorDomainWire,
				Cause: sharedErrors.NewInvalidReferenceError("bad parent reference", "ParentId"),
			},
			wantStatus: http.StatusBadRequest,
			wantBody:   mustJSON(t, wire.SFErrorWithFields(wire.SFCodeInvalidCrossReferenceKey, "bad parent reference", "ParentId")),
		},
		{
			name: "domain field integrity",
			err: &contract.RollupMaintenanceError{
				Kind:  contract.RollupMaintenanceErrorDomainWire,
				Cause: sharedErrors.NewFieldIntegrityError("autonumber token could not be resolved", "$Foo"),
			},
			wantStatus: http.StatusBadRequest,
			wantBody:   mustJSON(t, wire.SFErrorWithFields(wire.SFCodeFieldIntegrityException, "autonumber token could not be resolved", "$Foo")),
		},
		{
			name:       "internal",
			err:        &contract.RollupMaintenanceError{Kind: contract.RollupMaintenanceErrorInternal, Message: "An internal server error occurred. Contact your system administrator."},
			wantStatus: http.StatusInternalServerError,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeInternalError, "An internal server error occurred. Contact your system administrator.")),
		},
		{
			name:       "plain",
			err:        errors.New("bad rollup"),
			wantStatus: http.StatusBadRequest,
			wantBody:   mustJSON(t, wire.SFErrorResponse(wire.SFCodeInvalidRequest, "bad rollup")),
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			resp, err := AdminRollupRecalculateNeutralHandler(context.Background(), &dialect.Request{
				Params: map[string]string{"object": "Account"},
				Header: validRollupServingAttestationHeader(),
			}, dialect.Deps{Contracts: contract.Deps{RollupMaintenance: &fakeRollupMaintenance{err: tc.err}}})
			if err != nil {
				t.Fatalf("AdminRollupRecalculateNeutralHandler: %v", err)
			}
			if resp.Status != tc.wantStatus {
				t.Fatalf("status = %d, want %d", resp.Status, tc.wantStatus)
			}
			assertBody(t, tc.name, resp.Body, "", tc.wantBody)
		})
	}
}

type fakeRollupMaintenance struct {
	result  *contract.RollupRecalculateResult
	err     error
	request contract.RollupRecalculateRequest
	called  bool
}

func (f *fakeRollupMaintenance) RecalculateRollups(_ context.Context, request contract.RollupRecalculateRequest) (*contract.RollupRecalculateResult, error) {
	f.called = true
	f.request = request
	return f.result, f.err
}
