package cprelation

import (
	"errors"
	"strings"
	"testing"
)

func TestNormalizeRelationsForcesCPMaxCountAndKeepsSiblingLimits(t *testing.T) {
	relations, err := normalizeRelations([]relationDTO{
		relationInput(relationTypeCP, 99),
		relationInput(relationTypeBrother, 7),
		relationInput(relationTypeSister, 8),
	})
	if err != nil {
		t.Fatalf("normalizeRelations() error = %v", err)
	}

	got := map[string]int32{}
	for _, relation := range relations {
		got[relation.RelationType] = relation.MaxCountPerUser
	}
	if got[relationTypeCP] != 1 {
		t.Fatalf("CP max count = %d, want 1", got[relationTypeCP])
	}
	if got[relationTypeBrother] != 7 {
		t.Fatalf("brother max count = %d, want 7", got[relationTypeBrother])
	}
	if got[relationTypeSister] != 8 {
		t.Fatalf("sister max count = %d, want 8", got[relationTypeSister])
	}
}

func TestNormalizeRelationsRequiresIncreasingLevelThresholds(t *testing.T) {
	input := relationInput(relationTypeBrother, 5)
	input.Levels[2].IntimacyThreshold = input.Levels[1].IntimacyThreshold

	_, err := normalizeRelations([]relationDTO{
		relationInput(relationTypeCP, 1),
		input,
		relationInput(relationTypeSister, 5),
	})
	if !errors.Is(err, ErrInvalidArgument) {
		t.Fatalf("normalizeRelations() error = %v, want ErrInvalidArgument", err)
	}
}

func TestNormalizeCPListOptionsBoundsPageSize(t *testing.T) {
	options := normalizeCPListOptions(cpListOptions{Page: -3, PageSize: 1000, Status: "ALL", RelationType: " Brother "})
	if options.Page != 1 {
		t.Fatalf("page = %d, want 1", options.Page)
	}
	if options.PageSize != cpListMaxPageSize {
		t.Fatalf("page size = %d, want max %d", options.PageSize, cpListMaxPageSize)
	}
	if options.Status != "" {
		t.Fatalf("status = %q, want empty all filter", options.Status)
	}
	if options.RelationType != relationTypeBrother {
		t.Fatalf("relation type = %q, want brother", options.RelationType)
	}
}

func TestRelationshipListWhereValidatesFilters(t *testing.T) {
	whereSQL, args, err := relationshipListWhere("lalu", cpListOptions{RelationType: relationTypeSister, Status: "active"})
	if err != nil {
		t.Fatalf("relationshipListWhere() error = %v", err)
	}
	if !strings.Contains(whereSQL, "r.app_code = ?") ||
		!strings.Contains(whereSQL, "r.relation_type = ?") ||
		!strings.Contains(whereSQL, "r.status = ?") {
		t.Fatalf("relationship where sql missing filters: %s", whereSQL)
	}
	wantArgs := []any{"lalu", relationTypeSister, statusActive}
	if len(args) != len(wantArgs) {
		t.Fatalf("args len = %d, want %d: %#v", len(args), len(wantArgs), args)
	}
	for index := range wantArgs {
		if args[index] != wantArgs[index] {
			t.Fatalf("args[%d] = %#v, want %#v", index, args[index], wantArgs[index])
		}
	}

	if _, _, err := relationshipListWhere("lalu", cpListOptions{RelationType: "enemy"}); !errors.Is(err, ErrInvalidArgument) {
		t.Fatalf("invalid relation type error = %v, want ErrInvalidArgument", err)
	}
	if _, _, err := relationshipListWhere("lalu", cpListOptions{Status: "pending"}); !errors.Is(err, ErrInvalidArgument) {
		t.Fatalf("invalid relationship status error = %v, want ErrInvalidArgument", err)
	}
}

func TestApplicationListWhereValidatesApplicationStatuses(t *testing.T) {
	whereSQL, args, err := applicationListWhere("lalu", cpListOptions{RelationType: relationTypeCP, Status: "pending"})
	if err != nil {
		t.Fatalf("applicationListWhere() error = %v", err)
	}
	if !strings.Contains(whereSQL, "a.relation_type = ?") || !strings.Contains(whereSQL, "a.status = ?") {
		t.Fatalf("application where sql missing filters: %s", whereSQL)
	}
	if len(args) != 3 || args[0] != "lalu" || args[1] != relationTypeCP || args[2] != "pending" {
		t.Fatalf("application args mismatch: %#v", args)
	}
	if _, _, err := applicationListWhere("lalu", cpListOptions{Status: "active"}); !errors.Is(err, ErrInvalidArgument) {
		t.Fatalf("invalid application status error = %v, want ErrInvalidArgument", err)
	}
}

func TestRelationshipDurationDaysIsInclusive(t *testing.T) {
	const formedAtMS int64 = 1700000000000
	if got := relationshipDurationDays(formedAtMS, 0, formedAtMS); got != 1 {
		t.Fatalf("same moment duration = %d, want 1", got)
	}
	if got := relationshipDurationDays(formedAtMS, 0, formedAtMS+dayMS*2+1); got != 3 {
		t.Fatalf("third day duration = %d, want 3", got)
	}
	if got := relationshipDurationDays(formedAtMS, formedAtMS+dayMS, formedAtMS+dayMS*9); got != 2 {
		t.Fatalf("ended relationship duration = %d, want 2", got)
	}
	if got := relationshipDurationDays(formedAtMS, formedAtMS-1, formedAtMS+dayMS); got != 0 {
		t.Fatalf("invalid ended duration = %d, want 0", got)
	}
}

func TestCPGiftSnapshotEmpty(t *testing.T) {
	if !((&cpGiftSnapshotDTO{}).empty()) {
		t.Fatalf("zero gift should be empty")
	}
	gift := cpGiftSnapshotDTO{GiftValue: 100}
	if gift.empty() {
		t.Fatalf("gift with value should not be empty")
	}
}

func relationInput(relationType string, maxCount int32) relationDTO {
	return relationDTO{
		RelationType:           relationType,
		MaxCountPerUser:        maxCount,
		ApplicationExpireHours: defaultExpireHours,
		Status:                 statusActive,
		Levels: []levelDTO{
			{Level: 1, IntimacyThreshold: 0, Status: statusActive},
			{Level: 2, IntimacyThreshold: 10_000, Status: statusActive},
			{Level: 3, IntimacyThreshold: 30_000, Status: statusActive},
			{Level: 4, IntimacyThreshold: 60_000, Status: statusActive},
			{Level: 5, IntimacyThreshold: 100_000, Status: statusActive},
		},
	}
}
