package compatibility_test

import (
	"encoding/json"
	"fmt"
	"os"
	"reflect"
	"strings"
	"testing"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	commonv1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/common/v1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/proto"
)

type recordAccessEnvelopeGolden struct {
	Schema            string `json:"schema"`
	EvidenceRevisions struct {
		NexusCRM string `json:"nexuscrm"`
		AppPacks string `json:"nexuscrm_app_packs"`
		Contract string `json:"contract_base"`
	} `json:"evidence_revisions"`
	CurrentRoute struct {
		MaxSubmittedRecords int    `json:"max_submitted_records"`
		GenericBodyCapBytes int    `json:"generic_body_cap_bytes"`
		BodyCapClass        string `json:"body_cap_class"`
	} `json:"current_route"`
	BusinessEnvelope struct {
		ObjectIdentifierBytes int `json:"object_identifier_bytes"`
		RecordIdentifierBytes int `json:"record_identifier_bytes"`
		MaxSubmittedRecords   int `json:"max_submitted_records"`
		MaxRequestWireBytes   int `json:"max_request_wire_bytes"`
		OneOverRequestBytes   int `json:"one_over_request_wire_bytes"`
		MaxResponseWireBytes  int `json:"max_response_wire_bytes"`
		OneOverResponseBytes  int `json:"one_over_response_wire_bytes"`
	} `json:"business_envelope"`
	Transport struct {
		GRPCFrameHeaderBytes      int    `json:"grpc_frame_header_bytes"`
		ReceiveCapBytes           int    `json:"receive_cap_bytes"`
		ReceiveHeadroomBytes      int    `json:"receive_headroom_bytes"`
		SendCapBytes              int    `json:"send_cap_bytes"`
		SendHeadroomBytes         int    `json:"send_headroom_bytes"`
		Compression               string `json:"compression"`
		SemanticByteLimitDecision string `json:"semantic_byte_limit_decision"`
	} `json:"transport"`
}

func TestRecordAccessBusinessAndTransportEnvelope(t *testing.T) {
	request := maximumRecordAccessRequest()
	response := maximumRecordAccessResponse()
	oneOverRequest := proto.Clone(request).(*runtimev1.EvaluateRecordAccessRequest)
	oneOverRequest.RecordIds = append(oneOverRequest.RecordIds, maximumRecordID(semantic.MaxRecords))
	oneOverResponse := proto.Clone(response).(*runtimev1.EvaluateRecordAccessResponse)
	oneOverResponse.Results = append(oneOverResponse.Results, &runtimev1.RecordAccessResult{
		RecordId: maximumRecordID(semantic.MaxRecords), Read: true, Update: true, Delete: true,
	})

	var got recordAccessEnvelopeGolden
	got.Schema = "runtime-record-access-wire-envelope.v1"
	got.EvidenceRevisions.NexusCRM = "964150890e86fccbea8268621bb76ea5551fb90f"
	got.EvidenceRevisions.AppPacks = "03435cd941f25e983c90dddd836dcbd1ae1fccea"
	got.EvidenceRevisions.Contract = "77290832e809df5cafa6696e48dfc54b83c564f7"
	got.CurrentRoute.MaxSubmittedRecords = semantic.MaxRecords
	got.CurrentRoute.GenericBodyCapBytes = 25 << 20
	got.CurrentRoute.BodyCapClass = "existing generic HTTP buffering safety cap; not the record-access business contract"
	got.BusinessEnvelope.ObjectIdentifierBytes = semantic.MaxObjectIdentifierBytes
	got.BusinessEnvelope.RecordIdentifierBytes = semantic.MaxRecordIdentifierBytes
	got.BusinessEnvelope.MaxSubmittedRecords = semantic.MaxRecords
	got.BusinessEnvelope.MaxRequestWireBytes = proto.Size(request)
	got.BusinessEnvelope.OneOverRequestBytes = proto.Size(oneOverRequest)
	got.BusinessEnvelope.MaxResponseWireBytes = proto.Size(response)
	got.BusinessEnvelope.OneOverResponseBytes = proto.Size(oneOverResponse)
	got.Transport.GRPCFrameHeaderBytes = 5
	got.Transport.ReceiveCapBytes = envelope.MaxRequestWireBytes
	got.Transport.ReceiveHeadroomBytes = envelope.MaxRequestWireBytes - got.BusinessEnvelope.MaxRequestWireBytes
	got.Transport.SendCapBytes = envelope.MaxResponseWireBytes
	got.Transport.SendHeadroomBytes = envelope.MaxResponseWireBytes - got.BusinessEnvelope.MaxResponseWireBytes
	got.Transport.Compression = "disabled"
	got.Transport.SemanticByteLimitDecision = "no separate invented byte limit; count and identifier limits fully bound the business shape below the existing transport caps"

	data, err := os.ReadFile("../testdata/record_access_envelope_golden.json")
	if err != nil {
		t.Fatal(err)
	}
	var want recordAccessEnvelopeGolden
	if err := json.Unmarshal(data, &want); err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(got, want) {
		encoded, _ := json.MarshalIndent(got, "", "  ")
		t.Fatalf("record-access envelope changed; review deliberately:\n%s", encoded)
	}
}

func maximumRecordAccessRequest() *runtimev1.EvaluateRecordAccessRequest {
	object := strings.Repeat("O", semantic.MaxObjectIdentifierBytes)
	request := &runtimev1.EvaluateRecordAccessRequest{ObjectId: &object}
	for i := 0; i < semantic.MaxRecords; i++ {
		request.RecordIds = append(request.RecordIds, maximumRecordID(i))
	}
	return request
}

func maximumRecordAccessResponse() *runtimev1.EvaluateRecordAccessResponse {
	response := &runtimev1.EvaluateRecordAccessResponse{
		Receipts: &commonv1.ConsistencyReceipts{
			ProtocolVersion: &commonv1.ProtocolVersion{Major: semantic.ProtocolMajor, Minor: semantic.ProtocolMinor},
			CatalogRevision: ^uint64(0), SchemaGeneration: ^uint64(0),
			EngineGeneration: ^uint64(0), SecurityEpoch: ^uint64(0),
			CapabilityVersion: &commonv1.CapabilityVersion{Name: semantic.CapabilityName, Major: 1},
		},
	}
	for i := 0; i < semantic.MaxRecords; i++ {
		response.Results = append(response.Results, &runtimev1.RecordAccessResult{
			RecordId: maximumRecordID(i), Read: true, Update: true, Delete: true,
		})
	}
	return response
}

func maximumRecordID(index int) string {
	prefix := fmt.Sprintf("R%06d", index)
	return prefix + strings.Repeat("x", semantic.MaxRecordIdentifierBytes-len(prefix))
}
