package dialectsfcrm

import (
	"context"
	"errors"
	"reflect"
	"testing"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

func TestHistoryObjectShapeOwnedByDialect(t *testing.T) {
	cases := []struct {
		historyObject string
		wantParent    string
		wantField     string
	}{
		{"AccountHistory", "Account", "AccountId"},
		{"ContactHistory", "Contact", "ContactId"},
		{"OpportunityFieldHistory", "Opportunity", "OpportunityId"},
		{"Widget__History", "Widget__c", "ParentId"},
	}
	for _, tc := range cases {
		t.Run(tc.historyObject, func(t *testing.T) {
			if got := parentObjectFromHistoryName(tc.historyObject); got != tc.wantParent {
				t.Fatalf("parentObjectFromHistoryName() = %q; want %q", got, tc.wantParent)
			}
			if got := historyParentFieldName(tc.historyObject); got != tc.wantField {
				t.Fatalf("historyParentFieldName() = %q; want %q", got, tc.wantField)
			}
		})
	}
	if isHistoryObject("OpportunityHistory") {
		t.Fatal("OpportunityHistory must remain reserved for the specialized stage/snapshot surface")
	}
	if !isHistoryObject("OpportunityFieldHistory") {
		t.Fatal("OpportunityFieldHistory must route to the generic field-history surface")
	}
}

func TestHistoryQueryLowersToNeutralContractRequest(t *testing.T) {
	query := &historyQueryCapture{
		result: &contract.HistoryQueryResult{
			TotalSize: 1,
			Done:      true,
			Rows: []contract.HistoryRecord{{
				ID:          "017hist",
				ParentID:    "001parent",
				Field:       "Name",
				DataType:    "string",
				CreatedDate: "2026-06-24T10:00:00.000Z",
				CreatedByID: "005user",
			}},
		},
	}

	resp, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Query: map[string][]string{
			"q": {"SELECT Id, Field, OldValue, NewValue, CreatedDate, CreatedById FROM AccountHistory WHERE AccountId = '001parent' AND Field = 'Name' ORDER BY CreatedDate DESC LIMIT 5 OFFSET 2"},
		},
		User: &models.UserSession{ID: "005reader"},
	}, dialect.Deps{Contracts: contract.Deps{Query: query}})
	if err != nil {
		t.Fatalf("QueryNeutralHandler error = %v", err)
	}
	if resp.Status != 200 {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}

	wantSelect := []string{"Id", "Field", "OldValue", "NewValue", "CreatedDate", "CreatedById"}
	if query.req.ParentObjectAPIName != "Account" {
		t.Fatalf("ParentObjectAPIName = %q; want Account", query.req.ParentObjectAPIName)
	}
	if query.req.ParentID != "001parent" {
		t.Fatalf("ParentID = %q; want 001parent", query.req.ParentID)
	}
	if !reflect.DeepEqual(query.req.Filter, queryspec.FieldPredicate("Field", "=", "Name")) {
		t.Fatalf("Filter = %#v; want bound Field predicate", query.req.Filter)
	}
	if !reflect.DeepEqual(query.req.SelectFields, wantSelect) {
		t.Fatalf("SelectFields = %#v; want %#v", query.req.SelectFields, wantSelect)
	}
	if query.req.Limit != 5 || query.req.Offset != 2 {
		t.Fatalf("limit/offset = %d/%d; want 5/2", query.req.Limit, query.req.Offset)
	}
	if !query.req.SortDesc {
		t.Fatal("SortDesc = false; want true")
	}
	if query.req.PageSize != historyDefaultBatchSize {
		t.Fatalf("PageSize = %d; want %d", query.req.PageSize, historyDefaultBatchSize)
	}
}

func TestHistoryQueryRejectsEmptyAndDuplicateParentPredicates(t *testing.T) {
	tests := []struct {
		name  string
		query string
	}{
		{name: "empty", query: "SELECT Id FROM AccountHistory WHERE AccountId = ''"},
		{name: "whitespace", query: "SELECT Id FROM AccountHistory WHERE AccountId = '   '"},
		{name: "empty then value", query: "SELECT Id FROM AccountHistory WHERE AccountId = '' AND AccountId = '001parent'"},
		{name: "duplicate values", query: "SELECT Id FROM AccountHistory WHERE AccountId = '001parent' AND AccountId = '001other'"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			query := &historyQueryCapture{}
			response, err := QueryNeutralHandler(context.Background(), &dialect.Request{
				Version: "v63.0", Query: map[string][]string{"q": {tt.query}},
			}, dialect.Deps{Contracts: contract.Deps{Query: query}})
			if err != nil || response.Status != 400 || query.called {
				t.Fatalf("status=%d called=%t err=%v body=%#v", response.Status, query.called, err, response.Body)
			}
		})
	}
}

func TestHistoryQueryRejectsUnsupportedParent(t *testing.T) {
	query := &historyQueryCapture{}
	resp, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Query: map[string][]string{
			"q": {"SELECT Id, Field FROM TaskHistory WHERE TaskId = '00Ttask'"},
		},
	}, dialect.Deps{Contracts: contract.Deps{Query: query}})
	if err != nil {
		t.Fatalf("QueryNeutralHandler error = %v", err)
	}
	if resp.Status != 400 {
		t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
	}
	if query.called {
		t.Fatal("unsupported TaskHistory reached the neutral history query contract")
	}
}

func TestHistoryQueryRejectsFieldPredicateWithoutParent(t *testing.T) {
	query := &historyQueryCapture{}
	resp, err := QueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Query: map[string][]string{
			"q": {"SELECT Id, Field FROM AccountHistory WHERE Field = 'Name'"},
		},
	}, dialect.Deps{Contracts: contract.Deps{Query: query}})
	if err != nil {
		t.Fatalf("QueryNeutralHandler error = %v", err)
	}
	if resp.Status != 400 {
		t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
	}
	if query.called {
		t.Fatal("Field-only history query reached the neutral history query contract")
	}
}

type historyQueryCapture struct {
	req    contract.HistoryQueryRequest
	result *contract.HistoryQueryResult
	called bool
}

func (q *historyQueryCapture) Query(context.Context, queryspec.QuerySpec, *models.UserSession) (*contract.QueryResult, error) {
	return nil, errors.New("unexpected Query call")
}

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

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

func (q *historyQueryCapture) QueryHistory(_ context.Context, req contract.HistoryQueryRequest, _ *models.UserSession) (*contract.HistoryQueryResult, error) {
	q.called = true
	q.req = req
	return q.result, nil
}
