package services

import (
	"errors"
	"strings"
	"testing"

	"github.com/google/uuid"
)

func TestValidateShareInput(t *testing.T) {
	uid := uuid.New()
	puID := uuid.New()
	prID := uuid.New()

	cases := []struct {
		name    string
		kind    string
		input   ShareGrantInput
		wantErr bool
	}{
		{"user happy", "user", ShareGrantInput{RecipientKind: "user", UserID: &uid}, false},
		{"user missing id", "user", ShareGrantInput{RecipientKind: "user"}, true},
		{"office happy", "office", ShareGrantInput{RecipientKind: "office", Office: "munich"}, false},
		{"office unknown key", "office", ShareGrantInput{RecipientKind: "office", Office: "atlantis"}, true},
		{"office empty", "office", ShareGrantInput{RecipientKind: "office"}, true},
		{"partner_unit happy", "partner_unit", ShareGrantInput{RecipientKind: "partner_unit", PartnerUnitID: &puID}, false},
		{"partner_unit missing id", "partner_unit", ShareGrantInput{RecipientKind: "partner_unit"}, true},
		{"project happy", "project", ShareGrantInput{RecipientKind: "project", ProjectID: &prID}, false},
		{"project missing id", "project", ShareGrantInput{RecipientKind: "project"}, true},
		{"unknown kind", "bogus", ShareGrantInput{RecipientKind: "bogus"}, true},
	}
	for _, c := range cases {
		err := validateShareInput(c.kind, c.input)
		if c.wantErr && !errors.Is(err, ErrInvalidInput) {
			t.Errorf("%s: expected ErrInvalidInput, got %v", c.name, err)
		}
		if !c.wantErr && err != nil {
			t.Errorf("%s: unexpected error %v", c.name, err)
		}
	}
}

func TestPredicateIncludesAllShareBranches(t *testing.T) {
	pred := checklistVisibilityPredicate("c", 1)
	wants := []string{
		"c.owner_id = $1",
		"c.visibility IN ('firm', 'global')",
		"u.global_role = 'global_admin'",
		"s.recipient_kind = 'user'",
		"s.recipient_kind = 'office'",
		"s.recipient_kind = 'partner_unit'",
		"s.recipient_kind = 'project'",
		"paliad.checklist_shares",
		"paliad.partner_unit_members",
		"paliad.projects",
		"paliad.project_teams",
	}
	for _, w := range wants {
		if !strings.Contains(pred, w) {
			t.Errorf("predicate missing %q in:\n%s", w, pred)
		}
	}
}

func TestPqUniqueViolationDetection(t *testing.T) {
	cases := []struct {
		err  string
		want bool
	}{
		{"pq: duplicate key value violates unique constraint \"checklist_shares_user_uniq\"", true},
		{"pq: 23505 something", true},
		{"some other error", false},
	}
	for _, c := range cases {
		got := pqUniqueViolation(errors.New(c.err))
		if got != c.want {
			t.Errorf("pqUniqueViolation(%q) = %v; want %v", c.err, got, c.want)
		}
	}
	if pqUniqueViolation(nil) {
		t.Error("nil err should not be a unique violation")
	}
}

func TestNullableString(t *testing.T) {
	if got := nullableString(""); got != nil {
		t.Errorf("empty should map to nil, got %v", got)
	}
	if got := nullableString("  "); got != nil {
		t.Errorf("whitespace should map to nil, got %v", got)
	}
	if got := nullableString("  munich  "); got != "munich" {
		t.Errorf("expected trimmed 'munich', got %v", got)
	}
}

func TestNormaliseSliceAVisibilityAcceptsShared(t *testing.T) {
	for _, v := range []string{"private", "firm", "shared"} {
		if _, err := normaliseSliceAVisibility(v); err != nil {
			t.Errorf("Slice-B visibility %q rejected: %v", v, err)
		}
	}
	if _, err := normaliseSliceAVisibility("global"); !errors.Is(err, ErrInvalidInput) {
		t.Errorf("'global' should be rejected as author-set, got %v", err)
	}
}
