package validator

import (
	"fmt"
	"testing"

	govalidator "github.com/go-playground/validator/v10"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestValidatePasswordComplexity(t *testing.T) {
	tests := []struct {
		name     string
		password string
		valid    bool
	}{
		{"valid password", "Password1", true},
		{"valid complex password", "MyP@ssw0rd!", true},
		{"missing uppercase", "password1", false},
		{"missing lowercase", "PASSWORD1", false},
		{"missing digit", "Password", false},
		{"only digits", "12345678", false},
		{"only lowercase", "abcdefgh", false},
		{"only uppercase", "ABCDEFGH", false},
		{"empty string", "", false},
		{"single valid char each", "aA1", true},
		{"unicode uppercase with digit and lower", "Über1abc", true},
	}

	v := govalidator.New()
	v.RegisterValidation("password_complexity", validatePasswordComplexity)

	type testStruct struct {
		Password string `validate:"password_complexity"`
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			s := testStruct{Password: tc.password}
			err := v.Struct(s)
			if tc.valid && err != nil {
				t.Errorf("expected password %q to be valid, got error: %v", tc.password, err)
			}
			if !tc.valid && err == nil {
				t.Errorf("expected password %q to be invalid, got nil error", tc.password)
			}
		})
	}
}

func TestValidatePasswordComplexityWithMinLength(t *testing.T) {
	v := govalidator.New()
	v.RegisterValidation("password_complexity", validatePasswordComplexity)

	type request struct {
		Password string `validate:"required,min=8,password_complexity"`
	}

	tests := []struct {
		name     string
		password string
		valid    bool
	}{
		{"valid 8+ chars with complexity", "Abcdefg1", true},
		{"too short but complex", "Ab1", false},
		{"long but no uppercase", "abcdefgh1", false},
		{"long but no lowercase", "ABCDEFGH1", false},
		{"long but no digit", "Abcdefghi", false},
		{"empty", "", false},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			r := request{Password: tc.password}
			err := v.Struct(r)
			if tc.valid && err != nil {
				t.Errorf("expected %q to be valid, got error: %v", tc.password, err)
			}
			if !tc.valid && err == nil {
				t.Errorf("expected %q to be invalid, got nil", tc.password)
			}
		})
	}
}

func TestFormatMessagePasswordComplexity(t *testing.T) {
	cv := NewCustomValidator()

	type request struct {
		Password string `json:"password" validate:"required,min=8,password_complexity"`
	}

	r := request{Password: "lowercase1"}
	err := cv.Validate(r)
	if err == nil {
		t.Fatal("expected validation error for password without uppercase")
	}

	resp := FormatValidationErrors(err)
	if resp == nil {
		t.Fatal("expected non-nil error response")
	}

	field, ok := resp.Fields["password"]
	if !ok {
		t.Fatal("expected 'password' field in error response")
	}

	expectedMsg := "Password must be at least 8 characters with at least one uppercase letter, one lowercase letter, and one digit"
	if field.Message != expectedMsg {
		t.Errorf("expected message %q, got %q", expectedMsg, field.Message)
	}

	if field.Tag != "password_complexity" {
		t.Errorf("expected tag 'password_complexity', got %q", field.Tag)
	}
}

func TestPasswordComplexity_AdditionalCases(t *testing.T) {
	cv := NewCustomValidator()

	type request struct {
		Password string `json:"password" validate:"required,min=8,password_complexity"`
	}

	tests := []struct {
		name  string
		pw    string
		valid bool
	}{
		{"no uppercase no digit", "password", false},
		{"no lowercase", "PASSWORD1", false},
		{"no digit", "Password", false},
		{"too short", "Pass1", false},
		{"valid standard", "Password1", true},
		{"valid with special chars", "P@ssw0rd", true},
		{"spaces with complexity", "Pass 1234", true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			r := request{Password: tc.pw}
			err := cv.Validate(r)
			if tc.valid {
				assert.NoError(t, err, "expected %q to be valid", tc.pw)
			} else {
				assert.Error(t, err, "expected %q to be invalid", tc.pw)
			}
		})
	}
}

func TestFormatValidationErrors_AllTags(t *testing.T) {
	cv := NewCustomValidator()

	type allTags struct {
		Required       string `json:"required" validate:"required"`
		Email          string `json:"email" validate:"email"`
		MinLen         string `json:"min_len" validate:"min=5"`
		MaxLen         string `json:"max_len" validate:"max=3"`
		OneOf          string `json:"one_of" validate:"oneof=a b c"`
		URL            string `json:"url" validate:"url"`
	}

	input := allTags{
		Required: "",      // fails required
		Email:    "bad",   // fails email
		MinLen:   "ab",    // fails min=5
		MaxLen:   "abcde", // fails max=3
		OneOf:    "z",     // fails oneof
		URL:      "nope",  // fails url
	}

	err := cv.Validate(input)
	require.Error(t, err)

	resp := FormatValidationErrors(err)
	require.NotNil(t, resp)
	assert.Equal(t, "Validation failed", resp.Error)

	expectedMessages := map[string]string{
		"required": "This field is required",
		"email":    "Must be a valid email address",
		"min_len":  "Must be at least 5 characters",
		"max_len":  "Must be at most 3 characters",
		"one_of":   "Must be one of: a b c",
		"url":      "Must be a valid URL",
	}

	for field, expectedMsg := range expectedMessages {
		fe, ok := resp.Fields[field]
		assert.True(t, ok, "expected field %q in error response", field)
		if ok {
			assert.Equal(t, expectedMsg, fe.Message, "message mismatch for field %q", field)
		}
	}
}

func TestFormatValidationErrors_NonValidationError(t *testing.T) {
	err := fmt.Errorf("some random error")
	resp := FormatValidationErrors(err)
	require.NotNil(t, resp)
	assert.Equal(t, "some random error", resp.Error)
	assert.Nil(t, resp.Fields)
}

func TestNewCustomValidator_UsesJSONTagNames(t *testing.T) {
	cv := NewCustomValidator()

	type request struct {
		FirstName string `json:"first_name" validate:"required"`
	}

	err := cv.Validate(request{})
	require.Error(t, err)

	resp := FormatValidationErrors(err)
	require.NotNil(t, resp)
	_, ok := resp.Fields["first_name"]
	assert.True(t, ok, "expected JSON tag name 'first_name' in error fields")
}

func TestCustomValidator_Validate_Success(t *testing.T) {
	cv := NewCustomValidator()

	type request struct {
		Name string `json:"name" validate:"required"`
	}

	err := cv.Validate(request{Name: "test"})
	assert.NoError(t, err)
}
