package mysql

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

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

	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/shared/pkg/uuidv7"
)

func openFileManagerFolderRepoTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:file_manager_folder_repo_%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(&filemanager.Folder{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	return db
}

func TestNewFileManagerFolderRepository(t *testing.T) {
	db := openFileManagerFolderRepoTestDB(t)
	repo := NewFileManagerFolderRepository(db)
	if repo == nil || repo.db == nil {
		t.Fatalf("expected repository initialized")
	}
}

func TestFileManagerFolderRepositoryCreateAndGetByID(t *testing.T) {
	db := openFileManagerFolderRepoTestDB(t)
	repo := NewFileManagerFolderRepository(db)

	row := &filemanager.Folder{
		Name:           "Operations",
		NameNormalized: "operations",
		NameSlot:       "live",
		Depth:          0,
	}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("create folder: %v", err)
	}
	if len(row.ID) != 16 {
		t.Fatalf("expected generated id")
	}

	got, err := repo.GetByID(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("get by id: %v", err)
	}
	if got == nil || got.Name != "Operations" {
		t.Fatalf("expected created folder")
	}

	missing, err := repo.GetByID(context.Background(), uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("get missing by id: %v", err)
	}
	if missing != nil {
		t.Fatalf("expected nil for missing folder")
	}
}

func TestFileManagerFolderRepositoryUpdate(t *testing.T) {
	db := openFileManagerFolderRepoTestDB(t)
	repo := NewFileManagerFolderRepository(db)

	row := &filemanager.Folder{
		Name:           "Old Name",
		NameNormalized: "old name",
		NameSlot:       "live",
	}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("seed create: %v", err)
	}

	row.Name = "New Name"
	row.NameNormalized = "new name"
	if err := repo.Update(context.Background(), row); err != nil {
		t.Fatalf("update folder: %v", err)
	}

	got, err := repo.GetByID(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("get after update: %v", err)
	}
	if got == nil || got.Name != "New Name" || got.NameNormalized != "new name" {
		t.Fatalf("unexpected updated row: %+v", got)
	}
}

func TestFileManagerFolderRepositoryDelete(t *testing.T) {
	db := openFileManagerFolderRepoTestDB(t)
	repo := NewFileManagerFolderRepository(db)

	row := &filemanager.Folder{
		Name:           "Delete Me",
		NameNormalized: "delete me",
		NameSlot:       "live",
	}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("seed create: %v", err)
	}

	deletedBy := uuidv7.MustBytes()
	purgeAt := time.Now().UTC().Add(24 * time.Hour).Truncate(time.Second)
	if err := repo.Delete(context.Background(), row.ID, deletedBy, &purgeAt); err != nil {
		t.Fatalf("delete folder: %v", err)
	}

	active, err := repo.GetByID(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("get after delete: %v", err)
	}
	if active != nil {
		t.Fatalf("expected deleted row hidden from GetByID")
	}

	var stored filemanager.Folder
	if err := db.WithContext(context.Background()).Unscoped().Where("id = ?", row.ID).First(&stored).Error; err != nil {
		t.Fatalf("unscoped find: %v", err)
	}
	if stored.DeletedAt == nil {
		t.Fatalf("expected deleted_at populated")
	}
	if stored.PurgeAt == nil || !stored.PurgeAt.Equal(purgeAt) {
		t.Fatalf("expected purge_at set")
	}
	if !bytes.Equal(stored.DeletedBy, deletedBy) {
		t.Fatalf("expected deleted_by set")
	}
	if stored.NameSlot == "live" {
		t.Fatalf("expected name_slot switched from live on delete")
	}

	if err := repo.Delete(context.Background(), row.ID, deletedBy, nil); err == nil {
		t.Fatalf("expected record not found on second delete")
	}
}

func TestFileManagerFolderRepositoryRestorePurgeAndTrashQueries(t *testing.T) {
	db := openFileManagerFolderRepoTestDB(t)
	repo := NewFileManagerFolderRepository(db)

	row := &filemanager.Folder{
		Name:           "Archive",
		NameNormalized: "archive",
		NameSlot:       "live",
	}
	if err := repo.Create(context.Background(), row); err != nil {
		t.Fatalf("create folder: %v", err)
	}
	deletedBy := uuidv7.MustBytes()
	if err := repo.Delete(context.Background(), row.ID, deletedBy, nil); err != nil {
		t.Fatalf("delete folder: %v", err)
	}

	anyRow, err := repo.GetByIDAny(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("get by id any: %v", err)
	}
	if anyRow == nil || anyRow.DeletedAt == nil {
		t.Fatalf("expected deleted row from GetByIDAny")
	}

	trashRows, trashTotal, err := repo.ListTrashByParent(context.Background(), nil, "", 10, 0)
	if err != nil {
		t.Fatalf("list trash by parent: %v", err)
	}
	if trashTotal != 1 || len(trashRows) != 1 {
		t.Fatalf("expected one trashed folder")
	}

	restoreBy := uuidv7.MustBytes()
	if err := repo.Restore(context.Background(), row.ID, restoreBy); err != nil {
		t.Fatalf("restore folder: %v", err)
	}
	restored, err := repo.GetByID(context.Background(), row.ID)
	if err != nil {
		t.Fatalf("get restored folder: %v", err)
	}
	if restored == nil || restored.DeletedAt != nil || restored.NameSlot != "live" {
		t.Fatalf("expected restored live folder")
	}

	if err := repo.Delete(context.Background(), row.ID, deletedBy, nil); err != nil {
		t.Fatalf("delete folder 2: %v", err)
	}
	if err := repo.Purge(context.Background(), row.ID); err != nil {
		t.Fatalf("purge folder: %v", err)
	}
	if got, err := repo.GetByIDAny(context.Background(), row.ID); err != nil || got != nil {
		t.Fatalf("expected purged folder gone, got=%v err=%v", got, err)
	}
	if err := repo.Purge(context.Background(), row.ID); err == nil {
		t.Fatalf("expected purge not found on second call")
	}
}

func TestFileManagerFolderRepositoryGetByParentAndName(t *testing.T) {
	db := openFileManagerFolderRepoTestDB(t)
	repo := NewFileManagerFolderRepository(db)

	root := &filemanager.Folder{Name: "Root", NameNormalized: "root", NameSlot: "live"}
	if err := repo.Create(context.Background(), root); err != nil {
		t.Fatalf("create root: %v", err)
	}

	child := &filemanager.Folder{
		ParentID:       root.ID,
		Name:           "Child",
		NameNormalized: "child",
		NameSlot:       "live",
		Depth:          1,
	}
	if err := repo.Create(context.Background(), child); err != nil {
		t.Fatalf("create child: %v", err)
	}

	gotRoot, err := repo.GetByParentAndName(context.Background(), nil, "root")
	if err != nil {
		t.Fatalf("get root by parent/name: %v", err)
	}
	if gotRoot == nil || !bytes.Equal(gotRoot.ID, root.ID) {
		t.Fatalf("expected root by nil parent")
	}

	gotChild, err := repo.GetByParentAndName(context.Background(), root.ID, "child")
	if err != nil {
		t.Fatalf("get child by parent/name: %v", err)
	}
	if gotChild == nil || !bytes.Equal(gotChild.ID, child.ID) {
		t.Fatalf("expected child by parent/name")
	}

	notFound, err := repo.GetByParentAndName(context.Background(), root.ID, "missing")
	if err != nil {
		t.Fatalf("get missing by parent/name: %v", err)
	}
	if notFound != nil {
		t.Fatalf("expected nil on missing folder")
	}
}

func TestFileManagerFolderRepositoryListByParent(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		db := openFileManagerFolderRepoTestDB(t)
		repo := NewFileManagerFolderRepository(db)

		rootA := &filemanager.Folder{Name: "B", NameNormalized: "b", NameSlot: "live"}
		rootB := &filemanager.Folder{Name: "A", NameNormalized: "a", NameSlot: "live"}
		_ = repo.Create(context.Background(), rootA)
		_ = repo.Create(context.Background(), rootB)

		rows, total, err := repo.ListByParent(context.Background(), nil, "name ASC", 1, 0)
		if err != nil {
			t.Fatalf("list root folders: %v", err)
		}
		if total != 2 {
			t.Fatalf("expected total 2, got %d", total)
		}
		if len(rows) != 1 || rows[0].Name != "A" {
			t.Fatalf("expected sorted+limited list")
		}
	})

	t.Run("count error", func(t *testing.T) {
		db := openFileManagerFolderRepoTestDB(t)
		repo := NewFileManagerFolderRepository(db)
		if err := db.Migrator().DropTable(&filemanager.Folder{}); err != nil {
			t.Fatalf("drop table: %v", err)
		}
		if _, _, err := repo.ListByParent(context.Background(), nil, "", 10, 0); err == nil {
			t.Fatalf("expected count error")
		}
	})

	t.Run("find error", func(t *testing.T) {
		db := openFileManagerFolderRepoTestDB(t)
		repo := NewFileManagerFolderRepository(db)
		_ = repo.Create(context.Background(), &filemanager.Folder{Name: "X", NameNormalized: "x", NameSlot: "live"})
		if _, _, err := repo.ListByParent(context.Background(), nil, "name ASC, )", 10, 0); err == nil {
			t.Fatalf("expected find error")
		}
	})
}
