package boxstore

import (
	"archive/zip"
	"bytes"
	"os"
	"path/filepath"
	"testing"
	"time"

	"warpbox/lib/models"
)

func TestStartRetentionWaitsForEveryFileToFinish(t *testing.T) {
	manifest := models.BoxManifest{
		RetentionSecs: 10,
		Files: []models.BoxFile{
			{ID: "one", Status: models.FileStatusReady},
			{ID: "two", Status: models.FileStatusWork},
		},
	}

	startRetentionIfTerminalUnlocked(&manifest)

	if !manifest.ExpiresAt.IsZero() {
		t.Fatalf("expected retention to stay unset while a file is still uploading, got %s", manifest.ExpiresAt)
	}
}

func TestStartRetentionBeginsWhenEveryFileIsTerminal(t *testing.T) {
	manifest := models.BoxManifest{
		RetentionSecs: 10,
		Files: []models.BoxFile{
			{ID: "one", Status: models.FileStatusReady},
			{ID: "two", Status: models.FileStatusFailed},
		},
	}
	before := time.Now().UTC()

	startRetentionIfTerminalUnlocked(&manifest)

	if manifest.ExpiresAt.IsZero() {
		t.Fatal("expected retention to start once every file is complete or failed")
	}
	if manifest.ExpiresAt.Before(before.Add(9 * time.Second)) {
		t.Fatalf("expected retention to start from completion time, got %s", manifest.ExpiresAt)
	}
}

func TestStartRetentionSkipsOneTimeDownload(t *testing.T) {
	manifest := models.BoxManifest{
		RetentionSecs:   10,
		OneTimeDownload: true,
		Files: []models.BoxFile{
			{ID: "one", Status: models.FileStatusReady},
			{ID: "two", Status: models.FileStatusReady},
		},
	}

	startRetentionIfTerminalUnlocked(&manifest)

	if !manifest.ExpiresAt.IsZero() {
		t.Fatalf("expected one-time download box to avoid retention expiry, got %s", manifest.ExpiresAt)
	}
}

func TestSafeBoxFilePathRejectsTraversal(t *testing.T) {
	restoreUploadRoot := UploadRoot()
	defer SetUploadRoot(restoreUploadRoot)
	SetUploadRoot(t.TempDir())

	boxID := "0123456789abcdef0123456789abcdef"
	if _, ok := SafeBoxFilePath(boxID, "../outside.txt"); ok {
		t.Fatal("expected traversal to be rejected")
	}
	if _, ok := SafeBoxFilePath("../bad", "file.txt"); ok {
		t.Fatal("expected invalid box id to be rejected")
	}
}

func TestAddFileToZipRejectsUnsafeManifestName(t *testing.T) {
	restoreUploadRoot := UploadRoot()
	defer SetUploadRoot(restoreUploadRoot)
	SetUploadRoot(t.TempDir())

	var buffer bytes.Buffer
	zipWriter := zip.NewWriter(&buffer)
	if err := AddFileToZip(zipWriter, "0123456789abcdef0123456789abcdef", "../outside.txt"); err == nil {
		t.Fatal("expected unsafe zip filename to be rejected")
	}
}

func TestListFilesSkipsSymlinks(t *testing.T) {
	restoreUploadRoot := UploadRoot()
	defer SetUploadRoot(restoreUploadRoot)
	SetUploadRoot(t.TempDir())

	boxID := "0123456789abcdef0123456789abcdef"
	if err := os.MkdirAll(BoxPath(boxID), 0755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	if err := os.WriteFile(filepath.Join(BoxPath(boxID), "safe.txt"), []byte("safe"), 0644); err != nil {
		t.Fatalf("WriteFile returned error: %v", err)
	}
	if err := os.Symlink(filepath.Join(BoxPath(boxID), "safe.txt"), filepath.Join(BoxPath(boxID), "link.txt")); err != nil {
		t.Skipf("symlink unavailable: %v", err)
	}

	files, err := ListFiles(boxID)
	if err != nil {
		t.Fatalf("ListFiles returned error: %v", err)
	}
	if len(files) != 1 || files[0].Name != "safe.txt" {
		t.Fatalf("expected only regular file, got %#v", files)
	}
}

func TestBoxPasswordUsesBcryptAndVerifiesLegacy(t *testing.T) {
	restoreUploadRoot := UploadRoot()
	defer SetUploadRoot(restoreUploadRoot)
	SetUploadRoot(t.TempDir())

	boxID := "0123456789abcdef0123456789abcdef"
	if err := os.MkdirAll(BoxPath(boxID), 0755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	if _, err := CreateManifest(boxID, models.CreateBoxRequest{Password: "secret"}); err != nil {
		t.Fatalf("CreateManifest returned error: %v", err)
	}
	manifest, err := ReadManifest(boxID)
	if err != nil {
		t.Fatalf("ReadManifest returned error: %v", err)
	}
	if manifest.PasswordHashAlg != "bcrypt" {
		t.Fatalf("expected bcrypt password hash, got %q", manifest.PasswordHashAlg)
	}
	if !VerifyPassword(manifest, "secret") {
		t.Fatal("expected bcrypt password to verify")
	}

	legacy := models.BoxManifest{
		PasswordSalt: "salt",
		PasswordHash: legacyPasswordHash("salt", "secret"),
		AuthToken:    "token",
	}
	if !VerifyPassword(legacy, "secret") {
		t.Fatal("expected legacy password hash to verify")
	}
}
