package runtimeassembly

import (
	"context"
	"errors"
	"testing"

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

type metadataAdminReadRepositoryStub struct {
	ports.MetadataRepository
	objects []*models.ObjectMetadata
	rules   []*models.ValidationRule
	err     error
	ruleErr error
}

func (repository *metadataAdminReadRepositoryStub) GetAllSchemas(context.Context) ([]*models.ObjectMetadata, error) {
	return repository.objects, repository.err
}

func (*metadataAdminReadRepositoryStub) GetAllRelationships(context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

func (*metadataAdminReadRepositoryStub) GetAllFlowDefinitions(context.Context) ([]*models.FlowDefinition, error) {
	return nil, nil
}

func (*metadataAdminReadRepositoryStub) GetAllValidationRules(context.Context) ([]*models.ValidationRule, error) {
	return nil, nil
}

func (repository *metadataAdminReadRepositoryStub) GetValidationRules(context.Context, string) ([]*models.ValidationRule, error) {
	return repository.rules, repository.ruleErr
}

func TestMetadataAdminReadRuntimePreparesCurrentMetadataService(t *testing.T) {
	repository := &metadataAdminReadRepositoryStub{objects: []*models.ObjectMetadata{{
		ID: "object-account", APIName: "Account", Label: "Account", PluralLabel: "Accounts",
	}}}
	runtime, err := NewMetadataAdminReadRuntime(repository)
	if err != nil {
		t.Fatal(err)
	}
	if err := runtime.Prepare(t.Context()); err != nil {
		t.Fatal(err)
	}
	objects, err := runtime.Reads.ListObjects(t.Context())
	if err != nil {
		t.Fatal(err)
	}
	if len(objects) != 1 || objects[0].APIName != "Account" {
		t.Fatalf("prepared admin-read objects = %#v", objects)
	}
}

func TestMetadataAdminReadRuntimeRefreshesObjectsAfterStartup(t *testing.T) {
	repository := &metadataAdminReadRepositoryStub{objects: []*models.ObjectMetadata{{
		ID: "object-account", APIName: "Account", Label: "Account", PluralLabel: "Accounts",
	}}}
	runtime, err := NewMetadataAdminReadRuntime(repository)
	if err != nil {
		t.Fatal(err)
	}
	if err := runtime.Prepare(t.Context()); err != nil {
		t.Fatal(err)
	}
	description := "Current repository description"
	repository.objects = []*models.ObjectMetadata{{
		ID: "object-account", APIName: "Account", Label: "Account", PluralLabel: "Accounts",
		Description: &description,
	}, {
		ID: "object-contact", APIName: "Contact", Label: "Contact", PluralLabel: "Contacts",
	}}
	objects, err := runtime.Reads.ListObjects(t.Context())
	if err != nil {
		t.Fatal(err)
	}
	if len(objects) != 2 || objects[0].Description == nil || *objects[0].Description != description ||
		objects[1].APIName != "Contact" {
		t.Fatalf("refreshed admin-read objects = %#v", objects)
	}
}

func TestMetadataAdminReadRuntimeFailsStartupInsteadOfPublishingEmptyCache(t *testing.T) {
	want := errors.New("metadata unavailable")
	runtime, err := NewMetadataAdminReadRuntime(&metadataAdminReadRepositoryStub{err: want})
	if err != nil {
		t.Fatal(err)
	}
	if err := runtime.Prepare(t.Context()); !errors.Is(err, want) {
		t.Fatalf("Prepare() = %v, want %v", err, want)
	}
}

func TestMetadataAdminReadRuntimeRequiresRepository(t *testing.T) {
	if runtime, err := NewMetadataAdminReadRuntime(nil); err == nil || runtime != nil {
		t.Fatalf("NewMetadataAdminReadRuntime(nil) = %#v, %v", runtime, err)
	}
}

func TestMetadataAdminReadRuntimeReadsValidationRulesStrictlyFromRepository(t *testing.T) {
	repository := &metadataAdminReadRepositoryStub{
		objects: []*models.ObjectMetadata{{
			ID: "object-opportunity", APIName: "Opportunity", Label: "Opportunity", PluralLabel: "Opportunities",
		}},
		rules: []*models.ValidationRule{{
			ID: "rule-a", ObjectAPIName: "Opportunity", Name: "PositiveAmount",
			Condition: "Amount < 0", ErrorMessage: "Amount must be positive",
		}},
	}
	runtime, err := NewMetadataAdminReadRuntime(repository)
	if err != nil {
		t.Fatal(err)
	}
	if err := runtime.Prepare(t.Context()); err != nil {
		t.Fatal(err)
	}
	rules, err := runtime.Reads.ListValidationRules(t.Context(), "Opportunity")
	if err != nil || len(rules) != 1 || rules[0].ID != "rule-a" {
		t.Fatalf("validation rules = %#v, %v", rules, err)
	}

	want := errors.New("validation-rule repository unavailable")
	repository.ruleErr = want
	if _, err := runtime.Reads.ListValidationRules(t.Context(), "Opportunity"); !errors.Is(err, want) {
		t.Fatalf("strict validation-rule failure = %v, want %v", err, want)
	}
}
