package records

import (
	"context"
	"encoding/json"
	"os"
	"reflect"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"testing"
	"time"

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

const (
	p1cM2cSelectedFields        = 256
	p1cM2cSemanticRequestBytes  = 64 * 1024
	p1cM2cCompleteResponseBytes = 1024 * 1024
	p1cM2cIdentifierBytes       = 255
	p1cM2cDeadline              = 5 * time.Second
	p1cM2cTransportReceiveBytes = 128 * 1024
	p1cM2cTransportSendBytes    = 2 * 1024 * 1024
)

type p1cM2cSemanticRequest struct {
	Object             string   `json:"object"`
	RecordID           string   `json:"record_id"`
	SelectedRootFields []string `json:"selected_root_fields"`
	MinCatalogRevision *uint64  `json:"min_catalog_revision,omitempty"`
}

type p1cM2cSemanticField struct {
	Name   string `json:"name"`
	Kind   string `json:"kind"`
	String string `json:"string"`
}

type p1cM2cSemanticReceipts struct {
	ProtocolVersion   string `json:"protocol_version"`
	CatalogRevision   uint64 `json:"catalog_revision"`
	SchemaGeneration  uint64 `json:"schema_generation"`
	EngineGeneration  uint64 `json:"engine_generation"`
	SecurityEpoch     uint64 `json:"security_epoch"`
	CapabilityVersion string `json:"capability_version"`
}

type p1cM2cSemanticResponse struct {
	RecordID string                 `json:"record_id"`
	Fields   []p1cM2cSemanticField  `json:"fields"`
	Receipts p1cM2cSemanticReceipts `json:"receipts"`
}

type p1cM2cEnvelopeReport struct {
	Classification string `json:"classification"`
	Revisions      struct {
		NexusCRMBase string `json:"nexuscrm_base"`
		AppPacks     string `json:"app_packs"`
		Fixture      string `json:"issue_tracker_fixture"`
	} `json:"revisions"`
	Candidate struct {
		SelectedFields        int    `json:"selected_fields"`
		SemanticRequestBytes  int    `json:"semantic_request_bytes"`
		CompleteResponseBytes int    `json:"complete_response_bytes"`
		IdentifierBytes       int    `json:"identifier_bytes"`
		Deadline              string `json:"deadline"`
		TransportReceiveBytes int    `json:"transport_receive_bytes"`
		TransportSendBytes    int    `json:"transport_send_bytes"`
		Compression           string `json:"compression"`
	} `json:"candidate"`
	Vectors struct {
		FieldCountAt                       int   `json:"field_count_at"`
		FieldCountOver                     int   `json:"field_count_over"`
		RequestAtBytes                     int   `json:"request_at_bytes"`
		RequestOverBytes                   int   `json:"request_over_bytes"`
		RequestAllIndependentMaxima        int   `json:"request_all_independent_maxima_bytes"`
		ResponseAtBytes                    int   `json:"response_at_bytes"`
		ResponseOverBytes                  int   `json:"response_over_bytes"`
		ObjectFieldIdentifierAtBytes       int   `json:"object_field_identifier_at_bytes"`
		ObjectFieldIdentifierOverBytes     int   `json:"object_field_identifier_over_bytes"`
		RecordIdentifierAtBytes            int   `json:"record_identifier_at_bytes"`
		RecordIdentifierOverBytes          int   `json:"record_identifier_over_bytes"`
		DeadlineAtNanoseconds              int64 `json:"deadline_at_nanoseconds"`
		DeadlineOverNanoseconds            int64 `json:"deadline_over_nanoseconds"`
		RequestCodecFramingBudget          int   `json:"request_codec_framing_budget_bytes"`
		ResponseCodecFramingBudget         int   `json:"response_codec_framing_budget_bytes"`
		CurrentPathAtFieldBoundary         int   `json:"current_path_fields_at_boundary"`
		CurrentPathOverFieldBoundary       int   `json:"current_path_fields_one_over"`
		CurrentPathMissingDeadlineAccepted bool  `json:"current_path_missing_deadline_accepted"`
		CurrentPathOverDeadlineAccepted    bool  `json:"current_path_over_deadline_accepted"`
	} `json:"vectors"`
	Findings struct {
		LimitsAreCurrentBehavior        bool   `json:"limits_are_current_behavior"`
		WireHeadroomMeasured            bool   `json:"wire_headroom_measured"`
		ObjectField255StorageCompatible bool   `json:"object_field_255_storage_compatible"`
		ObjectFieldPhysicalAtBytes      int    `json:"object_field_physical_at_bytes"`
		ObjectFieldPhysicalOverBytes    int    `json:"object_field_physical_over_bytes"`
		Record255StorageCompatible      bool   `json:"record_255_storage_compatible"`
		GenericObjectRecordStorageBytes int    `json:"generic_object_record_storage_bytes"`
		GeneratedRecordIdentifierBytes  int    `json:"generated_record_identifier_bytes"`
		DeadlineHasRouteSLOOwner        bool   `json:"deadline_has_route_slo_owner"`
		Decision                        string `json:"decision"`
	} `json:"findings"`
}

func TestP1cM2cCandidateEnvelopeGolden(t *testing.T) {
	report := p1cM2cBuildEnvelopeReport(t)
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("resolve candidate test path")
	}
	goldenPath := strings.TrimSuffix(file, "selected_root_envelope_candidate_test.go") + "testdata/selected_root_envelope_candidate_golden.json"
	wantBytes, err := os.ReadFile(goldenPath)
	if err != nil {
		t.Fatalf("read golden: %v", err)
	}
	var want p1cM2cEnvelopeReport
	if err := json.Unmarshal(wantBytes, &want); err != nil {
		t.Fatalf("decode golden: %v", err)
	}
	if !reflect.DeepEqual(report, want) {
		got, _ := json.MarshalIndent(report, "", "  ")
		t.Fatalf("candidate envelope evidence changed; review and update the golden deliberately:\n%s", got)
	}
}

func TestP1cM2cCandidateFieldBoundaryRunsCurrentSelectedRootPath(t *testing.T) {
	schema := &models.ObjectMetadata{APIName: "Issue", SharingModel: constants.SharingModelPrivate}
	schema.Fields = append(schema.Fields,
		models.FieldMetadata{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
		models.FieldMetadata{APIName: constants.FieldOwnerID, Type: constants.FieldTypeReference, IsSystem: true},
	)
	fieldRead := make(map[string]bool, p1cM2cSelectedFields+1)
	record := models.SObject{constants.FieldID: "ISS-M2C"}
	selected := make([]string, p1cM2cSelectedFields+1)
	for i := range selected {
		name := p1cM2cShortFieldName(i)
		selected[i] = name
		schema.Fields = append(schema.Fields, models.FieldMetadata{APIName: name, Type: constants.FieldTypeString})
		fieldRead[strings.ToLower(name)] = true
		record[name] = "v"
	}
	projection := &selectedRootProjectionStub{record: record}
	service := selectedRootTestService(schema, true, fieldRead, projection)
	user := &models.UserSession{ID: "user-m2c", ProfileID: constants.ProfileStandardUser}

	at, err := service.ReadSelectedRecord(context.Background(), "Issue", "ISS-M2C", selected[:p1cM2cSelectedFields], user)
	if err != nil {
		t.Fatalf("read candidate field boundary: %v", err)
	}
	if len(at.Fields) != p1cM2cSelectedFields {
		t.Fatalf("boundary fields = %d, want %d", len(at.Fields), p1cM2cSelectedFields)
	}
	over, err := service.ReadSelectedRecord(context.Background(), "Issue", "ISS-M2C", selected, user)
	if err != nil {
		t.Fatalf("current unbounded path at one-over candidate: %v", err)
	}
	if len(over.Fields) != p1cM2cSelectedFields+1 {
		t.Fatalf("one-over fields = %d, want %d", len(over.Fields), p1cM2cSelectedFields+1)
	}
	overDeadline, cancel := context.WithTimeout(context.Background(), p1cM2cDeadline+time.Nanosecond)
	defer cancel()
	if _, err := service.ReadSelectedRecord(overDeadline, "Issue", "ISS-M2C", selected[:1], user); err != nil {
		t.Fatalf("current path unexpectedly enforces candidate deadline: %v", err)
	}
}

func p1cM2cBuildEnvelopeReport(t *testing.T) p1cM2cEnvelopeReport {
	t.Helper()
	revision := ^uint64(0)
	requestAt := p1cM2cRequestOfSize(t, p1cM2cSemanticRequestBytes, revision)
	requestOver := p1cM2cRequestOfSize(t, p1cM2cSemanticRequestBytes+1, revision)
	p1cM2cAssertRequestIndependentBounds(t, requestAt)
	p1cM2cAssertRequestIndependentBounds(t, requestOver)
	responseAt := p1cM2cResponseOfSize(t, p1cM2cCompleteResponseBytes)
	responseOver := p1cM2cResponseOfSize(t, p1cM2cCompleteResponseBytes+1)
	allMax := p1cM2cSemanticRequest{
		Object: strings.Repeat("O", p1cM2cIdentifierBytes), RecordID: strings.Repeat("r", p1cM2cIdentifierBytes),
		SelectedRootFields: p1cM2cFieldNames(p1cM2cSelectedFields, p1cM2cIdentifierBytes), MinCatalogRevision: &revision,
	}

	var report p1cM2cEnvelopeReport
	report.Classification = "candidate evidence only; no production limit is approved or enforced"
	report.Revisions.NexusCRMBase = "a6c517f7e31d158f782f6f1a85066207fad12290"
	report.Revisions.AppPacks = "bb138effbe9c2857db432224892c232e14bddf80"
	report.Revisions.Fixture = "ec2ed3b2f0501d749f7a2b2eac1a86bbeba506b8"
	report.Candidate.SelectedFields = p1cM2cSelectedFields
	report.Candidate.SemanticRequestBytes = p1cM2cSemanticRequestBytes
	report.Candidate.CompleteResponseBytes = p1cM2cCompleteResponseBytes
	report.Candidate.IdentifierBytes = p1cM2cIdentifierBytes
	report.Candidate.Deadline = p1cM2cDeadline.String()
	report.Candidate.TransportReceiveBytes = p1cM2cTransportReceiveBytes
	report.Candidate.TransportSendBytes = p1cM2cTransportSendBytes
	report.Candidate.Compression = "disabled"
	report.Vectors.FieldCountAt = p1cM2cSelectedFields
	report.Vectors.FieldCountOver = p1cM2cSelectedFields + 1
	report.Vectors.RequestAtBytes = p1cM2cJSONSize(t, requestAt)
	report.Vectors.RequestOverBytes = p1cM2cJSONSize(t, requestOver)
	report.Vectors.RequestAllIndependentMaxima = p1cM2cJSONSize(t, allMax)
	report.Vectors.ResponseAtBytes = p1cM2cJSONSize(t, responseAt)
	report.Vectors.ResponseOverBytes = p1cM2cJSONSize(t, responseOver)
	report.Vectors.ObjectFieldIdentifierAtBytes = len(strings.Repeat("A", p1cM2cIdentifierBytes))
	report.Vectors.ObjectFieldIdentifierOverBytes = len(strings.Repeat("A", p1cM2cIdentifierBytes+1))
	report.Vectors.RecordIdentifierAtBytes = len(strings.Repeat("é", 127) + "a")
	report.Vectors.RecordIdentifierOverBytes = len(strings.Repeat("é", 127) + "ab")
	report.Vectors.DeadlineAtNanoseconds = p1cM2cDeadline.Nanoseconds()
	report.Vectors.DeadlineOverNanoseconds = (p1cM2cDeadline + time.Nanosecond).Nanoseconds()
	report.Vectors.RequestCodecFramingBudget = p1cM2cTransportReceiveBytes - p1cM2cSemanticRequestBytes
	report.Vectors.ResponseCodecFramingBudget = p1cM2cTransportSendBytes - p1cM2cCompleteResponseBytes
	report.Vectors.CurrentPathAtFieldBoundary = p1cM2cSelectedFields
	report.Vectors.CurrentPathOverFieldBoundary = p1cM2cSelectedFields + 1
	report.Vectors.CurrentPathMissingDeadlineAccepted = true
	report.Vectors.CurrentPathOverDeadlineAccepted = true
	report.Findings.LimitsAreCurrentBehavior = false
	report.Findings.WireHeadroomMeasured = false
	report.Findings.ObjectField255StorageCompatible = false
	report.Findings.ObjectFieldPhysicalAtBytes = 64
	report.Findings.ObjectFieldPhysicalOverBytes = 65
	report.Findings.Record255StorageCompatible = false
	report.Findings.GenericObjectRecordStorageBytes = 36
	report.Findings.GeneratedRecordIdentifierBytes = 18
	report.Findings.DeadlineHasRouteSLOOwner = false
	report.Findings.Decision = "revise object/field identifiers or add an approved logical-to-physical mapping; keep record identifier, 5s, and measured wire headroom blocked"
	return report
}

func p1cM2cAssertRequestIndependentBounds(t *testing.T, request p1cM2cSemanticRequest) {
	t.Helper()
	identifier := regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
	if len(request.Object) > p1cM2cIdentifierBytes || !identifier.MatchString(request.Object) {
		t.Fatalf("object identifier is outside independent candidate: %d bytes", len(request.Object))
	}
	if len(request.RecordID) > p1cM2cIdentifierBytes {
		t.Fatalf("record identifier is outside independent candidate: %d bytes", len(request.RecordID))
	}
	if len(request.SelectedRootFields) != p1cM2cSelectedFields {
		t.Fatalf("selected fields = %d, want %d", len(request.SelectedRootFields), p1cM2cSelectedFields)
	}
	seen := make(map[string]struct{}, len(request.SelectedRootFields))
	for _, field := range request.SelectedRootFields {
		if len(field) > p1cM2cIdentifierBytes || !identifier.MatchString(field) {
			t.Fatalf("field identifier is outside independent candidate: %q (%d bytes)", field, len(field))
		}
		if _, duplicate := seen[field]; duplicate {
			t.Fatalf("duplicate field identifier %q", field)
		}
		seen[field] = struct{}{}
	}
}

func p1cM2cRequestOfSize(t *testing.T, size int, revision uint64) p1cM2cSemanticRequest {
	t.Helper()
	request := p1cM2cSemanticRequest{
		Object: strings.Repeat("O", p1cM2cIdentifierBytes), RecordID: strings.Repeat("r", p1cM2cIdentifierBytes),
		SelectedRootFields: p1cM2cFieldNames(p1cM2cSelectedFields, 8), MinCatalogRevision: &revision,
	}
	for got := p1cM2cJSONSize(t, request); got < size; got = p1cM2cJSONSize(t, request) {
		advanced := false
		for i := range request.SelectedRootFields {
			if len(request.SelectedRootFields[i]) < p1cM2cIdentifierBytes && got < size {
				request.SelectedRootFields[i] += "x"
				got++
				advanced = true
			}
		}
		if !advanced {
			t.Fatalf("cannot construct %d-byte semantic request under independent candidates", size)
		}
	}
	if got := p1cM2cJSONSize(t, request); got != size {
		t.Fatalf("request bytes = %d, want %d", got, size)
	}
	return request
}

func p1cM2cResponseOfSize(t *testing.T, size int) p1cM2cSemanticResponse {
	t.Helper()
	response := p1cM2cSemanticResponse{
		RecordID: strings.Repeat("r", p1cM2cIdentifierBytes),
		Receipts: p1cM2cSemanticReceipts{
			ProtocolVersion: "1.0", CatalogRevision: ^uint64(0), SchemaGeneration: ^uint64(0),
			EngineGeneration: ^uint64(0), SecurityEpoch: ^uint64(0), CapabilityVersion: "runtime.records.read-selected-root.v1",
		},
	}
	for i := 0; i < p1cM2cSelectedFields; i++ {
		response.Fields = append(response.Fields, p1cM2cSemanticField{Name: p1cM2cShortFieldName(i), Kind: "string", String: "v"})
	}
	base := p1cM2cJSONSize(t, response)
	if base > size {
		t.Fatalf("response base bytes = %d, target %d", base, size)
	}
	response.Fields[len(response.Fields)-1].String += strings.Repeat("x", size-base)
	if got := p1cM2cJSONSize(t, response); got != size {
		t.Fatalf("response bytes = %d, want %d", got, size)
	}
	return response
}

func p1cM2cFieldNames(count, width int) []string {
	fields := make([]string, count)
	for i := range fields {
		name := p1cM2cShortFieldName(i)
		fields[i] = name + strings.Repeat("x", width-len(name))
	}
	sort.Strings(fields)
	return fields
}

func p1cM2cShortFieldName(i int) string {
	const digits = "0123456789"
	return "F" + string(digits[(i/100)%10]) + string(digits[(i/10)%10]) + string(digits[i%10])
}

func p1cM2cJSONSize(t *testing.T, value any) int {
	t.Helper()
	encoded, err := json.Marshal(value)
	if err != nil {
		t.Fatalf("encode semantic vector: %v", err)
	}
	return len(encoded)
}
