package records

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"testing"

	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type selectedSystemProjectionStub struct {
	fields          []string
	securityContext constants.SecurityContext
	record          models.SObject
	calls           int
}

func (s *selectedSystemProjectionStub) ReadSelectedRootProjection(ctx context.Context, _ *models.ObjectMetadata, _ string, fields []string, user *models.UserSession) (models.SObject, error) {
	s.calls++
	s.fields = append([]string(nil), fields...)
	s.securityContext = constants.GetSecurityContext(ctx)
	if user != nil {
		panic("selected system record must not carry a user projection")
	}
	return s.record, nil
}

func TestReadSelectedSystemRecordUsesExactSystemProjectionAndCapsOutput(t *testing.T) {
	schema := &models.ObjectMetadata{APIName: "Issue", Fields: []models.FieldMetadata{
		{APIName: constants.FieldID, Type: constants.FieldTypeString},
		{APIName: "Subject", Type: constants.FieldTypeString},
		{APIName: "Secret", Type: constants.FieldTypeString},
	}}
	projection := &selectedSystemProjectionStub{record: models.SObject{
		constants.FieldID: "ISS-1", "Subject": "bounded", "Secret": "must-not-escape",
	}}
	service := &PersistenceService{
		metadata: metadata.NewMetadataService(&stubMetaRepoRMR{schema: schema}, nil, nil),
	}
	service.SetSelectedRootProjectionReader(projection)

	got, err := service.ReadSelectedSystemRecord(context.Background(), "Issue", "ISS-1", []string{"subject", constants.FieldID, "Subject"})
	if err != nil {
		t.Fatalf("ReadSelectedSystemRecord: %v", err)
	}
	if want := []string{constants.FieldID, "Subject"}; !reflect.DeepEqual(projection.fields, want) {
		t.Fatalf("projection fields = %v, want exact canonical %v", projection.fields, want)
	}
	if projection.securityContext != constants.SystemContext {
		t.Fatalf("security context = %v, want SystemContext", projection.securityContext)
	}
	if projection.calls != 1 {
		t.Fatalf("projection calls = %d, want 1", projection.calls)
	}
	if !reflect.DeepEqual(got, models.SObject{constants.FieldID: "ISS-1", "Subject": "bounded"}) {
		t.Fatalf("result = %#v, want only declared fields", got)
	}
}

func TestReadSelectedSystemRecordRejectsUnknownFieldBeforeProjection(t *testing.T) {
	schema := &models.ObjectMetadata{APIName: "Issue", Fields: []models.FieldMetadata{
		{APIName: constants.FieldID, Type: constants.FieldTypeString},
	}}
	projection := &selectedSystemProjectionStub{}
	service := &PersistenceService{
		metadata: metadata.NewMetadataService(&stubMetaRepoRMR{schema: schema}, nil, nil),
	}
	service.SetSelectedRootProjectionReader(projection)

	if _, err := service.ReadSelectedSystemRecord(context.Background(), "Issue", "ISS-1", []string{"Missing"}); err == nil {
		t.Fatal("ReadSelectedSystemRecord error = nil, want unknown-field rejection")
	}
	if projection.calls != 0 {
		t.Fatalf("projection calls = %d, want 0", projection.calls)
	}
}

func TestReadSelectedSystemRecordRejectsReadProtectedFieldBeforeProjection(t *testing.T) {
	schema := &models.ObjectMetadata{APIName: "GenericObject", Fields: []models.FieldMetadata{
		{APIName: constants.FieldID, Type: constants.FieldTypeString},
		{
			APIName:              "ProtectedValue",
			Type:                 constants.FieldTypeString,
			ReadProtectionPolicy: constants.FieldReadProtectionBlocked,
		},
	}}
	projection := &selectedSystemProjectionStub{record: models.SObject{
		constants.FieldID: "rec-1", "ProtectedValue": "must-never-load",
	}}
	service := &PersistenceService{
		metadata: metadata.NewMetadataService(&stubMetaRepoRMR{schema: schema}, nil, nil),
	}
	service.SetSelectedRootProjectionReader(projection)

	_, err := service.ReadSelectedSystemRecord(context.Background(), schema.APIName, "rec-1", []string{constants.FieldID, "ProtectedValue"})
	if err == nil || !strings.Contains(err.Error(), "read-protected") {
		t.Fatalf("ReadSelectedSystemRecord error = %v, want read-protected rejection", err)
	}
	if projection.calls != 0 {
		t.Fatalf("projection calls = %d, want blocked selection rejected before database access", projection.calls)
	}
}

func TestReadSelectedSystemRecordSupportsEveryProjectablePhysicalFieldType(t *testing.T) {
	physicalTypes := []constants.SchemaFieldType{
		constants.FieldTypeString,
		constants.FieldTypeInt,
		constants.FieldTypeDouble,
		constants.FieldTypeCurrency,
		constants.FieldTypeDate,
		constants.FieldTypeDatetime,
		constants.FieldTypeBoolean,
		constants.FieldTypePicklist,
		constants.FieldTypeEmail,
		constants.FieldTypePhone,
		constants.FieldTypeTextarea,
		constants.FieldTypeUrl,
		constants.FieldTypePercent,
		constants.FieldTypeReference,
		constants.FieldTypeMasterDetail,
		constants.FieldTypeRichtextarea,
		constants.FieldTypeAutonumber,
		constants.FieldTypeMultiPicklist,
		constants.FieldTypeEncryptedString,
		constants.FieldTypePassword,
		constants.FieldTypeJson,
	}
	schema := &models.ObjectMetadata{APIName: "PhysicalTypes", Fields: []models.FieldMetadata{
		{APIName: constants.FieldID, Type: constants.FieldTypeString},
	}}
	selected := []string{constants.FieldID}
	record := models.SObject{constants.FieldID: "rec-1"}
	for i, fieldType := range physicalTypes {
		name := fmt.Sprintf("Field_%02d", i)
		schema.Fields = append(schema.Fields, models.FieldMetadata{APIName: name, Type: fieldType})
		selected = append(selected, name)
		record[name] = fmt.Sprintf("value-%02d", i)
	}
	projection := &selectedSystemProjectionStub{record: record}
	service := &PersistenceService{
		metadata: metadata.NewMetadataService(&stubMetaRepoRMR{schema: schema}, nil, nil),
	}
	service.SetSelectedRootProjectionReader(projection)

	got, err := service.ReadSelectedSystemRecord(context.Background(), schema.APIName, "rec-1", selected)
	if err != nil {
		t.Fatalf("ReadSelectedSystemRecord physical type matrix: %v", err)
	}
	if len(projection.fields) != len(selected) || len(got) != len(record) {
		t.Fatalf("projection fields=%d result fields=%d, want %d projectable physical fields", len(projection.fields), len(got), len(selected))
	}
	if got[selected[len(selected)-1]] != record[selected[len(selected)-1]] {
		t.Fatalf("last projectable field was not preserved: got=%#v want=%#v", got, record)
	}
}

func TestSelectedRootTypedContractRemainsNarrowerThanPhysicalProjection(t *testing.T) {
	schema := &models.ObjectMetadata{APIName: "Issue", Fields: []models.FieldMetadata{
		{APIName: "Tags", Type: constants.FieldTypeMultiPicklist},
	}}
	if fields, _, err := normalizeSelectedRootProjectionFields(schema, []string{"Tags"}); err != nil || !reflect.DeepEqual(fields, []string{"Tags"}) {
		t.Fatalf("physical projection normalization = %v, %v; want Tags accepted", fields, err)
	}
	if _, _, err := normalizeSelectedRootFields(schema, []string{"Tags"}); err == nil || !strings.Contains(err.Error(), "no v1 scalar representation") {
		t.Fatalf("typed normalization error = %v, want unchanged v1 representation rejection", err)
	}
}
