package metadata

import (
	"context"
	"strings"
	"testing"

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

func TestPrepareTableDefinitionRegistersPolymorphicDiscriminatorReadOnly(t *testing.T) {
	ms := NewMetadataService(nil, NewSchemaManager(persistence.NewSchemaRepository(nil, nil)), nil)
	schema := &models.ObjectMetadata{
		APIName: "Probe__c",
		Label:   "Probe",
		Fields: []models.FieldMetadata{{
			APIName:       "RelatedTo__c",
			Label:         "Related To",
			Type:          constants.FieldTypeReference,
			ReferenceTo:   []string{"Account", "Opportunity"},
			IsPolymorphic: true,
			Createable:    true,
			Updateable:    true,
		}},
	}

	def, fields, err := ms.PrepareTableDefinition(context.Background(), schema)
	if err != nil {
		t.Fatalf("PrepareTableDefinition: %v", err)
	}
	if !metadataTestColumnExists(def.Columns, "RelatedTo__c_type") {
		t.Fatalf("RelatedTo__c_type column missing: %#v", def.Columns)
	}
	for _, field := range fields {
		if field.Field == nil || field.Field.APIName != "RelatedTo__c_type" {
			continue
		}
		if field.Field.Createable || field.Field.Updateable {
			t.Fatalf("RelatedTo__c_type Createable/Updateable = %v/%v, want false/false", field.Field.Createable, field.Field.Updateable)
		}
		if !field.CapabilitiesDeclared {
			t.Fatal("RelatedTo__c_type CapabilitiesDeclared = false, want true")
		}
		return
	}
	t.Fatal("RelatedTo__c_type field metadata missing")
}

func TestPrepareTableDefinitionLocksExplicitPolymorphicDiscriminatorDeclaration(t *testing.T) {
	ms := NewMetadataService(nil, NewSchemaManager(persistence.NewSchemaRepository(nil, nil)), nil)
	schema := &models.ObjectMetadata{
		APIName: "Probe__c",
		Label:   "Probe",
		Fields: []models.FieldMetadata{
			{
				APIName:       "RelatedTo__c",
				Label:         "Related To",
				Type:          constants.FieldTypeReference,
				ReferenceTo:   []string{"Account", "Opportunity"},
				IsPolymorphic: true,
				Createable:    true,
				Updateable:    true,
			},
			{
				APIName:    "RelatedTo__c_type",
				Label:      "Related To Type",
				Type:       constants.FieldTypeString,
				Createable: true,
				Updateable: true,
			},
		},
	}

	def, fields, err := ms.PrepareTableDefinition(context.Background(), schema)
	if err != nil {
		t.Fatalf("PrepareTableDefinition: %v", err)
	}
	typeCol := metadataTestColumn(t, def.Columns, "RelatedTo__c_type")
	if typeCol.GeneratedForPolymorphicReference != "RelatedTo__c" {
		t.Fatalf("RelatedTo__c_type column GeneratedForPolymorphicReference = %q, want RelatedTo__c", typeCol.GeneratedForPolymorphicReference)
	}
	var matches int
	for _, field := range fields {
		if field.Field == nil || field.Field.APIName != "RelatedTo__c_type" {
			continue
		}
		matches++
		if field.Field.GeneratedForPolymorphicReference != "RelatedTo__c" {
			t.Fatalf("RelatedTo__c_type GeneratedForPolymorphicReference = %q, want RelatedTo__c", field.Field.GeneratedForPolymorphicReference)
		}
		if field.Field.Createable || field.Field.Updateable {
			t.Fatalf("explicit RelatedTo__c_type Createable/Updateable = %v/%v, want false/false", field.Field.Createable, field.Field.Updateable)
		}
		if !field.CapabilitiesDeclared {
			t.Fatal("explicit RelatedTo__c_type CapabilitiesDeclared = false, want true")
		}
	}
	if matches != 1 {
		t.Fatalf("RelatedTo__c_type field metadata rows = %d, want 1", matches)
	}
}

func TestPolymorphicDiscriminatorFieldNamesIncludesExplicitCompanion(t *testing.T) {
	fields := []models.FieldMetadata{
		{
			APIName:       "RelatedTo__c",
			Label:         "Related To",
			Type:          constants.FieldTypeReference,
			ReferenceTo:   []string{"Account", "Opportunity"},
			IsPolymorphic: true,
		},
		{
			APIName: "RelatedTo__c_type",
			Label:   "Related To Type",
			Type:    constants.FieldTypeString,
		},
	}

	got := polymorphicDiscriminatorFieldNames(fields)

	if !got["relatedto__c_type"] {
		t.Fatal("RelatedTo__c_type should be recognized as a generated discriminator companion")
	}
}

func TestUpdateFieldRejectsGeneratedPolymorphicDiscriminator(t *testing.T) {
	ms := NewMetadataService(&metadataDiscriminatorRepo{schema: metadataDiscriminatorSchema()}, nil, nil)

	err := ms.UpdateField(context.Background(), "Probe__c", "RelatedTo__c_type", &models.FieldMetadata{
		Label:      "Writable Type",
		Createable: true,
		Updateable: true,
	})
	if err == nil || !strings.Contains(err.Error(), "generated polymorphic discriminator") {
		t.Fatalf("UpdateField error = %v, want generated discriminator validation error", err)
	}
}

func TestDeleteFieldRejectsGeneratedPolymorphicDiscriminator(t *testing.T) {
	ms := NewMetadataService(&metadataDiscriminatorRepo{schema: metadataDiscriminatorSchema()}, nil, nil)

	err := ms.DeleteField(context.Background(), "Probe__c", "RelatedTo__c_type")
	if err == nil || !strings.Contains(err.Error(), "generated polymorphic discriminator") {
		t.Fatalf("DeleteField error = %v, want generated discriminator validation error", err)
	}
}

func TestDeleteFieldDropsGeneratedPolymorphicDiscriminatorWithParent(t *testing.T) {
	schemaRepo := &metadataDropColumnRepo{}
	ms := NewMetadataService(
		&metadataDiscriminatorRepo{schema: metadataDiscriminatorSchema()},
		NewSchemaManager(schemaRepo),
		nil,
	)

	if err := ms.DeleteField(context.Background(), "Probe__c", "RelatedTo__c"); err != nil {
		t.Fatalf("DeleteField(parent): %v", err)
	}

	got := strings.Join(schemaRepo.dropped, ",")
	if got != "RelatedTo__c,RelatedTo__c_type" {
		t.Fatalf("dropped columns = %q, want parent then generated discriminator", got)
	}
}

type metadataDiscriminatorRepo struct {
	ports.MetadataRepository
	schema *models.ObjectMetadata
}

func (r *metadataDiscriminatorRepo) GetSchemaByAPIName(ctx context.Context, apiName string) (*models.ObjectMetadata, error) {
	return r.schema, nil
}

type metadataDropColumnRepo struct {
	ports.SchemaRepository
	dropped []string
}

func (r *metadataDropColumnRepo) DropColumn(tableName, colName string) error {
	r.dropped = append(r.dropped, colName)
	return nil
}

func metadataDiscriminatorSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		ID:      "obj_probe",
		APIName: "Probe__c",
		Fields: []models.FieldMetadata{
			{
				APIName:       "RelatedTo__c",
				Label:         "Related To",
				Type:          constants.FieldTypeReference,
				ReferenceTo:   []string{"Account", "Opportunity"},
				IsPolymorphic: true,
				Createable:    true,
				Updateable:    true,
			},
			{
				APIName:    "RelatedTo__c_type",
				Label:      "Related To Type",
				Type:       constants.FieldTypeString,
				Createable: true,
				Updateable: true,
			},
		},
	}
}

func metadataTestColumnExists(columns []domainschema.ColumnDefinition, name string) bool {
	_, ok := metadataTestColumnWithOK(columns, name)
	return ok
}

func metadataTestColumn(t *testing.T, columns []domainschema.ColumnDefinition, name string) domainschema.ColumnDefinition {
	t.Helper()
	col, ok := metadataTestColumnWithOK(columns, name)
	if !ok {
		t.Fatalf("column %s not found: %#v", name, columns)
	}
	return col
}

func metadataTestColumnWithOK(columns []domainschema.ColumnDefinition, name string) (domainschema.ColumnDefinition, bool) {
	for _, col := range columns {
		if col.Name == name {
			return col, true
		}
	}
	return domainschema.ColumnDefinition{}, false
}
