package services

import (
	"authentication/models"
	"database/sql"
	"encoding/json"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
)

func TestInsertAccessLogLogin(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	userID := "user123"
	participantID := "part456"
	activityType := 17
	ipAddress := "192.168.1.1"
	fieldData := json.RawMessage(`{"key": "value"}`)
	currentTime := time.Now()

	accessLog := models.UserAccessLog{
		UserID:        &userID,
		ParticipantID: &participantID,
		ActivityType:  activityType,
		IPAddress:     ipAddress,
		FieldUpdated:  &fieldData,
		Time:          currentTime,
	}

	mock.ExpectExec(`INSERT INTO access_log \( user_id, participant_id, activity_type, ip_address, field_updated, time\) VALUES \(\?, \?, \?, \?, \?, \?\)`).
		WithArgs(&userID, &participantID, activityType, ipAddress, &fieldData, currentTime).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err := InsertAccessLogLogin(accessLog)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Unfulfilled expectations: %v", err)
	}
}

func TestInsertAccessLogLoginNullFields(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	activityType := 5
	ipAddress := "10.0.0.1"
	currentTime := time.Now()

	accessLog := models.UserAccessLog{
		UserID:        nil,
		ParticipantID: nil,
		ActivityType:  activityType,
		IPAddress:     ipAddress,
		FieldUpdated:  nil,
		Time:          currentTime,
	}

	mock.ExpectExec(`INSERT INTO access_log \( user_id, participant_id, activity_type, ip_address, field_updated, time\) VALUES \(\?, \?, \?, \?, \?, \?\)`).
		WithArgs(nil, nil, activityType, ipAddress, nil, currentTime).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err := InsertAccessLogLogin(accessLog)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}
}

func TestInsertAccessLogLoginError(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	userID := "user999"
	activityType := 17
	ipAddress := "172.16.0.1"
	currentTime := time.Now()

	accessLog := models.UserAccessLog{
		UserID:       &userID,
		ActivityType: activityType,
		IPAddress:    ipAddress,
		Time:         currentTime,
	}

	mock.ExpectExec(`INSERT INTO access_log`).
		WillReturnError(sql.ErrConnDone)

	err := InsertAccessLogLogin(accessLog)

	if err == nil {
		t.Error("Expected error, got nil")
	}

	if err != sql.ErrConnDone {
		t.Errorf("Expected sql.ErrConnDone, got: %v", err)
	}
}

func TestGetActivityMessages(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	activityID := 17
	expectedDescription := "User logged in"

	rows := sqlmock.NewRows([]string{"Description"}).
		AddRow(expectedDescription)

	mock.ExpectQuery(`SELECT Description FROM activity_type WHERE id = \?`).
		WithArgs(activityID).
		WillReturnRows(rows)

	description, err := GetActivityMessages(activityID)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if description != expectedDescription {
		t.Errorf("Expected description '%s', got '%s'", expectedDescription, description)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Unfulfilled expectations: %v", err)
	}
}

func TestGetActivityMessagesNotFound(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	activityID := 999

	mock.ExpectQuery(`SELECT Description FROM activity_type WHERE id = \?`).
		WithArgs(activityID).
		WillReturnError(sql.ErrNoRows)

	description, err := GetActivityMessages(activityID)

	if err != sql.ErrNoRows {
		t.Errorf("Expected sql.ErrNoRows, got: %v", err)
	}

	if description != "" {
		t.Errorf("Expected empty description, got '%s'", description)
	}
}

func TestGetActivityMessagesError(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	activityID := 5

	mock.ExpectQuery(`SELECT Description FROM activity_type WHERE id = \?`).
		WithArgs(activityID).
		WillReturnError(sql.ErrConnDone)

	description, err := GetActivityMessages(activityID)

	if err == nil {
		t.Error("Expected error, got nil")
	}

	if description != "" {
		t.Errorf("Expected empty description on error, got '%s'", description)
	}
}

func TestInsertAccessLogLoginMultipleActivityTypes(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	activityTypes := []int{1, 5, 10, 17, 20}

	for _, actType := range activityTypes {
		userID := "user123"
		ipAddress := "192.168.1.1"
		currentTime := time.Now()

		accessLog := models.UserAccessLog{
			UserID:       &userID,
			ActivityType: actType,
			IPAddress:    ipAddress,
			Time:         currentTime,
		}

		mock.ExpectExec(`INSERT INTO access_log`).
			WillReturnResult(sqlmock.NewResult(1, 1))

		err := InsertAccessLogLogin(accessLog)

		if err != nil {
			t.Errorf("Expected no error for activity type %d, got: %v", actType, err)
		}
	}
}

func TestGetActivityMessagesMultipleTypes(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	testCases := []struct {
		id          int
		description string
	}{
		{1, "User created"},
		{5, "User updated"},
		{10, "User deleted"},
		{17, "User logged in"},
		{20, "Password changed"},
	}

	for _, tc := range testCases {
		t.Run(tc.description, func(t *testing.T) {
			rows := sqlmock.NewRows([]string{"Description"}).
				AddRow(tc.description)

			mock.ExpectQuery(`SELECT Description FROM activity_type WHERE id = \?`).
				WithArgs(tc.id).
				WillReturnRows(rows)

			description, err := GetActivityMessages(tc.id)

			if err != nil {
				t.Errorf("Expected no error, got: %v", err)
			}

			if description != tc.description {
				t.Errorf("Expected '%s', got '%s'", tc.description, description)
			}
		})
	}
}

func TestInsertAccessLogLoginWithJSONField(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	userID := "user789"
	activityType := 17
	ipAddress := "192.168.1.100"
	currentTime := time.Now()

	// Complex JSON field
	complexJSON := map[string]interface{}{
		"action":    "login",
		"timestamp": time.Now().Unix(),
		"metadata": map[string]string{
			"browser": "Chrome",
			"os":      "Windows",
		},
	}
	jsonData, _ := json.Marshal(complexJSON)
	fieldData := json.RawMessage(jsonData)

	accessLog := models.UserAccessLog{
		UserID:       &userID,
		ActivityType: activityType,
		IPAddress:    ipAddress,
		FieldUpdated: &fieldData,
		Time:         currentTime,
	}

	mock.ExpectExec(`INSERT INTO access_log`).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err := InsertAccessLogLogin(accessLog)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}
}

func TestInsertAccessLogLoginIPv6(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	userID := "user123"
	activityType := 17
	ipAddressV6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
	currentTime := time.Now()

	accessLog := models.UserAccessLog{
		UserID:       &userID,
		ActivityType: activityType,
		IPAddress:    ipAddressV6,
		Time:         currentTime,
	}

	mock.ExpectExec(`INSERT INTO access_log`).
		WillReturnResult(sqlmock.NewResult(1, 1))

	err := InsertAccessLogLogin(accessLog)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}
}

func TestGetActivityMessagesEmptyDescription(t *testing.T) {
	mock, cleanup := setupMockDB(t)
	defer cleanup()

	activityID := 99

	rows := sqlmock.NewRows([]string{"Description"}).
		AddRow("")

	mock.ExpectQuery(`SELECT Description FROM activity_type WHERE id = \?`).
		WithArgs(activityID).
		WillReturnRows(rows)

	description, err := GetActivityMessages(activityID)

	if err != nil {
		t.Errorf("Expected no error, got: %v", err)
	}

	if description != "" {
		t.Errorf("Expected empty description, got '%s'", description)
	}
}
