package errors

import (
	"errors"
	"fmt"
	"testing"
)

type unauthenticatedTestError struct{ enabled bool }

func (e unauthenticatedTestError) Error() string                  { return "unauthenticated" }
func (e unauthenticatedTestError) UnauthenticatedCondition() bool { return e.enabled }

type permissionDeniedTestError struct{ enabled bool }

func (e permissionDeniedTestError) Error() string                   { return "permission denied" }
func (e permissionDeniedTestError) PermissionDeniedCondition() bool { return e.enabled }

type conflictTestError struct{ enabled bool }

func (e conflictTestError) Error() string           { return "conflict" }
func (e conflictTestError) ConflictCondition() bool { return e.enabled }

type infrastructureTestError struct {
	internal  bool
	retriable bool
	cause     error
}

func (e infrastructureTestError) Error() string            { return "infrastructure" }
func (e infrastructureTestError) Unwrap() error            { return e.cause }
func (e infrastructureTestError) InternalCondition() bool  { return e.internal }
func (e infrastructureTestError) RetriableCondition() bool { return e.retriable }

func TestInfrastructureClassifierPreservesOwningWrapper(t *testing.T) {
	retriable := infrastructureTestError{internal: true, retriable: true}
	if got := ClassifyInfrastructure(fmt.Errorf("transparent: %w", retriable)); got != InfrastructureConditionRetriable {
		t.Fatalf("dual-marked retriable condition = %v, want retriable", got)
	}

	internal := infrastructureTestError{internal: true, cause: retriable}
	if got := ClassifyInfrastructure(fmt.Errorf("transparent: %w", internal)); got != InfrastructureConditionInternal {
		t.Fatalf("outer internal condition = %v, want internal", got)
	}

	joined := errors.Join(retriable, infrastructureTestError{internal: true})
	if got := ClassifyInfrastructure(joined); got != InfrastructureConditionInternal {
		t.Fatalf("ambiguous joined condition = %v, want fail-closed internal", got)
	}
}

func TestNeutralSecurityAndConflictClassifiersFollowWrappedConditions(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		classify func(error) bool
	}{
		{name: "unauthenticated", err: unauthenticatedTestError{enabled: true}, classify: IsUnauthenticated},
		{name: "permission denied", err: permissionDeniedTestError{enabled: true}, classify: IsPermissionDenied},
		{name: "conflict", err: conflictTestError{enabled: true}, classify: IsConflict},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if !test.classify(fmt.Errorf("wrapped: %w", test.err)) {
				t.Fatal("wrapped condition was not classified")
			}
		})
	}
}

func TestNeutralSecurityAndConflictClassifiersFailClosed(t *testing.T) {
	if IsUnauthenticated(unauthenticatedTestError{}) ||
		IsPermissionDenied(permissionDeniedTestError{}) ||
		IsConflict(conflictTestError{}) ||
		IsUnauthenticated(errors.New("opaque")) ||
		IsPermissionDenied(errors.New("opaque")) ||
		IsConflict(errors.New("opaque")) {
		t.Fatal("classifier accepted an absent or false condition")
	}
}
