package metadata

import (
	"context"
	"errors"
	"strings"
	"testing"

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

func TestProgramMetadataReaderDelegatesToMetadataServiceEffectiveSnapshot(t *testing.T) {
	ctx := context.Background()
	child := &models.ObjectMetadata{
		APIName: "ProgramChild",
		Fields: []models.FieldMetadata{
			{APIName: "Title", Type: constants.FieldTypeString},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"ProgramParent"}},
		},
	}
	parent := &models.ObjectMetadata{APIName: "ProgramParent"}
	ownerPack := "program-pack"
	rule := &models.ValidationRule{
		ObjectAPIName: "ProgramChild",
		Name:          "Program_Child_Gate",
		Active:        true,
		OwnerPack:     &ownerPack,
	}
	rel := &models.Relationship{
		ChildObjectAPIName:  child.APIName,
		ParentObjectAPIName: parent.APIName,
		FieldAPIName:        "ParentId",
		RelationshipName:    "Parent",
	}
	ms := &MetadataService{}
	ms.repo = programMetadataReaderRepo{
		validationRules: map[string][]*models.ValidationRule{
			"programchild": {rule},
		},
	}
	snap := &metadataSnapshot{
		schemas:          []*models.ObjectMetadata{child, parent},
		schemaMap:        map[string]*models.ObjectMetadata{"programchild": child, "programparent": parent},
		fieldMap:         map[string][]models.FieldMetadata{"programchild": child.Fields},
		relationshipsMap: map[string][]*models.Relationship{"programchild": {rel}},
		validationRulesMap: map[string][]*models.ValidationRule{
			"programchild": {rule},
		},
	}
	ms.coreCache.Store(snap)
	ms.cache.Store(snap)
	reader := NewProgramMetadataReader(ms)

	object, ok, err := reader.Object(ctx, "programchild")
	if err != nil {
		t.Fatalf("Object: %v", err)
	}
	if !ok || object != child {
		t.Fatalf("Object ok=%v object=%p, want cached child %p", ok, object, child)
	}

	field, ok, err := reader.Field(ctx, "ProgramChild", "Title")
	if err != nil {
		t.Fatalf("Field: %v", err)
	}
	if !ok || field.APIName != "Title" {
		t.Fatalf("Field ok=%v field=%#v, want Title", ok, field)
	}
	field.APIName = "mutated"
	again, ok, err := reader.Field(ctx, "ProgramChild", "Title")
	if err != nil || !ok {
		t.Fatalf("Field second read ok=%v err=%v", ok, err)
	}
	if got, want := again.APIName, "Title"; got != want {
		t.Fatalf("Field second read APIName = %q, want %q", got, want)
	}

	fk, ok, err := reader.ForeignKeyForRelationship(ctx, "ProgramChild", "parent")
	if err != nil {
		t.Fatalf("ForeignKeyForRelationship: %v", err)
	}
	if !ok || fk != "ParentId" {
		t.Fatalf("ForeignKeyForRelationship ok=%v fk=%q, want ParentId", ok, fk)
	}

	targets, ok, err := reader.TargetObjectsForForeignKey(ctx, "ProgramChild", "parentId")
	if err != nil {
		t.Fatalf("TargetObjectsForForeignKey: %v", err)
	}
	if !ok || len(targets) != 1 || targets[0] != "ProgramParent" {
		t.Fatalf("TargetObjectsForForeignKey ok=%v targets=%v, want ProgramParent", ok, targets)
	}

	schemas, err := reader.Schemas(ctx)
	if err != nil {
		t.Fatalf("Schemas: %v", err)
	}
	if len(schemas) != 2 {
		t.Fatalf("Schemas len = %d, want 2", len(schemas))
	}
	schemas[0] = parent
	againSchemas, err := reader.Schemas(ctx)
	if err != nil {
		t.Fatalf("Schemas second read: %v", err)
	}
	if againSchemas[0] != child {
		t.Fatal("Schemas returned mutable slice backing store; want copied slice")
	}

	ruleReader := NewProgramValidationRuleReader(ms)
	rules, err := ruleReader.ValidationRules(ctx, "ProgramChild")
	if err != nil {
		t.Fatalf("ValidationRules: %v", err)
	}
	if len(rules) != 1 || rules[0] != rule {
		t.Fatalf("ValidationRules = %#v, want cached rule", rules)
	}
	rules[0] = nil
	againRules, err := ruleReader.ValidationRules(ctx, "ProgramChild")
	if err != nil {
		t.Fatalf("ValidationRules second read: %v", err)
	}
	if len(againRules) != 1 || againRules[0] != rule {
		t.Fatal("ValidationRules returned mutable slice backing store; want copied slice")
	}
}

func TestProgramMetadataReaderFailsClosedWhenUnwired(t *testing.T) {
	reader := NewProgramMetadataReader(nil)
	_, _, err := reader.Object(context.Background(), "ProgramChild")
	if !errors.Is(err, programchecker.ErrReaderUnavailable) {
		t.Fatalf("Object error = %v, want ErrReaderUnavailable", err)
	}
	_, _, err = reader.Field(context.Background(), "ProgramChild", "Title")
	if !errors.Is(err, programchecker.ErrReaderUnavailable) {
		t.Fatalf("Field error = %v, want ErrReaderUnavailable", err)
	}
	_, err = reader.Schemas(context.Background())
	if !errors.Is(err, programchecker.ErrReaderUnavailable) {
		t.Fatalf("Schemas error = %v, want ErrReaderUnavailable", err)
	}
	ruleReader := NewProgramValidationRuleReader(nil)
	_, err = ruleReader.ValidationRules(context.Background(), "ProgramChild")
	if !errors.Is(err, programchecker.ErrReaderUnavailable) {
		t.Fatalf("ValidationRules error = %v, want ErrReaderUnavailable", err)
	}
}

func TestProgramValidationRuleReaderPropagatesRepositoryErrors(t *testing.T) {
	readErr := errors.New("validation rule read failed")
	ruleReader := NewProgramValidationRuleReader(&MetadataService{
		repo: programMetadataReaderRepo{validationRulesErr: readErr},
	})

	_, err := ruleReader.ValidationRules(context.Background(), "ProgramChild")
	if !errors.Is(err, readErr) {
		t.Fatalf("ValidationRules error = %v, want %v", err, readErr)
	}
}

type programMetadataReaderRepo struct {
	ports.MetadataRepository
	validationRules    map[string][]*models.ValidationRule
	validationRulesErr error
}

func (r programMetadataReaderRepo) GetValidationRules(_ context.Context, objectAPIName string) ([]*models.ValidationRule, error) {
	if r.validationRulesErr != nil {
		return nil, r.validationRulesErr
	}
	rules := r.validationRules[strings.ToLower(strings.TrimSpace(objectAPIName))]
	out := make([]*models.ValidationRule, len(rules))
	copy(out, rules)
	return out, nil
}
