package dbmarshal

import (
	"encoding/json"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Test structures mimicking the TiDBCluster structure
type TestCluster struct {
	TestbedID         int64  `json:"-" dbstore:"testbedID"`
	TestbedName       string `json:"-" dbstore:"testbedName"`
	TestbedPlanExecID int64  `json:"-" dbstore:"testbedPlanExecID"`

	TiWorkloadClusterID string `json:"-" dbstore:"tiWorkloadClusterID"`

	TiDBCloudID   int64  `json:"tidbcloudID" dbstore:"tidbcloudID"`
	TiDBCloudOrg  string `json:"tidbcloudOrg" dbstore:"tidbcloudOrg"`
	TiDBCloudProj string `json:"tidbcloudProj" dbstore:"tidbcloudProj"`
}

type TestStruct struct {
	PublicField    string `json:"publicField"`
	PrivateField   string `json:"-" dbstore:"privateField"`
	RegularField   string
	IgnoredField   string       `json:"-"`
	PointerField   *TestCluster `dbstore:"pointerField"`
	EmbeddedStruct TestCluster  `dbstore:"embeddedStruct"`
}

// Test struct for time.Time fields
type TestTimeStruct struct {
	ID          int64      `json:"id" dbstore:"id"`
	CreatedAt   time.Time  `json:"createdAt" dbstore:"createdAt"`
	UpdatedAt   *time.Time `json:"updatedAt" dbstore:"updatedAt"`
	ScheduledAt time.Time  `json:"-" dbstore:"scheduledAt"`
	DeletedAt   *time.Time `json:"-" dbstore:"deletedAt"`
	PublicTime  time.Time  `json:"publicTime"`
}

// Test struct for ignore dbstore features
type TestDBStoreFeatures struct {
	ID             int64  `json:"id" dbstore:"id"`
	Name           string `json:"name" dbstore:"name"`
	IgnoredField   string `json:"-" dbstore:"-"` // Should be ignored in both JSON and dbstore
	NoTagField     string // Should use exact field name "NoTagField"
	JSONOnlyField  string `json:"jsonOnlyField"`              // Should use json tag since no dbstore tag
	BothTagsIgnore string `json:"jsonField" dbstore:"-"`      // Should be ignored in dbstore, but present in JSON
	PrivateField   string `json:"-" dbstore:"privateDBField"` // Should be included with dbstore name, ignored in JSON
}

func TestMarshal_BasicStruct(t *testing.T) {
	cluster := TestCluster{
		TestbedID:           123,
		TestbedName:         "test-testbed",
		TestbedPlanExecID:   456,
		TiWorkloadClusterID: "workload-123",
		TiDBCloudID:         789,
		TiDBCloudOrg:        "test-org",
		TiDBCloudProj:       "test-project",
	}

	// Test our custom marshaling
	dbBytes, err := Marshal(cluster)
	require.NoError(t, err)

	// Test standard JSON marshaling for comparison
	jsonBytes, err := json.Marshal(cluster)
	require.NoError(t, err)

	// Parse both results
	var dbResult map[string]interface{}
	var jsonResult map[string]interface{}

	err = json.Unmarshal(dbBytes, &dbResult)
	require.NoError(t, err)

	err = json.Unmarshal(jsonBytes, &jsonResult)
	require.NoError(t, err)

	// DB marshaling should include fields with dbstore tags (even with json:"-")
	assert.Equal(t, float64(123), dbResult["testbedID"])
	assert.Equal(t, "test-testbed", dbResult["testbedName"])
	assert.Equal(t, float64(456), dbResult["testbedPlanExecID"])
	assert.Equal(t, "workload-123", dbResult["tiWorkloadClusterID"])
	assert.Equal(t, float64(789), dbResult["tidbcloudID"])
	assert.Equal(t, "test-org", dbResult["tidbcloudOrg"])
	assert.Equal(t, "test-project", dbResult["tidbcloudProj"])

	// JSON marshaling should exclude fields with json:"-"
	_, hasTestbedID := jsonResult["testbedID"]
	assert.False(t, hasTestbedID, "JSON should not include testbedID due to json:\"-\" tag")

	_, hasTestbedName := jsonResult["testbedName"]
	assert.False(t, hasTestbedName, "JSON should not include testbedName due to json:\"-\" tag")

	// But should include fields with explicit json tags
	assert.Equal(t, float64(789), jsonResult["tidbcloudID"])
	assert.Equal(t, "test-org", jsonResult["tidbcloudOrg"])
	assert.Equal(t, "test-project", jsonResult["tidbcloudProj"])
}

func TestUnmarshal_BasicStruct(t *testing.T) {
	// Create JSON data that includes fields with dbstore tag names
	jsonData := `{
		"testbedID": 123,
		"testbedName": "test-testbed",
		"testbedPlanExecID": 456,
		"tiWorkloadClusterID": "workload-123",
		"tidbcloudID": 789,
		"tidbcloudOrg": "test-org",
		"tidbcloudProj": "test-project"
	}`

	var cluster TestCluster
	err := Unmarshal([]byte(jsonData), &cluster)
	require.NoError(t, err)

	// Verify all fields are correctly set
	assert.Equal(t, int64(123), cluster.TestbedID)
	assert.Equal(t, "test-testbed", cluster.TestbedName)
	assert.Equal(t, int64(456), cluster.TestbedPlanExecID)
	assert.Equal(t, "workload-123", cluster.TiWorkloadClusterID)
	assert.Equal(t, int64(789), cluster.TiDBCloudID)
	assert.Equal(t, "test-org", cluster.TiDBCloudOrg)
	assert.Equal(t, "test-project", cluster.TiDBCloudProj)
}

func TestMarshalUnmarshal_Roundtrip(t *testing.T) {
	original := TestCluster{
		TestbedID:           123,
		TestbedName:         "test-testbed",
		TestbedPlanExecID:   456,
		TiWorkloadClusterID: "workload-123",
		TiDBCloudID:         789,
		TiDBCloudOrg:        "test-org",
		TiDBCloudProj:       "test-project",
	}

	// Marshal
	data, err := Marshal(original)
	require.NoError(t, err)

	// Unmarshal
	var result TestCluster
	err = Unmarshal(data, &result)
	require.NoError(t, err)

	// Compare
	assert.Equal(t, original, result)
}

func TestMarshal_WithPointers(t *testing.T) {
	cluster := &TestCluster{
		TestbedID:   123,
		TestbedName: "test-testbed",
	}

	testStruct := TestStruct{
		PublicField:  "public",
		PrivateField: "private",
		RegularField: "regular",
		IgnoredField: "ignored",
		PointerField: cluster,
	}

	data, err := Marshal(testStruct)
	require.NoError(t, err)

	var result map[string]interface{}
	err = json.Unmarshal(data, &result)
	require.NoError(t, err)

	// Check main fields
	assert.Equal(t, "public", result["publicField"])
	assert.Equal(t, "private", result["privateField"])
	assert.Equal(t, "regular", result["RegularField"])

	// Ignored field should not be present
	_, hasIgnored := result["ignoredField"]
	assert.False(t, hasIgnored)

	// Check nested pointer struct
	pointerField, ok := result["pointerField"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, float64(123), pointerField["testbedID"])
	assert.Equal(t, "test-testbed", pointerField["testbedName"])
}

func TestUnmarshal_WithPointers(t *testing.T) {
	jsonData := `{
		"publicField": "public",
		"privateField": "private",
		"RegularField": "regular",
		"pointerField": {
			"testbedID": 123,
			"testbedName": "test-testbed"
		}
	}`

	var testStruct TestStruct
	err := Unmarshal([]byte(jsonData), &testStruct)
	require.NoError(t, err)

	assert.Equal(t, "public", testStruct.PublicField)
	assert.Equal(t, "private", testStruct.PrivateField)
	assert.Equal(t, "regular", testStruct.RegularField)

	require.NotNil(t, testStruct.PointerField)
	assert.Equal(t, int64(123), testStruct.PointerField.TestbedID)
	assert.Equal(t, "test-testbed", testStruct.PointerField.TestbedName)
}

func TestMarshal_EmbeddedStruct(t *testing.T) {
	testStruct := TestStruct{
		PublicField: "public",
		EmbeddedStruct: TestCluster{
			TestbedID:   456,
			TestbedName: "embedded-testbed",
		},
	}

	data, err := Marshal(testStruct)
	require.NoError(t, err)

	var result map[string]interface{}
	err = json.Unmarshal(data, &result)
	require.NoError(t, err)

	// Check embedded struct
	embeddedStruct, ok := result["embeddedStruct"].(map[string]interface{})
	require.True(t, ok)
	assert.Equal(t, float64(456), embeddedStruct["testbedID"])
	assert.Equal(t, "embedded-testbed", embeddedStruct["testbedName"])
}

func TestMarshal_NilPointer(t *testing.T) {
	testStruct := TestStruct{
		PublicField:  "public",
		PointerField: nil,
	}

	data, err := Marshal(testStruct)
	require.NoError(t, err)

	var result map[string]interface{}
	err = json.Unmarshal(data, &result)
	require.NoError(t, err)

	// Nil pointer should result in nil value
	assert.Nil(t, result["pointerField"])
}

func TestUnmarshal_NilOrMissingFields(t *testing.T) {
	jsonData := `{
		"publicField": "public"
	}`

	var testStruct TestStruct
	err := Unmarshal([]byte(jsonData), &testStruct)
	require.NoError(t, err)

	assert.Equal(t, "public", testStruct.PublicField)
	assert.Equal(t, "", testStruct.PrivateField) // Should be zero value
	assert.Nil(t, testStruct.PointerField)       // Should remain nil
}

func TestMarshal_NilValue(t *testing.T) {
	data, err := Marshal(nil)
	require.NoError(t, err)
	assert.Equal(t, "null", string(data))
}

func TestUnmarshal_NullValue(t *testing.T) {
	var cluster TestCluster
	err := Unmarshal([]byte("null"), &cluster)
	require.NoError(t, err)
	// Should remain zero value
	assert.Equal(t, TestCluster{}, cluster)
}

func TestUnmarshal_EmptyData(t *testing.T) {
	var cluster TestCluster
	err := Unmarshal([]byte(""), &cluster)
	require.NoError(t, err)
	// Should remain zero value
	assert.Equal(t, TestCluster{}, cluster)
}

func TestGetFieldName(t *testing.T) {
	tests := []struct {
		name     string
		tag      string
		expected string
	}{
		{
			name:     "dbstore tag only",
			tag:      `dbstore:"testbedID"`,
			expected: "testbedID",
		},
		{
			name:     "json tag ignored when dbstore present",
			tag:      `json:"jsonName" dbstore:"dbstoreName"`,
			expected: "dbstoreName",
		},
		{
			name:     "json tag used when no dbstore",
			tag:      `json:"jsonName"`,
			expected: "jsonName",
		},
		{
			name:     "json dash ignored",
			tag:      `json:"-"`,
			expected: "",
		},
		{
			name:     "dbstore with comma options",
			tag:      `dbstore:"fieldName,omitempty"`,
			expected: "fieldName",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create a dummy struct field for testing
			field := getFieldNameFromTags(tt.tag)
			assert.Equal(t, tt.expected, field)
		})
	}
}

// Helper function to test tag parsing without reflection
func getFieldNameFromTags(tagString string) string {
	// This mimics the logic in getFieldName but works with tag strings
	if dbstoreTag := extractTag(tagString, "dbstore"); dbstoreTag != "" {
		if dbstoreTag == "-" {
			return ""
		}
		return dbstoreTag
	}

	if jsonTag := extractTag(tagString, "json"); jsonTag != "" && jsonTag != "-" {
		return jsonTag
	}

	return ""
}

// Helper to extract tag values from tag strings
func extractTag(tagString, tagName string) string {
	// Simple tag extraction for testing
	// In real code, this is handled by reflect.StructField.Tag.Get()
	if tagString == "" {
		return ""
	}

	// Find the tag
	start := tagName + `:"`
	startIdx := strings.Index(tagString, start)
	if startIdx == -1 {
		return ""
	}

	startIdx += len(start)
	endIdx := strings.Index(tagString[startIdx:], `"`)
	if endIdx == -1 {
		return ""
	}

	tagValue := tagString[startIdx : startIdx+endIdx]
	// Return first part before comma
	if commaIdx := strings.Index(tagValue, ","); commaIdx != -1 {
		return tagValue[:commaIdx]
	}

	return tagValue
}

func TestMarshal_TimeFields(t *testing.T) {
	now := time.Date(2023, 12, 25, 15, 30, 45, 123456789, time.UTC)
	future := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)

	testStruct := TestTimeStruct{
		ID:          123,
		CreatedAt:   now,
		UpdatedAt:   &future,
		ScheduledAt: now.Add(24 * time.Hour),
		DeletedAt:   nil,
		PublicTime:  now,
	}

	// Test our custom marshaling
	dbBytes, err := Marshal(testStruct)
	require.NoError(t, err)

	// Test standard JSON marshaling for comparison
	jsonBytes, err := json.Marshal(testStruct)
	require.NoError(t, err)

	// Parse both results
	var dbResult map[string]interface{}
	var jsonResult map[string]interface{}

	err = json.Unmarshal(dbBytes, &dbResult)
	require.NoError(t, err)

	err = json.Unmarshal(jsonBytes, &jsonResult)
	require.NoError(t, err)

	// DB marshaling should include fields with dbstore tags (even with json:"-")
	assert.Equal(t, float64(123), dbResult["id"])
	assert.Contains(t, dbResult, "createdAt")
	assert.Contains(t, dbResult, "updatedAt")
	assert.Contains(t, dbResult, "scheduledAt") // Should be included due to dbstore tag
	assert.Contains(t, dbResult, "deletedAt")   // Should be included (nil value)
	assert.Contains(t, dbResult, "publicTime")

	// Verify time formats are preserved
	createdAtStr, ok := dbResult["createdAt"].(string)
	require.True(t, ok)
	parsedTime, err := time.Parse(time.RFC3339Nano, createdAtStr)
	require.NoError(t, err)
	assert.True(t, now.Equal(parsedTime))

	// JSON should exclude fields with json:"-"
	_, hasScheduledAt := jsonResult["scheduledAt"]
	assert.False(t, hasScheduledAt, "JSON should not include scheduledAt due to json:\"-\" tag")

	_, hasDeletedAt := jsonResult["deletedAt"]
	assert.False(t, hasDeletedAt, "JSON should not include deletedAt due to json:\"-\" tag")

	// But should include other time fields
	assert.Contains(t, jsonResult, "createdAt")
	assert.Contains(t, jsonResult, "updatedAt")
	assert.Contains(t, jsonResult, "publicTime")
}

func TestUnmarshal_TimeFields(t *testing.T) {
	// Create JSON data with time fields
	jsonData := `{
		"id": 123,
		"createdAt": "2023-12-25T15:30:45.123456789Z",
		"updatedAt": "2024-01-01T00:00:00Z",
		"scheduledAt": "2023-12-26T15:30:45.123456789Z",
		"deletedAt": null
	}`

	var testStruct TestTimeStruct
	err := Unmarshal([]byte(jsonData), &testStruct)
	require.NoError(t, err)

	// Verify all fields are correctly set
	assert.Equal(t, int64(123), testStruct.ID)

	expectedCreated := time.Date(2023, 12, 25, 15, 30, 45, 123456789, time.UTC)
	assert.True(t, expectedCreated.Equal(testStruct.CreatedAt))

	expectedUpdated := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
	require.NotNil(t, testStruct.UpdatedAt)
	assert.True(t, expectedUpdated.Equal(*testStruct.UpdatedAt))

	expectedScheduled := time.Date(2023, 12, 26, 15, 30, 45, 123456789, time.UTC)
	assert.True(t, expectedScheduled.Equal(testStruct.ScheduledAt))

	assert.Nil(t, testStruct.DeletedAt)
}

func TestMarshal_TimeZeroValues(t *testing.T) {
	testStruct := TestTimeStruct{
		ID:          456,
		CreatedAt:   time.Time{}, // Zero time
		UpdatedAt:   nil,
		ScheduledAt: time.Time{},
		DeletedAt:   nil,
	}

	data, err := Marshal(testStruct)
	require.NoError(t, err)

	var result map[string]interface{}
	err = json.Unmarshal(data, &result)
	require.NoError(t, err)

	// Zero time should be serialized as "0001-01-01T00:00:00Z"
	createdAtStr, ok := result["createdAt"].(string)
	require.True(t, ok)
	assert.Equal(t, "0001-01-01T00:00:00Z", createdAtStr)

	// Nil pointer should be null
	assert.Nil(t, result["updatedAt"])
	assert.Nil(t, result["deletedAt"])
}

func TestUnmarshal_TimeZeroAndNull(t *testing.T) {
	jsonData := `{
		"id": 456,
		"createdAt": "0001-01-01T00:00:00Z",
		"updatedAt": null,
		"scheduledAt": "0001-01-01T00:00:00Z",
		"deletedAt": null
	}`

	var testStruct TestTimeStruct
	err := Unmarshal([]byte(jsonData), &testStruct)
	require.NoError(t, err)

	assert.Equal(t, int64(456), testStruct.ID)
	assert.True(t, testStruct.CreatedAt.IsZero())
	assert.Nil(t, testStruct.UpdatedAt)
	assert.True(t, testStruct.ScheduledAt.IsZero())
	assert.Nil(t, testStruct.DeletedAt)
}

func TestMarshalUnmarshal_TimeRoundtrip(t *testing.T) {
	now := time.Now().UTC()
	future := now.Add(24 * time.Hour)

	original := TestTimeStruct{
		ID:          789,
		CreatedAt:   now,
		UpdatedAt:   &future,
		ScheduledAt: now.Add(48 * time.Hour),
		DeletedAt:   &now,
		PublicTime:  now,
	}

	// Marshal
	data, err := Marshal(original)
	require.NoError(t, err)

	// Unmarshal
	var result TestTimeStruct
	err = Unmarshal(data, &result)
	require.NoError(t, err)

	// Compare (times should be equal)
	assert.Equal(t, original.ID, result.ID)
	assert.True(t, original.CreatedAt.Equal(result.CreatedAt))

	require.NotNil(t, result.UpdatedAt)
	assert.True(t, original.UpdatedAt.Equal(*result.UpdatedAt))

	assert.True(t, original.ScheduledAt.Equal(result.ScheduledAt))

	require.NotNil(t, result.DeletedAt)
	assert.True(t, original.DeletedAt.Equal(*result.DeletedAt))

	assert.True(t, original.PublicTime.Equal(result.PublicTime))
}

func TestMarshal_TimeWithDifferentPrecision(t *testing.T) {
	// Test with microsecond precision
	microTime := time.Date(2023, 12, 25, 15, 30, 45, 123456000, time.UTC)

	// Test with nanosecond precision
	nanoTime := time.Date(2023, 12, 25, 15, 30, 45, 123456789, time.UTC)

	testStruct := TestTimeStruct{
		ID:         1,
		CreatedAt:  microTime,
		PublicTime: nanoTime,
	}

	data, err := Marshal(testStruct)
	require.NoError(t, err)

	var result map[string]interface{}
	err = json.Unmarshal(data, &result)
	require.NoError(t, err)

	// Verify precision is maintained in string format
	createdAtStr, ok := result["createdAt"].(string)
	require.True(t, ok, "createdAt should be a string")

	publicTimeStr, ok := result["publicTime"].(string)
	require.True(t, ok, "publicTime should be a string")

	// Parse back and verify
	parsedMicro, err := time.Parse(time.RFC3339Nano, createdAtStr)
	require.NoError(t, err)
	assert.True(t, microTime.Equal(parsedMicro))

	parsedNano, err := time.Parse(time.RFC3339Nano, publicTimeStr)
	require.NoError(t, err)
	assert.True(t, nanoTime.Equal(parsedNano))
}

func BenchmarkMarshal_WithTime(b *testing.B) {
	now := time.Now()
	future := now.Add(24 * time.Hour)

	testStruct := TestTimeStruct{
		ID:          123,
		CreatedAt:   now,
		UpdatedAt:   &future,
		ScheduledAt: now.Add(48 * time.Hour),
		DeletedAt:   &now,
		PublicTime:  now,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = Marshal(testStruct)
	}
}

func BenchmarkUnmarshal_WithTime(b *testing.B) {
	data := []byte(`{
		"id": 123,
		"createdAt": "2023-12-25T15:30:45.123456789Z",
		"updatedAt": "2024-01-01T00:00:00Z",
		"scheduledAt": "2023-12-26T15:30:45.123456789Z",
		"deletedAt": "2023-12-25T15:30:45.123456789Z",
		"publicTime": "2023-12-25T15:30:45.123456789Z"
	}`)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		var testStruct TestTimeStruct
		_ = Unmarshal(data, &testStruct)
	}
}

func TestMarshal_DBStoreFeatures(t *testing.T) {
	testStruct := TestDBStoreFeatures{
		ID:             123,
		Name:           "test-name",
		IgnoredField:   "should-be-ignored",
		NoTagField:     "no-tag-value",
		JSONOnlyField:  "json-only-value",
		BothTagsIgnore: "both-tags-ignore",
		PrivateField:   "private-value",
	}

	// Test our custom marshaling
	dbBytes, err := Marshal(testStruct)
	require.NoError(t, err)

	// Test standard JSON marshaling for comparison
	jsonBytes, err := json.Marshal(testStruct)
	require.NoError(t, err)

	// Parse both results
	var dbResult map[string]interface{}
	var jsonResult map[string]interface{}

	err = json.Unmarshal(dbBytes, &dbResult)
	require.NoError(t, err)

	err = json.Unmarshal(jsonBytes, &jsonResult)
	require.NoError(t, err)

	// DB marshaling should include fields with dbstore tags
	assert.Equal(t, float64(123), dbResult["id"])
	assert.Equal(t, "test-name", dbResult["name"])
	assert.Equal(t, "private-value", dbResult["privateDBField"]) // Uses dbstore tag name

	// Fields without dbstore tag should use exact field name
	assert.Equal(t, "no-tag-value", dbResult["NoTagField"])       // Exact field name, not camelCase
	assert.Equal(t, "json-only-value", dbResult["jsonOnlyField"]) // Uses json tag since no dbstore

	// Fields with dbstore:"-" should be ignored
	_, hasIgnoredField := dbResult["IgnoredField"]
	assert.False(t, hasIgnoredField, "IgnoredField should not be present due to dbstore:\"-\"")

	_, hasBothTagsIgnore := dbResult["BothTagsIgnore"]
	assert.False(t, hasBothTagsIgnore, "BothTagsIgnore should not be present due to dbstore:\"-\"")

	_, hasBothTagsIgnoreJSON := dbResult["jsonField"]
	assert.False(t, hasBothTagsIgnoreJSON, "BothTagsIgnore should not be present under json name either")

	// Standard JSON should behave as expected
	assert.Equal(t, float64(123), jsonResult["id"])
	assert.Equal(t, "test-name", jsonResult["name"])
	assert.Equal(t, "no-tag-value", jsonResult["NoTagField"])       // Uses field name
	assert.Equal(t, "json-only-value", jsonResult["jsonOnlyField"]) // Uses json tag
	assert.Equal(t, "both-tags-ignore", jsonResult["jsonField"])    // Uses json tag name

	// Should not include ignored fields and private fields
	_, hasIgnoredInJSON := jsonResult["IgnoredField"]
	assert.False(t, hasIgnoredInJSON, "IgnoredField should not be in JSON due to json:\"-\"")

	_, hasPrivateInJSON := jsonResult["PrivateField"]
	assert.False(t, hasPrivateInJSON, "PrivateField should not be in JSON due to json:\"-\"")
}

func TestUnmarshal_DBStoreFeatures(t *testing.T) {
	// Create JSON data using dbstore field names
	jsonData := `{
		"id": 456,
		"name": "unmarshal-test",
		"privateDBField": "private-unmarshal",
		"NoTagField": "exact-field-name",
		"jsonOnlyField": "json-tag-value"
	}`

	var testStruct TestDBStoreFeatures
	err := Unmarshal([]byte(jsonData), &testStruct)
	require.NoError(t, err)

	// Verify all fields are correctly set
	assert.Equal(t, int64(456), testStruct.ID)
	assert.Equal(t, "unmarshal-test", testStruct.Name)
	assert.Equal(t, "private-unmarshal", testStruct.PrivateField)
	assert.Equal(t, "exact-field-name", testStruct.NoTagField)
	assert.Equal(t, "json-tag-value", testStruct.JSONOnlyField)

	// Ignored fields should remain zero value
	assert.Equal(t, "", testStruct.IgnoredField)
	assert.Equal(t, "", testStruct.BothTagsIgnore)
}

func TestMarshalUnmarshal_DBStoreFeatures_Roundtrip(t *testing.T) {
	original := TestDBStoreFeatures{
		ID:             789,
		Name:           "roundtrip-test",
		IgnoredField:   "ignored-value",
		NoTagField:     "exact-name-value",
		JSONOnlyField:  "json-field-value",
		BothTagsIgnore: "ignore-this",
		PrivateField:   "private-roundtrip",
	}

	// Marshal
	data, err := Marshal(original)
	require.NoError(t, err)

	// Unmarshal
	var result TestDBStoreFeatures
	err = Unmarshal(data, &result)
	require.NoError(t, err)

	// Compare - ignored fields should be zero in result
	assert.Equal(t, original.ID, result.ID)
	assert.Equal(t, original.Name, result.Name)
	assert.Equal(t, original.NoTagField, result.NoTagField)
	assert.Equal(t, original.JSONOnlyField, result.JSONOnlyField)
	assert.Equal(t, original.PrivateField, result.PrivateField)

	// Ignored fields should be zero value in result
	assert.Equal(t, "", result.IgnoredField)
	assert.Equal(t, "", result.BothTagsIgnore)
}

func TestGetFieldName_NewFeatures(t *testing.T) {
	tests := []struct {
		name     string
		tag      string
		expected string
	}{
		{
			name:     "dbstore dash should be ignored",
			tag:      `dbstore:"-"`,
			expected: "",
		},
		{
			name:     "dbstore dash with options should be ignored",
			tag:      `dbstore:"-,omitempty"`,
			expected: "",
		},
		{
			name:     "dbstore tag takes precedence over json",
			tag:      `json:"jsonName" dbstore:"dbstoreName"`,
			expected: "dbstoreName",
		},
		{
			name:     "json tag used when no dbstore",
			tag:      `json:"jsonName"`,
			expected: "jsonName",
		},
		{
			name:     "json dash ignored when no dbstore",
			tag:      `json:"-"`,
			expected: "",
		},
		{
			name:     "dbstore dash ignores json tag",
			tag:      `json:"jsonName" dbstore:"-"`,
			expected: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			field := getFieldNameFromTags(tt.tag)
			assert.Equal(t, tt.expected, field)
		})
	}
}
