package service

import (
	"context"
	"errors"
	"testing"

	otherperson "wucher/internal/domain/other_person"
	"wucher/internal/shared/pkg/uuidv7"
)

type fakeOtherPersonRepo struct {
	byID       map[string]*otherperson.OtherPerson
	identity   map[string]*otherperson.OtherPerson // key: name|phone|email
	created    *otherperson.OtherPerson
	updated    *otherperson.OtherPerson
	deletedID  []byte
	forceError error
}

func newFakeOtherPersonRepo() *fakeOtherPersonRepo {
	return &fakeOtherPersonRepo{
		byID:     map[string]*otherperson.OtherPerson{},
		identity: map[string]*otherperson.OtherPerson{},
	}
}

func identityKey(name, phone, email string) string { return name + "|" + phone + "|" + email }

func (r *fakeOtherPersonRepo) Upsert(context.Context, *otherperson.OtherPerson) error { return nil }

func (r *fakeOtherPersonRepo) Create(_ context.Context, p *otherperson.OtherPerson) error {
	if r.forceError != nil {
		return r.forceError
	}
	if len(p.ID) == 0 {
		p.ID = uuidv7.MustBytes()
	}
	r.created = p
	cp := *p
	r.byID[string(p.ID)] = &cp
	r.identity[identityKey(p.Name, p.MobilePhone, p.Email)] = &cp
	return nil
}

func (r *fakeOtherPersonRepo) Update(_ context.Context, p *otherperson.OtherPerson) error {
	if r.forceError != nil {
		return r.forceError
	}
	r.updated = p
	cp := *p
	r.byID[string(p.ID)] = &cp
	return nil
}

func (r *fakeOtherPersonRepo) Delete(_ context.Context, id, _ []byte) error {
	if r.forceError != nil {
		return r.forceError
	}
	r.deletedID = id
	delete(r.byID, string(id))
	return nil
}

func (r *fakeOtherPersonRepo) GetByID(_ context.Context, id []byte) (*otherperson.OtherPerson, error) {
	return r.byID[string(id)], nil
}

func (r *fakeOtherPersonRepo) FindByIdentity(_ context.Context, name, phone, email string) (*otherperson.OtherPerson, error) {
	return r.identity[identityKey(name, phone, email)], nil
}

func (r *fakeOtherPersonRepo) Search(context.Context, string, int, int) ([]otherperson.OtherPerson, int64, error) {
	return nil, 0, nil
}

func TestOtherPersonServiceCreate(t *testing.T) {
	repo := newFakeOtherPersonRepo()
	svc := NewOtherPersonService(repo)
	ctx := context.Background()

	// Name required.
	if err := svc.Create(ctx, &otherperson.OtherPerson{Name: "   "}); !errors.Is(err, otherperson.ErrNameRequired) {
		t.Fatalf("expected ErrNameRequired, got %v", err)
	}

	// Happy path.
	p := &otherperson.OtherPerson{Name: "Guest", MobilePhone: "1", Email: "g@x.io"}
	if err := svc.Create(ctx, p); err != nil {
		t.Fatalf("create: %v", err)
	}
	if len(p.ID) == 0 || repo.created == nil {
		t.Fatalf("expected create persisted")
	}

	// Duplicate identity rejected.
	dup := &otherperson.OtherPerson{Name: "Guest", MobilePhone: "1", Email: "g@x.io"}
	if err := svc.Create(ctx, dup); !errors.Is(err, otherperson.ErrDuplicate) {
		t.Fatalf("expected ErrDuplicate, got %v", err)
	}
}

func TestOtherPersonServiceUpdate(t *testing.T) {
	repo := newFakeOtherPersonRepo()
	svc := NewOtherPersonService(repo)
	ctx := context.Background()

	// Seed two rows.
	a := &otherperson.OtherPerson{Name: "Alpha", MobilePhone: "1", Email: "a@x.io"}
	b := &otherperson.OtherPerson{Name: "Beta", MobilePhone: "2", Email: "b@x.io"}
	_ = svc.Create(ctx, a)
	_ = svc.Create(ctx, b)

	// Updating a missing id -> not found.
	missing := &otherperson.OtherPerson{ID: uuidv7.MustBytes(), Name: "X"}
	if err := svc.Update(ctx, missing); !errors.Is(err, otherperson.ErrNotFound) {
		t.Fatalf("expected ErrNotFound, got %v", err)
	}

	// Updating A to B's identity -> duplicate.
	clash := &otherperson.OtherPerson{ID: a.ID, Name: "Beta", MobilePhone: "2", Email: "b@x.io"}
	if err := svc.Update(ctx, clash); !errors.Is(err, otherperson.ErrDuplicate) {
		t.Fatalf("expected ErrDuplicate, got %v", err)
	}

	// Updating A to a fresh identity -> ok.
	ok := &otherperson.OtherPerson{ID: a.ID, Name: "Alpha2", MobilePhone: "1", Email: "a@x.io"}
	if err := svc.Update(ctx, ok); err != nil {
		t.Fatalf("expected update ok, got %v", err)
	}
	if repo.updated == nil || repo.updated.Name != "Alpha2" {
		t.Fatalf("update not forwarded")
	}
}

func TestOtherPersonServiceDeleteNotFound(t *testing.T) {
	repo := newFakeOtherPersonRepo()
	svc := NewOtherPersonService(repo)
	if err := svc.Delete(context.Background(), uuidv7.MustBytes(), nil); !errors.Is(err, otherperson.ErrNotFound) {
		t.Fatalf("expected ErrNotFound, got %v", err)
	}
}
