package validjsonschema

import (
	"context"
	"strings"
	"testing"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)

func TestValidatorAcceptsSchemaConformantJSON(t *testing.T) {
	result, err := New(Options{}).Validate(context.Background(), requestWithSchema(`{"name":"Aria"}`, objectSchema()))
	if err != nil {
		t.Fatalf("Validate() error = %v, want nil", err)
	}
	if !result.Approved {
		t.Fatalf("Validate() = %#v, want approved", result)
	}
}

func TestValidatorRejectsInvalidPayloadJSON(t *testing.T) {
	result, err := New(Options{}).Validate(context.Background(), requestWithSchema(`{"name":`, objectSchema()))
	if err != nil {
		t.Fatalf("Validate() error = %v, want nil", err)
	}
	if result.Approved {
		t.Fatalf("Approved = true, want false")
	}
	if result.ReasonCode != ReasonCodeInvalidJSON {
		t.Fatalf("ReasonCode = %q, want %q", result.ReasonCode, ReasonCodeInvalidJSON)
	}
}

func TestValidatorRejectsSchemaNonConformance(t *testing.T) {
	result, err := New(Options{}).Validate(context.Background(), requestWithSchema(`{"name":3}`, objectSchema()))
	if err != nil {
		t.Fatalf("Validate() error = %v, want nil", err)
	}
	if result.Approved {
		t.Fatalf("Approved = true, want false")
	}
	if result.ReasonCode != ReasonCodeSchemaInvalid {
		t.Fatalf("ReasonCode = %q, want %q", result.ReasonCode, ReasonCodeSchemaInvalid)
	}
}

func TestValidatorErrorsWhenSchemaContentMissing(t *testing.T) {
	_, err := New(Options{}).Validate(context.Background(), requestWithSchema(`{"name":"Aria"}`, nil))
	if err == nil {
		t.Fatal("Validate() error = nil, want missing schema content error")
	}
	if !strings.Contains(err.Error(), "schema content") {
		t.Fatalf("Validate() error = %q, want schema content context", err.Error())
	}
}

func TestValidatorErrorsWhenSchemaContentIsMalformed(t *testing.T) {
	_, err := New(Options{}).Validate(context.Background(), requestWithSchema(`{"name":"Aria"}`, []byte(`{"type":`)))
	if err == nil {
		t.Fatal("Validate() error = nil, want malformed schema error")
	}
	if !strings.Contains(err.Error(), "parse response schema") {
		t.Fatalf("Validate() error = %q, want parse schema context", err.Error())
	}
}

func TestSpecAndRegister(t *testing.T) {
	if Spec().Key != Key || Spec().ExecutionClass != contracts.ExecutionClassDeterministic {
		t.Fatalf("Spec() = %#v, want key and deterministic execution", Spec())
	}

	registry := pipeline.NewValidatorRegistry()
	if err := Register(registry); err != nil {
		t.Fatalf("Register() error = %v, want nil", err)
	}
	if registered, ok := registry.Spec(Key); !ok || registered.Key != Key {
		t.Fatalf("Spec(%q) = %#v, %v", Key, registered, ok)
	}
}

func requestWithSchema(payload string, schema []byte) contracts.SerializedValidationRequest {
	return contracts.SerializedValidationRequest{
		Schema: contracts.ArtifactSchema{
			ID:         "test.schema",
			Name:       "test_schema",
			Version:    "v1",
			JSONSchema: append([]byte(nil), schema...),
		},
		Content: []byte(payload), MediaType: "application/json",
	}
}

func objectSchema() []byte {
	return []byte(`{
		"$schema": "https://json-schema.org/draft/2020-12/schema",
		"type": "object",
		"required": ["name"],
		"properties": {
			"name": {"type": "string"}
		},
		"additionalProperties": false
	}`)
}
