package mysql

import (
	"context"
	"fmt"
	"testing"
	"time"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	otherperson "wucher/internal/domain/other_person"
)

func openOtherPersonTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:other_person_test_%d?mode=memory&cache=shared", time.Now().UnixNano())
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
		NowFunc: func() time.Time { return time.Now().UTC() },
	})
	if err != nil {
		t.Fatalf("open sqlite: %v", err)
	}
	if err := db.AutoMigrate(&otherperson.OtherPerson{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	return db
}

func TestOtherPersonRepositoryUpsertDedup(t *testing.T) {
	db := openOtherPersonTestDB(t)
	repo := NewOtherPersonRepository(db)
	ctx := context.Background()

	first := &otherperson.OtherPerson{Name: "  Guest One ", MobilePhone: " +43-1 ", Email: " a@x.io "}
	if err := repo.Upsert(ctx, first); err != nil {
		t.Fatalf("first upsert: %v", err)
	}
	if len(first.ID) == 0 {
		t.Fatalf("expected id set on create")
	}
	// values should be trimmed on create.
	if first.Name != "Guest One" || first.MobilePhone != "+43-1" || first.Email != "a@x.io" {
		t.Fatalf("expected trimmed values, got %q/%q/%q", first.Name, first.MobilePhone, first.Email)
	}

	// Same identity (even with different whitespace) must reuse the existing row, not create a new one.
	second := &otherperson.OtherPerson{Name: "Guest One", MobilePhone: "+43-1", Email: "a@x.io"}
	if err := repo.Upsert(ctx, second); err != nil {
		t.Fatalf("second upsert: %v", err)
	}
	if string(second.ID) != string(first.ID) {
		t.Fatalf("expected reuse of existing id")
	}

	// A different identity creates a new row.
	third := &otherperson.OtherPerson{Name: "Guest Two", MobilePhone: "+43-2", Email: "b@x.io"}
	if err := repo.Upsert(ctx, third); err != nil {
		t.Fatalf("third upsert: %v", err)
	}
	if string(third.ID) == string(first.ID) {
		t.Fatalf("expected new id for distinct identity")
	}

	_, total, err := repo.Search(ctx, "", 0, 0)
	if err != nil {
		t.Fatalf("search: %v", err)
	}
	if total != 2 {
		t.Fatalf("expected 2 master rows, got %d", total)
	}
}

func TestOtherPersonRepositorySearch(t *testing.T) {
	db := openOtherPersonTestDB(t)
	repo := NewOtherPersonRepository(db)
	ctx := context.Background()

	for _, p := range []*otherperson.OtherPerson{
		{Name: "Alice Anderson", MobilePhone: "111", Email: "alice@x.io"},
		{Name: "Bob Brown", MobilePhone: "222", Email: "bob@x.io"},
	} {
		if err := repo.Upsert(ctx, p); err != nil {
			t.Fatalf("seed: %v", err)
		}
	}

	rows, total, err := repo.Search(ctx, "alice", 0, 0)
	if err != nil {
		t.Fatalf("search by name: %v", err)
	}
	if total != 1 || len(rows) != 1 || rows[0].Name != "Alice Anderson" {
		t.Fatalf("expected only Alice, got total=%d rows=%d", total, len(rows))
	}

	// Match on email fragment too.
	rows, total, err = repo.Search(ctx, "bob@", 0, 0)
	if err != nil {
		t.Fatalf("search by email: %v", err)
	}
	if total != 1 || len(rows) != 1 || rows[0].Name != "Bob Brown" {
		t.Fatalf("expected only Bob, got total=%d rows=%d", total, len(rows))
	}

	// Empty query returns everything.
	_, total, err = repo.Search(ctx, "  ", 0, 0)
	if err != nil {
		t.Fatalf("search empty: %v", err)
	}
	if total != 2 {
		t.Fatalf("expected 2 rows for empty query, got %d", total)
	}
}

func TestOtherPersonRepositoryCreateUpdateDelete(t *testing.T) {
	db := openOtherPersonTestDB(t)
	repo := NewOtherPersonRepository(db)
	ctx := context.Background()

	row := &otherperson.OtherPerson{Name: "  Carol  ", MobilePhone: " 333 ", Email: " carol@x.io "}
	if err := repo.Create(ctx, row); err != nil {
		t.Fatalf("create: %v", err)
	}
	if len(row.ID) == 0 || row.Name != "Carol" {
		t.Fatalf("expected trimmed create with id, got %q id=%d", row.Name, len(row.ID))
	}

	// FindByIdentity trims and matches.
	found, err := repo.FindByIdentity(ctx, "Carol", "333", "carol@x.io")
	if err != nil {
		t.Fatalf("find identity: %v", err)
	}
	if found == nil || string(found.ID) != string(row.ID) {
		t.Fatalf("expected to find created row")
	}
	miss, err := repo.FindByIdentity(ctx, "Nobody", "", "")
	if err != nil {
		t.Fatalf("find miss: %v", err)
	}
	if miss != nil {
		t.Fatalf("expected nil for missing identity")
	}

	// Update replaces identity fields.
	row.Name = "Caroline"
	row.Email = "caroline@x.io"
	if err := repo.Update(ctx, row); err != nil {
		t.Fatalf("update: %v", err)
	}
	got, err := repo.GetByID(ctx, row.ID)
	if err != nil {
		t.Fatalf("get after update: %v", err)
	}
	if got == nil || got.Name != "Caroline" || got.Email != "caroline@x.io" {
		t.Fatalf("update not persisted: %+v", got)
	}

	// Delete soft-deletes: GetByID/Search no longer return it.
	if err := repo.Delete(ctx, row.ID, nil); err != nil {
		t.Fatalf("delete: %v", err)
	}
	got, err = repo.GetByID(ctx, row.ID)
	if err != nil {
		t.Fatalf("get after delete: %v", err)
	}
	if got != nil {
		t.Fatalf("expected soft-deleted row to be hidden")
	}
	_, total, err := repo.Search(ctx, "", 0, 0)
	if err != nil {
		t.Fatalf("search after delete: %v", err)
	}
	if total != 0 {
		t.Fatalf("expected 0 live rows after delete, got %d", total)
	}
}
