package dialectsfcrm

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"testing"

	"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"
	"github.com/nexuscrm/shared/pkg/queryspec"
	querycodec "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/querycodec"
)

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

	tests := []struct {
		name  string
		value any
		want  string
	}{
		{name: "date", value: querycodec.DateLiteralValue("2026-07-20"), want: "2026-07-20"},
		{name: "datetime", value: querycodec.DateTimeLiteralValue("2026-07-20T12:34:56.789Z"), want: "2026-07-20 12:34:56"},
		{name: "quoted date-shaped string", value: "2026-07-20", want: "2026-07-20"},
	}
	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			if got, ok := legacyQueryScalar(test.value).(string); !ok || got != test.want {
				t.Fatalf("legacyQueryScalar() = %#v (%T), want plain string %q", got, got, test.want)
			}
		})
	}
}

func TestQueryCodecAdapterPreservesStructuredDomainFailures(t *testing.T) {
	tests := []struct {
		name   string
		err    error
		status int
		body   string
	}{
		{
			name: "invalid field with field list",
			err: &sharederrors.DomainWireError{
				Kind: sharederrors.WireInvalidField, Message: "No such column", Fields: []string{"Missing__c"},
			},
			status: 400,
			body:   `[{"errorCode":"INVALID_FIELD","message":"No such column","fields":["Missing__c"]}]`,
		},
		{
			name:   "transport-sized request",
			err:    &sharederrors.DomainWireError{Kind: sharederrors.WireRequestTooLarge, Message: "too large"},
			status: 413,
			body:   `[{"errorCode":"INVALID_REQUEST","message":"too large"}]`,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
				Version: "v63.0", Query: map[string][]string{"q": {"SELECT Id FROM Account"}},
			}, dialect.Deps{Contracts: contract.Deps{Query: queryFailureStub{err: test.err}}})
			if err != nil {
				t.Fatal(err)
			}
			body, err := json.Marshal(response.Body)
			if err != nil {
				t.Fatal(err)
			}
			if response.Status != test.status || string(body) != test.body {
				t.Fatalf("status=%d body=%s", response.Status, body)
			}
		})
	}
}

func TestQueryCodecAdapterProjectsNeutralChildRecordsOnce(t *testing.T) {
	response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0", Query: map[string][]string{"q": {"SELECT Id, (SELECT Id, LastName FROM Contacts) FROM Account"}},
	}, dialect.Deps{Contracts: contract.Deps{Query: queryFailureStub{result: &contract.QueryResult{
		TotalSize: 1,
		Done:      true,
		Records: []contract.Record{{
			models.ObjectTypeMetadataKey: "Account",
			"Id":                         "001A",
			"Contacts": []contract.Record{{
				models.ObjectTypeMetadataKey: "Contact",
				"Id":                         "003A",
				"LastName":                   "Smith",
			}},
		}},
	}}}})
	if err != nil {
		t.Fatal(err)
	}
	body, err := json.Marshal(response.Body)
	if err != nil {
		t.Fatal(err)
	}
	want := `{"done":true,"records":[{"Contacts":{"done":true,"records":[{"Id":"003A","LastName":"Smith","attributes":{"type":"Contact"}}],"totalSize":1},"Id":"001A","attributes":{"type":"Account","url":"/services/data/v63.0/sobjects/Account/001A"}}],"totalSize":1}`
	if response.Status != 200 || string(body) != want {
		t.Fatalf("status=%d body=%s\nwant=%s", response.Status, body, want)
	}
	envelope := response.Body.(map[string]any)
	parent := envelope["records"].([]any)[0].(models.SObject)
	childEnvelope := parent["Contacts"].(map[string]any)
	if _, ok := childEnvelope["records"].([]any)[0].(models.SObject); !ok {
		t.Fatalf("child record = %T, want models.SObject", childEnvelope["records"].([]any)[0])
	}
}

func TestQueryCodecAdapterPreservesLegacyAggregateRecordType(t *testing.T) {
	response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0", Query: map[string][]string{"q": {"SELECT StageName, COUNT(Id) c FROM Opportunity GROUP BY StageName"}},
	}, dialect.Deps{Contracts: contract.Deps{Query: queryFailureStub{result: &contract.QueryResult{
		TotalSize: 1, Done: true, IsAggregate: true,
		Records: []contract.Record{{"StageName": "Prospecting", "c": int64(2)}},
	}}}})
	if err != nil {
		t.Fatal(err)
	}
	envelope := response.Body.(map[string]any)
	record, ok := envelope["records"].([]any)[0].(models.SObject)
	if !ok {
		t.Fatalf("aggregate record = %T, want models.SObject", envelope["records"].([]any)[0])
	}
	if record["StageName"] != "Prospecting" || record["c"] != int64(2) {
		t.Fatalf("aggregate record = %#v", record)
	}
}

func TestQueryCodecAdapterLeavesAnonymousAttributesObjectAsOrdinaryJSON(t *testing.T) {
	response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0", Query: map[string][]string{"q": {"SELECT Id, Payload FROM Account"}},
	}, dialect.Deps{Contracts: contract.Deps{Query: queryFailureStub{result: &contract.QueryResult{
		TotalSize: 1, Done: true,
		Records: []contract.Record{{
			models.ObjectTypeMetadataKey: "Account",
			"Id":                         "001A",
			"Payload": map[string]any{
				"attributes": map[string]any{"type": "ordinary-value"},
				"items":      []any{int64(0), "", nil},
			},
		}},
	}}}})
	if err != nil {
		t.Fatal(err)
	}
	envelope := response.Body.(map[string]any)
	record := envelope["records"].([]any)[0].(models.SObject)
	payload, ok := record["Payload"].(map[string]any)
	if !ok {
		t.Fatalf("anonymous payload = %T, want ordinary map", record["Payload"])
	}
	if _, becameRecord := record["Payload"].(models.SObject); becameRecord {
		t.Fatalf("anonymous payload became models.SObject: %#v", record["Payload"])
	}
	if payload["attributes"].(map[string]any)["type"] != "ordinary-value" {
		t.Fatalf("anonymous payload = %#v", payload)
	}
}

func TestQueryCodecAdapterUnwrapsCurrentEngineChildEnvelope(t *testing.T) {
	response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0", Query: map[string][]string{"q": {"SELECT Id, (SELECT Id FROM Contacts) FROM Account"}},
	}, dialect.Deps{Contracts: contract.Deps{Query: queryFailureStub{result: &contract.QueryResult{
		TotalSize: 1,
		Done:      true,
		Records: []contract.Record{{
			models.ObjectTypeMetadataKey: "Account",
			"Id":                         "001A",
			"Contacts": map[string]any{
				"totalSize": 1,
				"done":      true,
				"records": []any{contract.Record{
					models.ObjectTypeMetadataKey: "Contact",
					"Id":                         "003A",
				}},
			},
		}},
	}}}})
	if err != nil {
		t.Fatal(err)
	}
	body, err := json.Marshal(response.Body)
	if err != nil {
		t.Fatal(err)
	}
	want := `{"done":true,"records":[{"Contacts":{"done":true,"records":[{"Id":"003A","attributes":{"type":"Contact"}}],"totalSize":1},"Id":"001A","attributes":{"type":"Account","url":"/services/data/v63.0/sobjects/Account/001A"}}],"totalSize":1}`
	if response.Status != http.StatusOK || string(body) != want {
		t.Fatalf("status=%d body=%s\nwant=%s", response.Status, body, want)
	}
}

func TestQueryCodecAdapterPreservesLookupRelationshipProjection(t *testing.T) {
	relationship := "Owner"
	metadata := &fakeQuickActionMetadata{schema: &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{{
			APIName: "OwnerId", Type: "reference", ReferenceTo: []string{"User"},
			RelationshipName: &relationship,
		}},
	}}
	response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0", Query: map[string][]string{"q": {"SELECT Id, OwnerId, Owner.Name FROM Account"}},
	}, dialect.Deps{Contracts: contract.Deps{
		Metadata: metadata,
		Query: queryFailureStub{result: &contract.QueryResult{
			TotalSize: 1,
			Done:      true,
			Records: []contract.Record{{
				models.ObjectTypeMetadataKey: "Account",
				"Id":                         "001A",
				"OwnerId":                    "005A",
				"OwnerId_Name":               "Ada Lovelace",
			}},
		}},
	}})
	if err != nil {
		t.Fatal(err)
	}
	body, err := json.Marshal(response.Body)
	if err != nil {
		t.Fatal(err)
	}
	want := `{"done":true,"records":[{"Id":"001A","Owner":{"Id":"005A","Name":"Ada Lovelace","attributes":{"type":"User"}},"OwnerId":"005A","OwnerId_Name":"Ada Lovelace","attributes":{"type":"Account","url":"/services/data/v63.0/sobjects/Account/001A"}}],"totalSize":1}`
	if response.Status != http.StatusOK || string(body) != want {
		t.Fatalf("status=%d body=%s\nwant=%s", response.Status, body, want)
	}
}

type queryFailureStub struct {
	result *contract.QueryResult
	err    error
}

func (s queryFailureStub) Query(context.Context, queryspec.QuerySpec, *models.UserSession) (*contract.QueryResult, error) {
	return s.result, s.err
}

func (queryFailureStub) QueryHistory(context.Context, contract.HistoryQueryRequest, *models.UserSession) (*contract.HistoryQueryResult, error) {
	return nil, errors.New("unexpected QueryHistory call")
}

func (queryFailureStub) SearchWithSpec(context.Context, queryspec.SearchSpec, *models.UserSession) ([]contract.Record, error) {
	return nil, errors.New("unexpected SearchWithSpec call")
}

func (queryFailureStub) GlobalSearchWithSpec(context.Context, queryspec.GlobalSearchSpec, *models.UserSession) ([]contract.SearchResult, error) {
	return nil, errors.New("unexpected GlobalSearchWithSpec call")
}
