package service

import (
	"context"
	"errors"
	"testing"

	"wucher/internal/domain/contact"
	"wucher/internal/shared/pkg/uuidv7"
)

type mockContactRepo struct {
	listItems  []contact.ContactListItem
	listTotal  int64
	listErr    error
	lastFilter contact.ListFilter

	getItem         *contact.ContactListItem
	getErr          error
	lastGetID       []byte
	isActorAdmin    bool
	isActorAdminErr error
	resolveRoleCode string
	resolveRoleErr  error
	lastResolveID   []byte

	createID   []byte
	createErr  error
	lastCreate contact.CreateInput

	updateErr  error
	lastUpdate contact.UpdateInput

	deleteErr    error
	lastDeleteID []byte

	updateStatusErr    error
	lastStatusID       []byte
	lastStatusIsActive bool

	updateRoleErr error
	lastRoleID    []byte
	lastRoleCode  string

	updatePilotErr error
	lastPilotID    []byte
	lastCategory   string

	updateLicenseErr error
	lastLicenseID    []byte
	lastLicenseNo    *string

	updateChiefFlagsErr error
	lastChiefID         []byte
	lastChiefPatch      contact.ProfilePatch

	updateResponsibleErr error
	lastResponsibleID    []byte
	lastResponsibleValue bool

	bulkErr        error
	bulkCount      int64
	lastBulkIDs    [][]byte
	lastBulkActive bool
}

func (m *mockContactRepo) ListContacts(_ context.Context, filter contact.ListFilter) ([]contact.ContactListItem, int64, error) {
	m.lastFilter = filter
	if m.listErr != nil {
		return nil, 0, m.listErr
	}
	return m.listItems, m.listTotal, nil
}

func (m *mockContactRepo) GetContactByID(_ context.Context, userID []byte) (*contact.ContactListItem, error) {
	m.lastGetID = append([]byte(nil), userID...)
	if m.getErr != nil {
		return nil, m.getErr
	}
	return m.getItem, nil
}

func (m *mockContactRepo) IsActorAdmin(_ context.Context) (bool, error) {
	if m.isActorAdminErr != nil {
		return false, m.isActorAdminErr
	}
	return m.isActorAdmin, nil
}

func (m *mockContactRepo) ResolveRoleCodeByID(_ context.Context, roleID []byte) (string, error) {
	m.lastResolveID = append([]byte(nil), roleID...)
	if m.resolveRoleErr != nil {
		return "", m.resolveRoleErr
	}
	return m.resolveRoleCode, nil
}

func (m *mockContactRepo) CreateContact(_ context.Context, in contact.CreateInput) ([]byte, error) {
	m.lastCreate = in
	if m.createErr != nil {
		return nil, m.createErr
	}
	return m.createID, nil
}

func (m *mockContactRepo) UpdateContact(_ context.Context, in contact.UpdateInput) error {
	m.lastUpdate = in
	return m.updateErr
}

func (m *mockContactRepo) DeleteContact(_ context.Context, userID []byte) error {
	m.lastDeleteID = append([]byte(nil), userID...)
	return m.deleteErr
}

func (m *mockContactRepo) UpdateContactStatus(_ context.Context, userID []byte, isActive bool) error {
	m.lastStatusID = append([]byte(nil), userID...)
	m.lastStatusIsActive = isActive
	return m.updateStatusErr
}

func (m *mockContactRepo) UpdateContactRole(_ context.Context, userID []byte, roleCode string) error {
	m.lastRoleID = append([]byte(nil), userID...)
	m.lastRoleCode = roleCode
	return m.updateRoleErr
}

func (m *mockContactRepo) UpdatePilotCategory(_ context.Context, userID []byte, category string) error {
	m.lastPilotID = append([]byte(nil), userID...)
	m.lastCategory = category
	return m.updatePilotErr
}

func (m *mockContactRepo) UpdateLicense(_ context.Context, userID []byte, licenseNo *string) error {
	m.lastLicenseID = append([]byte(nil), userID...)
	m.lastLicenseNo = licenseNo
	return m.updateLicenseErr
}

func (m *mockContactRepo) UpdateChiefFlags(_ context.Context, userID []byte, patch contact.ProfilePatch) error {
	m.lastChiefID = append([]byte(nil), userID...)
	m.lastChiefPatch = patch
	return m.updateChiefFlagsErr
}

func (m *mockContactRepo) UpdateResponsibleComplaints(_ context.Context, userID []byte, value bool) error {
	m.lastResponsibleID = append([]byte(nil), userID...)
	m.lastResponsibleValue = value
	return m.updateResponsibleErr
}

func (m *mockContactRepo) BulkUpdateStatus(_ context.Context, userIDs [][]byte, isActive bool) (int64, error) {
	m.lastBulkIDs = append([][]byte(nil), userIDs...)
	m.lastBulkActive = isActive
	if m.bulkErr != nil {
		return 0, m.bulkErr
	}
	return m.bulkCount, nil
}

type mockPINVerifier struct {
	err        error
	lastUserID []byte
	lastAction string
	lastToken  string
}

func (m *mockPINVerifier) ConsumeSecurityPINActionToken(_ context.Context, userID []byte, action, token string) error {
	m.lastUserID = append([]byte(nil), userID...)
	m.lastAction = action
	m.lastToken = token
	return m.err
}

func TestNewContactService(t *testing.T) {
	repo := &mockContactRepo{}
	verifier := &mockPINVerifier{}
	svc := NewContactService(repo, verifier)
	if svc == nil || svc.repo != repo || svc.pinVerifier != verifier {
		t.Fatalf("expected service to hold dependencies")
	}
}

func TestContactServiceRepoNotConfigured(t *testing.T) {
	svc := NewContactService(nil, nil)
	ctx := context.Background()
	userID := uuidv7.MustBytes()

	if _, _, err := svc.ListContacts(ctx, contact.ListFilter{}); err == nil {
		t.Fatalf("expected list error")
	}
	if _, err := svc.GetContactByID(ctx, userID); err == nil {
		t.Fatalf("expected get error")
	}
	if _, err := svc.ResolveRoleCodeByID(ctx, userID); err == nil {
		t.Fatalf("expected resolve role error")
	}
	if _, err := svc.CreateContact(ctx, contact.CreateInput{}); err == nil {
		t.Fatalf("expected create error")
	}
	if err := svc.UpdateContact(ctx, contact.UpdateInput{}); err == nil {
		t.Fatalf("expected update error")
	}
	if err := svc.DeleteContact(ctx, userID); err == nil {
		t.Fatalf("expected delete error")
	}
	if err := svc.UpdateContactStatus(ctx, userID, true); err == nil {
		t.Fatalf("expected status error")
	}
	if err := svc.UpdateContactRole(ctx, userID, "pilot"); err == nil {
		t.Fatalf("expected role error")
	}
	if err := svc.UpdatePilotCategory(ctx, userID, "regular"); err == nil {
		t.Fatalf("expected pilot category error")
	}
	if err := svc.UpdateLicense(ctx, userID, nil); err == nil {
		t.Fatalf("expected license error")
	}
	if err := svc.UpdateChiefFlags(ctx, userID, contact.ProfilePatch{}); err == nil {
		t.Fatalf("expected chief flags error")
	}
	if err := svc.UpdateResponsibleComplaints(ctx, userID, true); err == nil {
		t.Fatalf("expected responsible complaints error")
	}
	if _, err := svc.BulkUpdateStatus(ctx, [][]byte{userID}, true); err == nil {
		t.Fatalf("expected bulk update error")
	}
}

func TestContactServiceDelegatesToRepo(t *testing.T) {
	ctx := context.Background()
	userID := uuidv7.MustBytes()
	roleID := uuidv7.MustBytes()
	licenseNo := "LIC"
	shortName := "PJ"
	repoErr := errors.New("repo failed")

	t.Run("list success and error", func(t *testing.T) {
		repo := &mockContactRepo{
			listItems: []contact.ContactListItem{{UserID: userID, Email: "user@example.com"}},
			listTotal: 1,
		}
		svc := NewContactService(repo, nil)
		filter := contact.ListFilter{Role: "pilot", Sort: "sortkey", Limit: 10, Offset: 5}
		items, total, err := svc.ListContacts(ctx, filter)
		if err != nil || total != 1 || len(items) != 1 {
			t.Fatalf("unexpected list result: total=%d len=%d err=%v", total, len(items), err)
		}
		if repo.lastFilter.Role != "pilot" || repo.lastFilter.Limit != 10 || repo.lastFilter.Offset != 5 {
			t.Fatalf("expected filter forwarded, got %#v", repo.lastFilter)
		}
		if repo.lastFilter.Sort != "users.is_active DESC, CASE WHEN users.is_active = 1 AND users.sortkey IS NOT NULL AND users.sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN users.is_active = 1 AND users.sortkey IS NOT NULL AND users.sortkey >= 0 THEN users.sortkey END ASC, users.first_name ASC" {
			t.Fatalf("unexpected normalized sort, got %q", repo.lastFilter.Sort)
		}

		repo.listErr = repoErr
		if _, _, err := svc.ListContacts(ctx, filter); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("get success and error", func(t *testing.T) {
		repo := &mockContactRepo{getItem: &contact.ContactListItem{UserID: userID, Email: "user@example.com"}}
		svc := NewContactService(repo, nil)
		item, err := svc.GetContactByID(ctx, userID)
		if err != nil || item == nil || string(repo.lastGetID) != string(userID) {
			t.Fatalf("unexpected get result: item=%v err=%v", item, err)
		}
		repo.getErr = repoErr
		if _, err := svc.GetContactByID(ctx, userID); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("resolve role success and error", func(t *testing.T) {
		repo := &mockContactRepo{resolveRoleCode: "pilot"}
		svc := NewContactService(repo, nil)
		roleCode, err := svc.ResolveRoleCodeByID(ctx, roleID)
		if err != nil || roleCode != "pilot" || string(repo.lastResolveID) != string(roleID) {
			t.Fatalf("unexpected resolve role result: role=%q err=%v", roleCode, err)
		}
		repo.resolveRoleErr = repoErr
		if _, err := svc.ResolveRoleCodeByID(ctx, roleID); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("create success and error", func(t *testing.T) {
		repo := &mockContactRepo{createID: userID}
		svc := NewContactService(repo, nil)
		in := contact.CreateInput{
			RoleID:  roleID,
			User:    contact.UserPatch{Email: &licenseNo},
			Profile: contact.ProfilePatch{ShortName: &shortName},
		}
		gotID, err := svc.CreateContact(ctx, in)
		if err != nil || string(gotID) != string(userID) {
			t.Fatalf("unexpected create result: id=%v err=%v", gotID, err)
		}
		if string(repo.lastCreate.RoleID) != string(roleID) || repo.lastCreate.Profile.ShortName == nil || *repo.lastCreate.Profile.ShortName != shortName {
			t.Fatalf("expected create input forwarded")
		}
		repo.createErr = repoErr
		if _, err := svc.CreateContact(ctx, in); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("update success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		in := contact.UpdateInput{UserID: userID, Profile: contact.ProfilePatch{ShortName: &shortName}}
		if err := svc.UpdateContact(ctx, in); err != nil {
			t.Fatalf("unexpected update error: %v", err)
		}
		if string(repo.lastUpdate.UserID) != string(userID) || repo.lastUpdate.Profile.ShortName == nil || *repo.lastUpdate.Profile.ShortName != shortName {
			t.Fatalf("expected update input forwarded")
		}
		repo.updateErr = repoErr
		if err := svc.UpdateContact(ctx, in); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("delete success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		if err := svc.DeleteContact(ctx, userID); err != nil {
			t.Fatalf("unexpected delete error: %v", err)
		}
		if string(repo.lastDeleteID) != string(userID) {
			t.Fatalf("expected delete id forwarded")
		}
		repo.deleteErr = repoErr
		if err := svc.DeleteContact(ctx, userID); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("status success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		if err := svc.UpdateContactStatus(ctx, userID, true); err != nil {
			t.Fatalf("unexpected status error: %v", err)
		}
		if string(repo.lastStatusID) != string(userID) || !repo.lastStatusIsActive {
			t.Fatalf("expected status args forwarded")
		}
		repo.updateStatusErr = repoErr
		if err := svc.UpdateContactStatus(ctx, userID, true); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("role success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		if err := svc.UpdateContactRole(ctx, userID, "pilot"); err != nil {
			t.Fatalf("unexpected role error: %v", err)
		}
		if string(repo.lastRoleID) != string(userID) || repo.lastRoleCode != "pilot" {
			t.Fatalf("expected role args forwarded")
		}
		repo.updateRoleErr = repoErr
		if err := svc.UpdateContactRole(ctx, userID, "pilot"); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("pilot category success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		if err := svc.UpdatePilotCategory(ctx, userID, "regular"); err != nil {
			t.Fatalf("unexpected pilot category error: %v", err)
		}
		if string(repo.lastPilotID) != string(userID) || repo.lastCategory != "regular" {
			t.Fatalf("expected pilot category args forwarded")
		}
		repo.updatePilotErr = repoErr
		if err := svc.UpdatePilotCategory(ctx, userID, "regular"); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("license success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		if err := svc.UpdateLicense(ctx, userID, &licenseNo); err != nil {
			t.Fatalf("unexpected license error: %v", err)
		}
		if string(repo.lastLicenseID) != string(userID) || repo.lastLicenseNo == nil || *repo.lastLicenseNo != licenseNo {
			t.Fatalf("expected license args forwarded")
		}
		repo.updateLicenseErr = repoErr
		if err := svc.UpdateLicense(ctx, userID, &licenseNo); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("chief flags success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		patch := contact.ProfilePatch{ShortName: &shortName}
		if err := svc.UpdateChiefFlags(ctx, userID, patch); err != nil {
			t.Fatalf("unexpected chief flags error: %v", err)
		}
		if string(repo.lastChiefID) != string(userID) || repo.lastChiefPatch.ShortName == nil || *repo.lastChiefPatch.ShortName != shortName {
			t.Fatalf("expected chief flags args forwarded")
		}
		repo.updateChiefFlagsErr = repoErr
		if err := svc.UpdateChiefFlags(ctx, userID, patch); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("responsible complaints success and error", func(t *testing.T) {
		repo := &mockContactRepo{}
		svc := NewContactService(repo, nil)
		if err := svc.UpdateResponsibleComplaints(ctx, userID, true); err != nil {
			t.Fatalf("unexpected responsible complaints error: %v", err)
		}
		if string(repo.lastResponsibleID) != string(userID) || !repo.lastResponsibleValue {
			t.Fatalf("expected responsible complaints args forwarded")
		}
		repo.updateResponsibleErr = repoErr
		if err := svc.UpdateResponsibleComplaints(ctx, userID, true); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})

	t.Run("bulk update success and error", func(t *testing.T) {
		repo := &mockContactRepo{bulkCount: 2}
		svc := NewContactService(repo, nil)
		ids := [][]byte{userID, uuidv7.MustBytes()}
		count, err := svc.BulkUpdateStatus(ctx, ids, true)
		if err != nil || count != 2 {
			t.Fatalf("unexpected bulk update result: count=%d err=%v", count, err)
		}
		if len(repo.lastBulkIDs) != 2 || !repo.lastBulkActive {
			t.Fatalf("expected bulk update args forwarded")
		}
		repo.bulkErr = repoErr
		if _, err := svc.BulkUpdateStatus(ctx, ids, true); !errors.Is(err, repoErr) {
			t.Fatalf("expected repo error, got %v", err)
		}
	})
}

func TestNormalizeContactSort(t *testing.T) {
	cases := map[string]string{
		"sortkey":    "users.is_active DESC, CASE WHEN users.is_active = 1 AND users.sortkey IS NOT NULL AND users.sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN users.is_active = 1 AND users.sortkey IS NOT NULL AND users.sortkey >= 0 THEN users.sortkey END ASC, users.first_name ASC",
		"-sortkey":   "users.is_active DESC, CASE WHEN users.is_active = 1 AND users.sortkey IS NOT NULL AND users.sortkey >= 0 THEN 0 ELSE 1 END ASC, CASE WHEN users.is_active = 1 AND users.sortkey IS NOT NULL AND users.sortkey >= 0 THEN users.sortkey END DESC, users.first_name ASC",
		"note":       "COALESCE(pp.note, dp.note, arp.note, tp.note, fap.note, sp.note, '') ASC",
		"-note":      "COALESCE(pp.note, dp.note, arp.note, tp.note, fap.note, sp.note, '') DESC",
		"is_active":  "users.is_active ASC",
		"-is_active": "users.is_active DESC",
		"name":       "users.first_name ASC",
		"email":      "users.email ASC",
		"unknown":    "",
		"":           "",
	}

	for in, want := range cases {
		if got := normalizeContactSort(in); got != want {
			t.Fatalf("normalizeContactSort(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestContactServiceCheckSensitiveAction(t *testing.T) {
	ctx := context.Background()
	validUserID := uuidv7.MustBytes()
	verifierErr := errors.New("pin verify failed")

	svc := NewContactService(&mockContactRepo{}, nil)
	if err := svc.CheckSensitiveAction(ctx, []byte("short"), "token", "action"); !errors.Is(err, ErrInvalidPINAction) {
		t.Fatalf("expected invalid pin action for invalid user id, got %v", err)
	}
	if err := svc.CheckSensitiveAction(ctx, validUserID, "token", "action"); err == nil || err.Error() != "pin verifier not configured" {
		t.Fatalf("expected pin verifier not configured error, got %v", err)
	}

	verifier := &mockPINVerifier{}
	svc = NewContactService(&mockContactRepo{}, verifier)
	if err := svc.CheckSensitiveAction(ctx, validUserID, "   ", "action"); !errors.Is(err, ErrInvalidPINAction) {
		t.Fatalf("expected invalid pin action for empty token, got %v", err)
	}
	if err := svc.CheckSensitiveAction(ctx, validUserID, "token", "   "); !errors.Is(err, ErrInvalidPINAction) {
		t.Fatalf("expected invalid pin action for empty action, got %v", err)
	}

	verifier.err = verifierErr
	if err := svc.CheckSensitiveAction(ctx, validUserID, " token ", " action "); !errors.Is(err, verifierErr) {
		t.Fatalf("expected verifier error, got %v", err)
	}
	if string(verifier.lastUserID) != string(validUserID) || verifier.lastAction != "action" || verifier.lastToken != "token" {
		t.Fatalf("expected trimmed action/token forwarded")
	}

	verifier.err = nil
	if err := svc.CheckSensitiveAction(ctx, validUserID, " token ", " action "); err != nil {
		t.Fatalf("unexpected verifier error: %v", err)
	}
}
