package metadata

import (
	"context"
	"strings"
	"testing"

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

func TestMetadataServiceObjectIdentityLookupsUseSchemaSnapshot(t *testing.T) {
	ctx := context.Background()
	service := NewMetadataService(&metadataLookupRepo{
		schemas: []*models.ObjectMetadata{
			{APIName: "Account", KeyPrefix: identityTestStringPtr("001")},
			{APIName: "OpportunityContactRole", KeyPrefix: identityTestStringPtr("00K")},
			{APIName: "OpportunityLineItem", KeyPrefix: identityTestStringPtr("00k")},
			{APIName: "NoPrefix"},
		},
	}, nil, nil)

	if got, ok := service.ResolveObjectForKeyPrefix(ctx, "001"); !ok || got != "Account" {
		t.Fatalf("ResolveObjectForKeyPrefix(001) = (%q, %v), want (Account, true)", got, ok)
	}
	if got, ok := service.ResolveObjectForKeyPrefix(ctx, "00K"); !ok || got != "OpportunityContactRole" {
		t.Fatalf("ResolveObjectForKeyPrefix(00K) = (%q, %v), want (OpportunityContactRole, true)", got, ok)
	}
	if got, ok := service.ResolveObjectForKeyPrefix(ctx, "00k"); !ok || got != "OpportunityLineItem" {
		t.Fatalf("ResolveObjectForKeyPrefix(00k) = (%q, %v), want (OpportunityLineItem, true)", got, ok)
	}
	if got, ok := service.ResolveObjectForRecordID(ctx, "001000000000001AAA"); !ok || got != "Account" {
		t.Fatalf("ResolveObjectForRecordID(001...) = (%q, %v), want (Account, true)", got, ok)
	}
	if got, ok := service.ResolveObjectForRecordID(ctx, "00100000-0000-4000-8000-000000000000"); ok || got != "" {
		t.Fatalf("ResolveObjectForRecordID(UUID with known prefix) = (%q, %v), want opaque/unresolved", got, ok)
	}
	if got, ok := service.KeyPrefixForObject(ctx, "account"); !ok || got != "001" {
		t.Fatalf("KeyPrefixForObject(account) = (%q, %v), want (001, true)", got, ok)
	}
	if got, ok := service.CanonicalObjectAPIName(ctx, "opportunitylineitem"); !ok || got != "OpportunityLineItem" {
		t.Fatalf("CanonicalObjectAPIName(opportunitylineitem) = (%q, %v), want (OpportunityLineItem, true)", got, ok)
	}
	if _, ok := service.KeyPrefixForObject(ctx, "NoPrefix"); ok {
		t.Fatal("KeyPrefixForObject(NoPrefix) resolved without KeyPrefix metadata")
	}
	if _, ok := service.ResolveObjectForKeyPrefix(ctx, "00"); ok {
		t.Fatal("ResolveObjectForKeyPrefix(00) resolved an unknown prefix")
	}
}

func TestMetadataServiceObjectIdentityLookupRejectsDuplicateKeyPrefixes(t *testing.T) {
	ctx := context.Background()
	service := NewMetadataService(&metadataLookupRepo{
		schemas: []*models.ObjectMetadata{
			{APIName: "Account", KeyPrefix: identityTestStringPtr("001")},
			{APIName: "DuplicateAccount", KeyPrefix: identityTestStringPtr("001")},
		},
	}, nil, nil)

	err := service.RefreshCache(ctx)
	if err == nil || !strings.Contains(err.Error(), "duplicate key prefix 001") {
		t.Fatalf("RefreshCache duplicate key prefix error = %v, want duplicate key prefix failure", err)
	}
}

func TestMetadataServiceRelationshipLookupsUseEffectiveRelationshipSnapshot(t *testing.T) {
	ctx := context.Background()
	service := NewMetadataService(&metadataLookupRepo{
		schemas: []*models.ObjectMetadata{
			{
				APIName: "Contact",
				Fields: []models.FieldMetadata{
					{
						APIName:     "AccountId",
						Type:        constants.FieldTypeReference,
						ReferenceTo: []string{"Account"},
					},
					{
						APIName:          "WhatId",
						Type:             constants.FieldTypeReference,
						RelationshipName: identityTestStringPtr("What"),
						ReferenceTo:      []string{"Account", "Opportunity"},
					},
				},
			},
			{APIName: "Account"},
			{APIName: "Opportunity"},
		},
		relationships: []*models.Relationship{
			{
				ChildObjectAPIName:  "Contact",
				ParentObjectAPIName: "Account",
				FieldAPIName:        "AccountId",
				RelationshipName:    "Customer",
			},
		},
	}, nil, nil)

	if got, ok := service.ForeignKeyForRelationship(ctx, "Contact", "customer"); !ok || got != "AccountId" {
		t.Fatalf("ForeignKeyForRelationship(Contact, customer) = (%q, %v), want (AccountId, true)", got, ok)
	}
	if _, ok := service.ForeignKeyForRelationship(ctx, "Contact", "Account"); ok {
		t.Fatal("ForeignKeyForRelationship(Contact, Account) resolved pre-overlay relationship name")
	}
	targets, ok := service.TargetObjectsForForeignKey(ctx, "Contact", "WhatId")
	if !ok {
		t.Fatal("TargetObjectsForForeignKey(Contact, WhatId) = false, want true")
	}
	if got := strings.Join(targets, ","); got != "Account,Opportunity" {
		t.Fatalf("TargetObjectsForForeignKey(Contact, WhatId) = %q, want Account,Opportunity", got)
	}
	children := service.GetChildRelationships(ctx, "Account")
	if len(children) != 1 || children[0].APIName != "Contact" {
		t.Fatalf("GetChildRelationships(Account) = %#v, want [Contact]", children)
	}
}

type metadataLookupRepo struct {
	ports.MetadataRepository
	schemas       []*models.ObjectMetadata
	relationships []*models.Relationship
}

func (r *metadataLookupRepo) GetAllSchemas(context.Context) ([]*models.ObjectMetadata, error) {
	return r.schemas, nil
}

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

func identityTestStringPtr(v string) *string {
	return &v
}
