package mysql

import (
	"context"
	"testing"

	"wucher/internal/domain/auth"
	"wucher/internal/domain/user"
	"wucher/internal/shared/pkg/appctx"
	"wucher/internal/shared/pkg/uuidv7"
)

func setupUserRepoFixture(t *testing.T) (*UserRepository, context.Context, []byte) {
	t.Helper()
	db := openDBTestSQLite(t)
	if err := AutoMigrate(db); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	repo := NewUserRepository(db)
	roleID := uuidv7.MustBytes()
	if err := db.Create(&auth.Role{
		ID:          roleID,
		Code:        "admin",
		Name:        "admin",
		Description: "admin role",
	}).Error; err != nil {
		t.Fatalf("seed role: %v", err)
	}
	return repo, context.Background(), roleID
}

func TestUserRepository_CRUDAndLookupBranches(t *testing.T) {
	repo, baseCtx, roleID := setupUserRepoFixture(t)
	actorID := uuidv7.MustBytes()
	ctx := appctx.WithUserID(baseCtx, actorID)

	u := &user.User{
		Email:     "john@example.com",
		Username:  "john",
		FirstName: "John",
		LastName:  "Doe",
		Timezone:  "UTC",
		RoleID:    roleID,
	}
	if err := repo.Create(ctx, u); err != nil {
		t.Fatalf("create: %v", err)
	}
	if len(u.CreatedBy) != 16 || len(u.UpdatedBy) != 16 {
		t.Fatalf("expected created_by and updated_by set by actor")
	}

	byEmail, err := repo.GetByEmail(ctx, u.Email)
	if err != nil || byEmail == nil {
		t.Fatalf("get by email failed: user=%v err=%v", byEmail, err)
	}
	byUsername, err := repo.GetByUsername(ctx, u.Username)
	if err != nil || byUsername == nil {
		t.Fatalf("get by username failed: user=%v err=%v", byUsername, err)
	}
	lastName, err := repo.GetLastNameByID(ctx, u.ID)
	if err != nil || lastName != "Doe" {
		t.Fatalf("get last name failed: lastName=%q err=%v", lastName, err)
	}

	// Update branch: RoleIDs nil should skip role-replacement path.
	byEmail.FirstName = "Johnny"
	byEmail.RoleIDs = nil
	if err := repo.Update(ctx, byEmail); err != nil {
		t.Fatalf("update (roleIDs nil): %v", err)
	}

	// Update branch: RoleIDs provided should execute role-replacement path.
	byEmail.LastName = "Doe-Updated"
	byEmail.RoleIDs = [][]byte{roleID}
	if err := repo.Update(ctx, byEmail); err != nil {
		t.Fatalf("update (roleIDs set): %v", err)
	}

	// List branches: with/without filter, sort, and limit.
	list, total, err := repo.List(ctx, "john", "users.email ASC", 1, 0)
	if err != nil {
		t.Fatalf("list with filter: %v", err)
	}
	if total < 1 || len(list) != 1 {
		t.Fatalf("expected one filtered result, total=%d len=%d", total, len(list))
	}
	list, total, err = repo.List(ctx, "", "", 0, 0)
	if err != nil {
		t.Fatalf("list without limit: %v", err)
	}
	if total < 1 || len(list) < 1 {
		t.Fatalf("expected at least one user, total=%d len=%d", total, len(list))
	}

	// Not found branches.
	if got, err := repo.GetByID(ctx, uuidv7.MustBytes()); err != nil || got != nil {
		t.Fatalf("expected not found by id => nil,nil; got=%v err=%v", got, err)
	}
	if got, err := repo.GetByEmail(ctx, "missing@example.com"); err != nil || got != nil {
		t.Fatalf("expected not found by email => nil,nil; got=%v err=%v", got, err)
	}
	if got, err := repo.GetByUsername(ctx, "missing-user"); err != nil || got != nil {
		t.Fatalf("expected not found by username => nil,nil; got=%v err=%v", got, err)
	}
	if got, err := repo.GetLastNameByID(ctx, uuidv7.MustBytes()); err != nil || got != "" {
		t.Fatalf("expected not found last_name => empty,nil; got=%q err=%v", got, err)
	}

	if err := repo.Delete(ctx, u.ID); err != nil {
		t.Fatalf("delete: %v", err)
	}
	// Delete branch with invalid uuid bytes (skip attachment cleanup query branch).
	if err := repo.Delete(ctx, []byte("short")); err != nil {
		t.Fatalf("delete short id should be noop-success, err=%v", err)
	}
}

func TestUserRepository_ErrorBranchesWhenDatabaseClosed(t *testing.T) {
	db := openDBTestSQLite(t)
	if err := AutoMigrate(db); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	repo := NewUserRepository(db)
	sqlDB, err := db.DB()
	if err != nil {
		t.Fatalf("db.DB: %v", err)
	}
	if err := sqlDB.Close(); err != nil {
		t.Fatalf("close db: %v", err)
	}

	ctx := context.Background()
	roleID := uuidv7.MustBytes()
	userID := uuidv7.MustBytes()
	u := &user.User{
		ID:        userID,
		Email:     "closed@example.com",
		Username:  "closed",
		FirstName: "Closed",
		LastName:  "DB",
		Timezone:  "UTC",
		RoleID:    roleID,
	}

	if err := repo.Create(ctx, u); err == nil {
		t.Fatalf("expected create error on closed db")
	}
	if err := repo.Update(ctx, u); err == nil {
		t.Fatalf("expected update error on closed db")
	}
	if err := repo.Delete(ctx, userID); err == nil {
		t.Fatalf("expected delete error on closed db")
	}
	if _, err := repo.GetByID(ctx, userID); err == nil {
		t.Fatalf("expected get by id error on closed db")
	}
	if _, err := repo.GetByEmail(ctx, u.Email); err == nil {
		t.Fatalf("expected get by email error on closed db")
	}
	if _, err := repo.GetByUsername(ctx, u.Username); err == nil {
		t.Fatalf("expected get by username error on closed db")
	}
	if _, err := repo.GetLastNameByID(ctx, userID); err == nil {
		t.Fatalf("expected get last name error on closed db")
	}
	if _, _, err := repo.List(ctx, "", "", 10, 0); err == nil {
		t.Fatalf("expected list error on closed db")
	}
}

func TestUserRepository_CreateDuplicateAndDeleteAttachmentCleanupError(t *testing.T) {
	repo, ctx, roleID := setupUserRepoFixture(t)

	u := &user.User{
		Email:     "dup@example.com",
		Username:  "dup-user",
		FirstName: "Dup",
		LastName:  "User",
		Timezone:  "UTC",
		RoleID:    roleID,
	}
	if err := repo.Create(ctx, u); err != nil {
		t.Fatalf("create first user: %v", err)
	}

	dup := &user.User{
		Email:     "dup@example.com",
		Username:  "dup-user-2",
		FirstName: "Dup2",
		LastName:  "User2",
		Timezone:  "UTC",
		RoleID:    roleID,
	}
	if err := repo.Create(ctx, dup); err == nil {
		t.Fatalf("expected duplicate-email create error")
	}

	// Force attachment cleanup statement to fail so delete returns error branch.
	if err := repo.db.Exec("DROP TABLE attachments").Error; err != nil {
		t.Fatalf("drop attachments table: %v", err)
	}
	if err := repo.Delete(ctx, u.ID); err == nil {
		t.Fatalf("expected delete error when attachments table missing")
	}
}
