package issuetrackerconformance

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
)

// RecordAccessEvaluator is the public neutral semantic operation consumed by
// the CI-only non-CRM fixture. Transport and Runtime implementation remain
// injected; the fixture imports neither backend internals nor a database.
type RecordAccessEvaluator func(context.Context, semantic.Request) (semantic.Response, error)

// Response validation is request-correlated because an unrequested result is
// an information disclosure, not merely a malformed response.
var _ func(semantic.Request, semantic.Response) error = semantic.ValidateResponse

type RecordAccessVector struct {
	Name     string
	Covers   []string
	Request  semantic.Request
	Response *semantic.Response
	Failure  *RecordAccessFailure
}

type RecordAccessFailure struct {
	Category semantic.FailureCategory  `json:"category"`
	Retry    semantic.RetryDisposition `json:"retry"`
}

type recordAccessVectorDocument struct {
	SchemaVersion string                   `json:"schema_version"`
	Vectors       []recordAccessVectorWire `json:"vectors"`
}

type recordAccessVectorWire struct {
	Name     string               `json:"name"`
	Covers   []string             `json:"covers"`
	Request  semantic.Request     `json:"request"`
	Response *semantic.Response   `json:"response,omitempty"`
	Failure  *RecordAccessFailure `json:"failure,omitempty"`
}

// RecordAccessVectors decodes the immutable contract-owned matrix without
// copying its semantic facts into a second fixture-owned golden file.
func RecordAccessVectors() ([]RecordAccessVector, error) {
	var document recordAccessVectorDocument
	if err := json.Unmarshal(semantic.ConformanceVectorsJSON(), &document); err != nil {
		return nil, fmt.Errorf("decode record-access conformance vectors: %w", err)
	}
	if document.SchemaVersion != "nexuscrm.runtime.record-access.conformance/v1" || len(document.Vectors) == 0 {
		return nil, fmt.Errorf("record-access conformance vector identity is invalid")
	}
	result := make([]RecordAccessVector, 0, len(document.Vectors))
	seen := make(map[string]struct{}, len(document.Vectors))
	for _, vector := range document.Vectors {
		if vector.Name == "" {
			return nil, fmt.Errorf("record-access conformance vector name is empty")
		}
		if _, duplicate := seen[vector.Name]; duplicate {
			return nil, fmt.Errorf("duplicate record-access conformance vector %q", vector.Name)
		}
		seen[vector.Name] = struct{}{}
		if (vector.Response == nil) == (vector.Failure == nil) {
			return nil, fmt.Errorf("record-access conformance vector %q must own exactly one outcome", vector.Name)
		}
		result = append(result, RecordAccessVector{
			Name: vector.Name, Covers: append([]string(nil), vector.Covers...),
			Request: cloneRecordAccessRequest(vector.Request), Response: cloneRecordAccessResponse(vector.Response),
			Failure: cloneRecordAccessFailure(vector.Failure),
		})
	}
	return result, nil
}

// Verify invokes the supplied neutral evaluator exactly once and compares the
// complete stable semantic outcome. Runtime setup remains owned by the caller
// so this test-only package cannot become a database or metadata authority.
func (v RecordAccessVector) Verify(ctx context.Context, evaluate RecordAccessEvaluator) error {
	if evaluate == nil {
		return fmt.Errorf("record-access evaluator is required")
	}
	response, err := evaluate(ctx, cloneRecordAccessRequest(v.Request))
	if v.Failure != nil {
		category := semantic.FailureInternal
		var failure *semantic.Failure
		switch {
		case errors.As(err, &failure):
			category = failure.Category
		case errors.Is(err, context.Canceled):
			category = semantic.FailureCanceled
		case errors.Is(err, context.DeadlineExceeded):
			category = semantic.FailureDeadlineExceeded
		}
		if err == nil || category != v.Failure.Category || semantic.RetryDispositionFor(category) != v.Failure.Retry {
			return fmt.Errorf("record-access vector %q failure = %v (%s), want %s/%s", v.Name, err, category, v.Failure.Category, v.Failure.Retry)
		}
		return nil
	}
	if err != nil {
		return fmt.Errorf("record-access vector %q failed: %w", v.Name, err)
	}
	if validationErr := semantic.ValidateResponse(v.Request, response); validationErr != nil {
		return fmt.Errorf("record-access vector %q returned invalid response: %w", v.Name, validationErr)
	}
	want := cloneRecordAccessResponse(v.Response)
	if !reflect.DeepEqual(normalizeRecordAccessResponse(response), normalizeRecordAccessResponse(*want)) {
		return fmt.Errorf("record-access vector %q response = %#v, want %#v", v.Name, response, *want)
	}
	return nil
}

func cloneRecordAccessRequest(request semantic.Request) semantic.Request {
	clone := semantic.Request{RecordIDs: append([]string(nil), request.RecordIDs...)}
	if request.ObjectID != nil {
		object := *request.ObjectID
		clone.ObjectID = &object
	}
	return clone
}

func cloneRecordAccessResponse(response *semantic.Response) *semantic.Response {
	if response == nil {
		return nil
	}
	clone := *response
	clone.Results = append([]semantic.Result(nil), response.Results...)
	return &clone
}

func cloneRecordAccessFailure(failure *RecordAccessFailure) *RecordAccessFailure {
	if failure == nil {
		return nil
	}
	clone := *failure
	return &clone
}

func normalizeRecordAccessResponse(response semantic.Response) semantic.Response {
	if response.Results == nil {
		response.Results = []semantic.Result{}
	}
	return response
}
