package persistence

import (
	"errors"
	"fmt"
	"reflect"
	"testing"

	"github.com/go-sql-driver/mysql"
	apperrors "github.com/nexuscrm/kernel/pkg/errors"
)

// --- F1: ExtractDuplicateKeyName ---

func TestExtractDuplicateKeyName_BareIndexName(t *testing.T) {
	msg := "Error 1062: Duplicate entry 'x' for key 'idx_Flow_DefinitionId_VersionNumber'"
	got := ExtractDuplicateKeyName(msg)
	want := "idx_Flow_DefinitionId_VersionNumber"
	if got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func TestExtractDuplicateKeyName_TablePrefixedIndexName(t *testing.T) {
	msg := "Error 1062: Duplicate entry 'x' for key 'Flow.idx_Flow_DefinitionId_VersionNumber'"
	got := ExtractDuplicateKeyName(msg)
	want := "idx_Flow_DefinitionId_VersionNumber"
	if got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func TestExtractDuplicateKeyName_PrimaryKey(t *testing.T) {
	msg := "Error 1062: Duplicate entry 'x' for key 'PRIMARY'"
	got := ExtractDuplicateKeyName(msg)
	want := "PRIMARY"
	if got != want {
		t.Errorf("got %q, want %q", got, want)
	}
}

func TestExtractDuplicateKeyName_NoMatchReturnsEmpty(t *testing.T) {
	for _, msg := range []string{
		"",
		"Duplicate entry 'x'",
		"random text",
		"Error 1062: Duplicate entry 'x' for key ''", // empty key name
	} {
		got := ExtractDuplicateKeyName(msg)
		if got != "" {
			t.Errorf("ExtractDuplicateKeyName(%q) = %q, want empty", msg, got)
		}
	}
}

// --- F2: IsDuplicateKeyError ---

func TestIsDuplicateKeyError_TypedMySQLError(t *testing.T) {
	// Real *mysql.MySQLError with Number=1062 → true
	err1062 := &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'x' for key 'idx_X'"}
	if !IsDuplicateKeyError(err1062) {
		t.Fatal("IsDuplicateKeyError(MySQLError{1062}) = false, want true")
	}
	// Different code → false
	err1146 := &mysql.MySQLError{Number: 1146, Message: "Table doesn't exist"}
	if IsDuplicateKeyError(err1146) {
		t.Fatal("IsDuplicateKeyError(MySQLError{1146}) = true, want false")
	}
	// 1062 with empty message → true (Number check fires first)
	err1062Empty := &mysql.MySQLError{Number: 1062, Message: ""}
	if !IsDuplicateKeyError(err1062Empty) {
		t.Fatal("IsDuplicateKeyError(MySQLError{1062, empty msg}) = false, want true")
	}
}

func TestIsDuplicateKeyError_StringFallback_MatchesWrappedDriverError(t *testing.T) {
	// Wrapped *mysql.MySQLError: the .Error() method formats as "Error 1062: ..."
	// and strings.Contains("Error 1062: ...", "Duplicate entry") → true.
	wrapped := fmt.Errorf("create flow: %w", &mysql.MySQLError{
		Number:  1062,
		Message: "Duplicate entry 'x' for key 'idx_X'",
	})
	if !IsDuplicateKeyError(wrapped) {
		t.Fatal("IsDuplicateKeyError(wrapped 1062) = false, want true")
	}
	// Plain string with "Duplicate entry" → true
	plain := errors.New("Error 1062: Duplicate entry 'x'")
	if !IsDuplicateKeyError(plain) {
		t.Fatal("IsDuplicateKeyError(plain 1062 string) = false, want true")
	}
	// Random error → false
	random := errors.New("random failure")
	if IsDuplicateKeyError(random) {
		t.Fatal("IsDuplicateKeyError(random) = true, want false")
	}
	// nil → false
	if IsDuplicateKeyError(nil) {
		t.Fatal("IsDuplicateKeyError(nil) = true, want false")
	}
}

// --- F4: NewDuplicateKeyError ---

func TestNewDuplicateKeyError_FieldNameExtraction(t *testing.T) {
	for _, tc := range []struct {
		resourceName string
		keyName      string
		wantField    string
	}{
		{"Flow", "idx_Flow_DefinitionId_VersionNumber", "DefinitionId_VersionNumber"},
		{"X", "uk_A_B", "B"},
		{"X", "plain_name", "plain_name"},
		{"X", "idx_Z", "idx_Z"},                    // len(parts) == 2, no strip
		{"Flow", "DeveloperName", "DeveloperName"}, // no prefix
	} {
		got := NewDuplicateKeyError(tc.resourceName, tc.keyName)
		if got == nil {
			t.Fatalf("NewDuplicateKeyError(%q, %q) = nil", tc.resourceName, tc.keyName)
		}
		if got.Field != tc.wantField {
			t.Errorf("NewDuplicateKeyError(%q, %q).Field = %q, want %q",
				tc.resourceName, tc.keyName, got.Field, tc.wantField)
		}
		if got.Resource != tc.resourceName {
			t.Errorf("NewDuplicateKeyError(%q, %q).Resource = %q, want %q",
				tc.resourceName, tc.keyName, got.Resource, tc.resourceName)
		}
		want := apperrors.NewConflictError(tc.resourceName, tc.wantField, "")
		if !reflect.DeepEqual(got, want) {
			t.Errorf("NewDuplicateKeyError(%q, %q) = %+v, want %+v",
				tc.resourceName, tc.keyName, got, want)
		}
	}
}
