package errors

import (
	stderrors "errors"
	"fmt"
	"testing"

	sharedErrors "github.com/nexuscrm/shared/pkg/errors"
)

type neutralFieldValidationError struct {
	field   string
	message string
}

func (e neutralFieldValidationError) Error() string             { return e.message }
func (e neutralFieldValidationError) ValidationField() string   { return e.field }
func (e neutralFieldValidationError) ValidationMessage() string { return e.message }

type neutralFieldWriteDeniedError struct {
	fields  []string
	message string
}

func (e neutralFieldWriteDeniedError) Error() string                    { return e.message }
func (e neutralFieldWriteDeniedError) FieldWriteDeniedFields() []string { return e.fields }
func (e neutralFieldWriteDeniedError) FieldWriteDeniedMessage() string  { return e.message }

func TestAsClientValidationErrorPreservesValidationRuleError(t *testing.T) {
	ruleErr := NewValidationRuleError("Rollup_Profile_Gate", "Description is required")

	got, ok := AsClientValidationError(fmt.Errorf("rollup hook failed: %w", ruleErr))
	if !ok {
		t.Fatal("AsClientValidationError ok = false, want true")
	}
	if got != ruleErr {
		t.Fatalf("got %#v, want original validation rule error", got)
	}
}

func TestAsClientValidationErrorPreservesAllValidationRuleFailures(t *testing.T) {
	validationErr := NewValidationRuleFailures([]ValidationRuleFailure{
		{RuleName: "IQM_Existing_Database", Message: "Existing Database is required", Field: "Existing_Database__c"},
		{RuleName: "IQM_Completed_Date", Message: "IQM Completed Date is required", Field: "IQM_Completed_Date__c"},
	})

	got, ok := AsClientValidationError(fmt.Errorf("rollup hook failed: %w", validationErr))
	if !ok {
		t.Fatal("AsClientValidationError ok = false, want true")
	}
	failures, ok := AsValidationRuleFailures(got)
	if !ok || len(failures) != 2 {
		t.Fatalf("AsClientValidationError failures = %#v, want both validation failures", failures)
	}
}

func TestAsClientValidationErrorPreservesValidationError(t *testing.T) {
	validationErr := NewValidationError("Amount", "must be positive")

	got, ok := AsClientValidationError(fmt.Errorf("derived write failed: %w", validationErr))
	if !ok {
		t.Fatal("AsClientValidationError ok = false, want true")
	}
	if got != validationErr {
		t.Fatalf("got %#v, want original validation error", got)
	}
}

func TestAsClientValidationErrorPreservesFLSDeniedError(t *testing.T) {
	flsErr := NewFLSDeniedErrorForFields([]string{"Amount"})

	got, ok := AsClientValidationError(fmt.Errorf("derived write failed: %w", flsErr))
	if !ok {
		t.Fatal("AsClientValidationError ok = false, want true")
	}
	if got != flsErr {
		t.Fatalf("got %#v, want original FLS error", got)
	}
}

func TestInvalidFieldForWriteKeepsItsOwnNeutralClassification(t *testing.T) {
	invalid := NewInvalidFieldForWriteError(
		[]string{"CatalogNamespaceId", "CatalogLocalKey"},
		"fields are owned by Catalog",
	)

	got, ok := AsClientValidationError(fmt.Errorf("record write failed: %w", invalid))
	if !ok || got != invalid {
		t.Fatalf("AsClientValidationError = (%#v, %t), want original invalid-write error", got, ok)
	}
	fields, message, ok := sharedErrors.InvalidFieldForWriteDetails(got)
	if !ok || fmt.Sprint(fields) != "[CatalogNamespaceId CatalogLocalKey]" || message != "validation error on field 'CatalogNamespaceId': fields are owned by Catalog" {
		t.Fatalf("InvalidFieldForWriteDetails = (%v, %q, %t)", fields, message, ok)
	}
	validation, ok := got.(sharedErrors.ValidationFieldConditioner)
	if !ok || validation.ValidationField() != "CatalogNamespaceId" || validation.ValidationMessage() != "fields are owned by Catalog" {
		t.Fatalf("legacy neutral validation contract = (%#v, %t)", validation, ok)
	}
	var legacy *ValidationError
	if !stderrors.As(invalid, &legacy) || legacy.Field != "CatalogNamespaceId" {
		t.Fatalf("legacy ValidationError = %#v, want first affected field", legacy)
	}
}

func TestClientValidationErrorsPreserveMixedFailureOrder(t *testing.T) {
	combined := NewClientValidationErrors(
		NewValidationError("Name", "is required"),
		NewValidationRuleFailures([]ValidationRuleFailure{
			{RuleName: "Rule_B", Message: "B is required", Field: "B__c"},
			{RuleName: "Rule_C", Message: "C is required", Field: "C__c"},
		}),
	)

	got, ok := AsClientValidationError(fmt.Errorf("save rejected: %w", combined))
	if !ok {
		t.Fatal("AsClientValidationError ok = false, want true")
	}
	leaves, ok := AsClientValidationErrors(got)
	if !ok || len(leaves) != 3 {
		t.Fatalf("AsClientValidationErrors = %#v, %t; want three leaves", leaves, ok)
	}
	fieldErr, ok := leaves[0].(*ValidationError)
	if !ok || fieldErr.Field != "Name" {
		t.Fatalf("first leaf = %#v, want Name ValidationError", leaves[0])
	}
	for i, want := range []string{"Rule_B", "Rule_C"} {
		ruleErr, ok := leaves[i+1].(*ValidationRuleError)
		if !ok || ruleErr.RuleName != want {
			t.Fatalf("rule leaf %d = %#v, want %s", i, leaves[i+1], want)
		}
	}
	if failures, ok := AsValidationRuleFailures(got); ok {
		t.Fatalf("AsValidationRuleFailures = %#v, true; mixed aggregate must refuse partial rule-only classification", failures)
	}
}

func TestAsClientValidationErrorsRejectsJoinedValidationAndPlainError(t *testing.T) {
	mixed := stderrors.Join(
		NewValidationError("Name", "is required"),
		stderrors.New("metadata authority unavailable"),
	)
	wrapped := fmt.Errorf("save validation failed: %w", mixed)
	malformedAggregate := &clientValidationErrorsError{Errors: []error{
		NewValidationError("Name", "is required"),
		stderrors.New("metadata authority unavailable"),
	}}

	for _, err := range []error{mixed, wrapped, malformedAggregate} {
		if got, ok := AsClientValidationErrors(err); ok {
			t.Fatalf("AsClientValidationErrors(%T) = %#v, true; want fail-closed false", err, got)
		}
		if got, ok := AsClientValidationError(err); ok {
			t.Fatalf("AsClientValidationError(%T) = %#v, true; want fail-closed false", err, got)
		}
	}
}

func TestAsClientValidationErrorsPreservesEveryJoinedValidationSibling(t *testing.T) {
	joined := stderrors.Join(
		neutralFieldValidationError{field: "Name", message: "Name is required"},
		neutralFieldWriteDeniedError{fields: []string{"Secret__c"}, message: "field is not writable"},
	)

	leaves, ok := AsClientValidationErrors(joined)
	if !ok || len(leaves) != 2 {
		t.Fatalf("AsClientValidationErrors = %#v, %t; want two neutral leaves", leaves, ok)
	}
	if _, ok := leaves[0].(neutralFieldValidationError); !ok {
		t.Fatalf("first leaf = %T, want neutral field validation", leaves[0])
	}
	if _, ok := leaves[1].(neutralFieldWriteDeniedError); !ok {
		t.Fatalf("second leaf = %T, want neutral field denial", leaves[1])
	}
}

func TestAsClientValidationErrorRefusesInternalWrapper(t *testing.T) {
	ruleErr := NewValidationRuleError("Rollup_Profile_Gate", "Description is required")
	internal := NewInternalError("rollback failed", ruleErr)

	if got, ok := AsClientValidationError(internal); ok {
		t.Fatalf("AsClientValidationError = (%#v, true), want false for InternalError wrapper", got)
	}
}

func TestAsClientValidationErrorIgnoresPlainErrors(t *testing.T) {
	if got, ok := AsClientValidationError(stderrors.New("network closed")); ok {
		t.Fatalf("AsClientValidationError = (%#v, true), want false for plain error", got)
	}
}
