package persistence

import (
	"errors"
	"fmt"
	"testing"

	"github.com/go-sql-driver/mysql"
)

func TestExtractDuplicateKeyName(t *testing.T) {
	tests := []struct {
		name     string
		errMsg   string
		expected string
	}{
		{
			name:     "standard duplicate entry error",
			errMsg:   "Duplicate entry 'abc' for key 'Account.unique_name'",
			expected: "unique_name",
		},
		{
			name:     "simple key name without table prefix",
			errMsg:   "Duplicate entry 'abc' for key 'unique_email'",
			expected: "unique_email",
		},
		{
			name:     "no match",
			errMsg:   "some other error",
			expected: "",
		},
		{
			name:     "empty string",
			errMsg:   "",
			expected: "",
		},
		{
			name:     "key with nested dots",
			errMsg:   "Duplicate entry 'x' for key 'schema.table.idx_field'",
			expected: "idx_field",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := extractDuplicateKeyName(tt.errMsg)
			if got != tt.expected {
				t.Errorf("extractDuplicateKeyName(%q) = %q, want %q", tt.errMsg, got, tt.expected)
			}
		})
	}
}

func TestNewDuplicateKeyError(t *testing.T) {
	tests := []struct {
		name      string
		tableName string
		keyName   string
		wantField string
	}{
		{
			name:      "idx prefix",
			tableName: "Account",
			keyName:   "idx_Account_email",
			wantField: "email",
		},
		{
			name:      "uk prefix",
			tableName: "Contact",
			keyName:   "uk_Contact_phone_number",
			wantField: "phone_number",
		},
		{
			name:      "short key name",
			tableName: "Account",
			keyName:   "idx_a",
			wantField: "idx_a", // Not enough parts to extract
		},
		{
			name:      "no prefix",
			tableName: "Account",
			keyName:   "email",
			wantField: "email",
		},
		{
			name:      "idx with multiple parts",
			tableName: "Account",
			keyName:   "idx_Account_first_name",
			wantField: "first_name",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewDuplicateKeyError(tt.tableName, tt.keyName)
			if got == nil {
				t.Fatal("NewDuplicateKeyError() returned nil")
			}
			// ConflictError should contain the field name
			errMsg := got.Error()
			if errMsg == "" {
				t.Error("error message should not be empty")
			}
		})
	}
}

func TestIsDuplicateKeyError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{
			name:     "nil error",
			err:      nil,
			expected: false,
		},
		{
			name:     "mysql duplicate error",
			err:      &mysql.MySQLError{Number: MySQLDuplicateEntryCode, Message: "Duplicate entry"},
			expected: true,
		},
		{
			name:     "mysql other error",
			err:      &mysql.MySQLError{Number: 1064, Message: "Syntax error"},
			expected: false,
		},
		{
			name:     "wrapped error with Duplicate entry message",
			err:      fmt.Errorf("failed: %w", errors.New("Duplicate entry 'abc' for key 'name'")),
			expected: true,
		},
		{
			name:     "wrapped error with Error 1062",
			err:      errors.New("Error 1062: Duplicate entry"),
			expected: true,
		},
		{
			name:     "unrelated error",
			err:      errors.New("connection refused"),
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsDuplicateKeyError(tt.err)
			if got != tt.expected {
				t.Errorf("IsDuplicateKeyError() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestIsUnknownColumnError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{
			name:     "nil error",
			err:      nil,
			expected: false,
		},
		{
			name:     "mysql unknown column error",
			err:      &mysql.MySQLError{Number: MySQLUnknownColumnCode, Message: "Unknown column 'BadField' in 'where clause'"},
			expected: true,
		},
		{
			name:     "mysql other error",
			err:      &mysql.MySQLError{Number: 1064, Message: "Syntax error"},
			expected: false,
		},
		{
			name:     "wrapped error with Unknown column message",
			err:      errors.New("Unknown column 'BadField' in 'field list'"),
			expected: true,
		},
		{
			name:     "wrapped error with Error 1054",
			err:      errors.New("Error 1054: Unknown column"),
			expected: true,
		},
		{
			name:     "unrelated error",
			err:      errors.New("connection refused"),
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsUnknownColumnError(tt.err)
			if got != tt.expected {
				t.Errorf("IsUnknownColumnError() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestExtractUnknownColumnName(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected string
	}{
		{
			name:     "standard unknown column error",
			err:      errors.New("Unknown column 'BadField' in 'where clause'"),
			expected: "BadField",
		},
		{
			name:     "column in field list",
			err:      errors.New("Unknown column 'Account.Region__c' in 'field list'"),
			expected: "Account.Region__c",
		},
		{
			name:     "no match",
			err:      errors.New("some other error"),
			expected: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := extractUnknownColumnName(tt.err)
			if got != tt.expected {
				t.Errorf("extractUnknownColumnName() = %q, want %q", got, tt.expected)
			}
		})
	}
}

func TestIsTableNotExistsError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{
			name:     "nil error",
			err:      nil,
			expected: false,
		},
		{
			name:     "mysql table not exists error",
			err:      &mysql.MySQLError{Number: MySQLTableNotExistsCode, Message: "Table doesn't exist"},
			expected: true,
		},
		{
			name:     "mysql other error",
			err:      &mysql.MySQLError{Number: 1064, Message: "Syntax error"},
			expected: false,
		},
		{
			name:     "wrapped error with doesn't exist message",
			err:      errors.New("Table 'test.foo' doesn't exist"),
			expected: true,
		},
		{
			name:     "wrapped error with Error 1146",
			err:      errors.New("Error 1146: Table doesn't exist"),
			expected: true,
		},
		{
			name:     "unrelated error",
			err:      errors.New("connection refused"),
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := IsTableNotExistsError(tt.err)
			if got != tt.expected {
				t.Errorf("IsTableNotExistsError() = %v, want %v", got, tt.expected)
			}
		})
	}
}
