package dialectsfcrm

import (
	"bytes"
	"context"
	"errors"
	"log"
	"strings"
	"testing"

	soql "github.com/nexuscrm/dialect-sf-crm-soql"
	"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 TestTranslateToolingQueryAliasesRewritesPredicate(t *testing.T) {
	req, err := soql.ParseSOQL("SELECT Id FROM ValidationRule WHERE ValidationName = 'BlockLead' AND EntityDefinition.DeveloperName = 'Lead'")
	if err != nil {
		t.Fatalf("ParseSOQL returned error: %v", err)
	}

	toolingTable := translateToolingQueryAliases(req)
	if toolingTable != "ValidationRule" {
		t.Fatalf("toolingTable = %q, want ValidationRule", toolingTable)
	}
	if req.ObjectAPIName != "_System_ValidationRule" {
		t.Fatalf("ObjectAPIName = %q, want internal validation rule table", req.ObjectAPIName)
	}
	if req.FilterExpr != "" {
		t.Fatalf("FilterExpr = %q, want parser path to keep legacy string empty", req.FilterExpr)
	}

	spec := querySpecFromParsedSOQL(req)
	if spec.Filter.Kind != queryspec.PredicateAnd {
		t.Fatalf("Filter.Kind = %q, want structured conjunction", spec.Filter.Kind)
	}
	rendered := predicateExpression(t, spec.Filter)
	if containsAny(rendered, "ValidationName", "EntityDefinition.DeveloperName") {
		t.Fatalf("filter aliases not rewritten at v63 boundary: %q", rendered)
	}
	if !strings.Contains(rendered, "Name") || !strings.Contains(rendered, "ObjectApiName") {
		t.Fatalf("filter missing translated fields: %q", rendered)
	}
}

func TestToolingQueryRecordsToV63ProjectsNeutralCarrier(t *testing.T) {
	records, err := toolingQueryRecordsToV63([]contract.Record{{
		models.ObjectTypeMetadataKey: "_System_ValidationRule",
		"attributes":                 map[string]any{"type": "Wrong", "url": "/old"},
		"Id":                         "0VR000000000001AAA",
		"Name":                       "BlockLead",
	}}, "ValidationRule", "v62.0", false)
	if err != nil {
		t.Fatalf("toolingQueryRecordsToV63() error = %v", err)
	}
	if len(records) != 1 {
		t.Fatalf("records len = %d, want 1", len(records))
	}
	row, ok := records[0].(models.SObject)
	if !ok {
		t.Fatalf("record = %T %#v, want models.SObject", records[0], records[0])
	}
	if _, ok := row[models.ObjectTypeMetadataKey]; ok {
		t.Fatalf("tooling row leaked neutral object type: %#v", row)
	}
	attrs, ok := row["attributes"].(map[string]any)
	if !ok {
		t.Fatalf("attributes = %T %#v, want map[string]any", row["attributes"], row["attributes"])
	}
	if attrs["type"] != "ValidationRule" {
		t.Fatalf("attributes.type = %#v, want ValidationRule", attrs["type"])
	}
	if attrs["url"] != "/services/data/v62.0/tooling/sobjects/ValidationRule/0VR000000000001AAA" {
		t.Fatalf("attributes.url = %#v, want versioned Tooling URL", attrs["url"])
	}
}

func TestToolingQueryNeutralHandlerDispatchesTranslatedSpec(t *testing.T) {
	query := &toolingQueryContract{
		result: &contract.QueryResult{
			TotalSize: 1,
			Done:      true,
			Records: []contract.Record{{
				models.ObjectTypeMetadataKey: "_System_ValidationRule",
				"Id":                         "0VR000000000001AAA",
				"Name":                       "BlockLead",
			}},
		},
	}
	resp, err := ToolingQueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Query: map[string][]string{
			"q": {"SELECT Id, ValidationName FROM ValidationRule WHERE EntityDefinition.DeveloperName = 'Lead'"},
		},
		User: &models.UserSession{ID: "005xx0000000001AAA", ProfileID: "00exx0000000001AAA"},
	}, dialect.Deps{Contracts: contract.Deps{Query: query}})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != 200 {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	if query.spec.ObjectAPIName != "_System_ValidationRule" {
		t.Fatalf("query object = %q, want internal validation rule table", query.spec.ObjectAPIName)
	}
	rendered := predicateExpression(t, query.spec.Filter)
	if containsAny(rendered, "ValidationName", "EntityDefinition.DeveloperName") {
		t.Fatalf("query filter aliases not translated: %q", rendered)
	}
	want := `{"done":true,"records":[{"Id":"0VR000000000001AAA","Name":"BlockLead","attributes":{"type":"ValidationRule","url":"/services/data/v63.0/tooling/sobjects/ValidationRule/0VR000000000001AAA"}}],"totalSize":1}`
	assertBody(t, "tooling query", resp.Body, want, want)
}

func TestToolingQueryNeutralHandlerRequiresQ(t *testing.T) {
	resp, err := ToolingQueryNeutralHandler(context.Background(), &dialect.Request{}, dialect.Deps{Contracts: contract.Deps{Query: &toolingQueryContract{}}})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	want := `[{"errorCode":"INVALID_QUERY","message":"q parameter is required"}]`
	assertBody(t, "missing q", resp.Body, want, want)
}

func TestToolingQueryNeutralHandlerSanitizesMissingQueryDependency(t *testing.T) {
	resp, err := ToolingQueryNeutralHandler(context.Background(), &dialect.Request{
		Query: map[string][]string{"q": {"SELECT Id FROM ValidationRule"}},
		User:  &models.UserSession{ID: "005xx0000000001AAA"},
	}, dialect.Deps{})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != 500 {
		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, "missing query dependency", resp.Body, want, want)
}

func TestToolingQueryNeutralHandlerSanitizesProjectionFailureAndLogsCause(t *testing.T) {
	var logs bytes.Buffer
	oldWriter := log.Writer()
	oldFlags := log.Flags()
	log.SetOutput(&logs)
	log.SetFlags(0)
	t.Cleanup(func() {
		log.SetOutput(oldWriter)
		log.SetFlags(oldFlags)
	})

	resp, err := ToolingQueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Query:   map[string][]string{"q": {"SELECT Name FROM ValidationRule"}},
		User:    &models.UserSession{ID: "005xx0000000001AAA"},
	}, dialect.Deps{Contracts: contract.Deps{Query: &toolingQueryContract{
		result: &contract.QueryResult{
			TotalSize: 1,
			Done:      true,
			Records: []contract.Record{{
				models.ObjectTypeMetadataKey: "_System_ValidationRule",
				"Name":                       "BlockLead",
			}},
		},
	}}})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != 500 {
		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, "projection failure", resp.Body, want, want)
	if strings.Contains(marshalJSON(t, resp.Body), "attributes.url") {
		t.Fatalf("response leaked projection cause: %s", marshalJSON(t, resp.Body))
	}
	if !strings.Contains(logs.String(), "sfcrm: record has no Id field") {
		t.Fatalf("logs = %q, want projection cause", logs.String())
	}
}

func TestToolingQueryNeutralHandlerLogsInternalQueryCause(t *testing.T) {
	var logs bytes.Buffer
	oldWriter := log.Writer()
	oldFlags := log.Flags()
	log.SetOutput(&logs)
	log.SetFlags(0)
	t.Cleanup(func() {
		log.SetOutput(oldWriter)
		log.SetFlags(oldFlags)
	})

	queryErr := toolingQueryInternalError{
		msg:   "query execution failed",
		cause: errors.New("driver: read replica unavailable"),
	}
	resp, err := ToolingQueryNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Query:   map[string][]string{"q": {"SELECT Id FROM ValidationRule"}},
		User:    &models.UserSession{ID: "005xx0000000001AAA"},
	}, dialect.Deps{Contracts: contract.Deps{Query: &toolingQueryContract{err: queryErr}}})
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != 500 {
		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, "internal query failure", resp.Body, want, want)
	rawBody := marshalJSON(t, resp.Body)
	if strings.Contains(rawBody, "read replica") || strings.Contains(rawBody, "query execution failed") {
		t.Fatalf("response leaked internal query cause: %s", rawBody)
	}
	if !strings.Contains(logs.String(), "driver: read replica unavailable") {
		t.Fatalf("logs = %q, want wrapped query cause", logs.String())
	}
}

type toolingQueryContract struct {
	spec   queryspec.QuerySpec
	result *contract.QueryResult
	err    error
}

func (q *toolingQueryContract) Query(_ context.Context, spec queryspec.QuerySpec, _ *models.UserSession) (*contract.QueryResult, error) {
	q.spec = spec
	if q.err != nil {
		return nil, q.err
	}
	return q.result, nil
}

func (q *toolingQueryContract) SearchWithSpec(context.Context, queryspec.SearchSpec, *models.UserSession) ([]contract.Record, error) {
	return nil, nil
}

func (q *toolingQueryContract) GlobalSearchWithSpec(context.Context, queryspec.GlobalSearchSpec, *models.UserSession) ([]contract.SearchResult, error) {
	return nil, nil
}

func (q *toolingQueryContract) QueryHistory(context.Context, contract.HistoryQueryRequest, *models.UserSession) (*contract.HistoryQueryResult, error) {
	return nil, nil
}

type toolingQueryInternalError struct {
	msg   string
	cause error
}

func (e toolingQueryInternalError) Error() string {
	if e.cause != nil {
		return e.msg + ": " + e.cause.Error()
	}
	return e.msg
}

func (e toolingQueryInternalError) Unwrap() error {
	return e.cause
}

func (e toolingQueryInternalError) InternalCondition() bool {
	return true
}

func containsAny(s string, needles ...string) bool {
	for _, needle := range needles {
		if strings.Contains(s, needle) {
			return true
		}
	}
	return false
}
