package utils

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"io"
	"path/filepath"
	"strings"
	"testing"
)

func createTestTarGz(t *testing.T, files map[string]string, modifyHeader func(hdr *tar.Header)) io.Reader {
	t.Helper()
	var buf bytes.Buffer
	gzw := gzip.NewWriter(&buf)
	tw := tar.NewWriter(gzw)

	for name, content := range files {
		hdr := &tar.Header{
			Name: name,
			Mode: 0644,
			Size: int64(len(content)),
		}
		if modifyHeader != nil {
			modifyHeader(hdr)
		}
		if err := tw.WriteHeader(hdr); err != nil {
			t.Fatalf("Failed to write tar header for %s: %v", name, err)
		}
		if _, err := tw.Write([]byte(content)); err != nil {
			t.Fatalf("Failed to write tar content for %s: %v", name, err)
		}
	}

	if err := tw.Close(); err != nil {
		t.Fatalf("Failed to close tar writer: %v", err)
	}
	if err := gzw.Close(); err != nil {
		t.Fatalf("Failed to close gzip writer: %v", err)
	}
	return &buf
}

func TestUntarQuadlets_Valid(t *testing.T) {
	inputFiles := map[string]string{
		"workload.kat": "kind: Workload",
		"vlb.kat":      "kind: VirtualLoadBalancer",
	}
	reader := createTestTarGz(t, inputFiles, nil)

	outputFiles, err := UntarQuadlets(reader)
	if err != nil {
		t.Fatalf("UntarQuadlets() error = %v, wantErr %v", err, false)
	}

	if len(outputFiles) != len(inputFiles) {
		t.Errorf("Expected %d files, got %d", len(inputFiles), len(outputFiles))
	}
	for name, content := range inputFiles {
		outContent, ok := outputFiles[name]
		if !ok {
			t.Errorf("Expected file %s not found in output", name)
		}
		if string(outContent) != content {
			t.Errorf("Content mismatch for %s: got '%s', want '%s'", name, string(outContent), content)
		}
	}
}

func TestUntarQuadlets_EmptyArchive(t *testing.T) {
	reader := createTestTarGz(t, map[string]string{}, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with empty archive did not return an error")
	}
	if !strings.Contains(err.Error(), "no .kat files found") {
		t.Errorf("Expected 'no .kat files found' error, got: %v", err)
	}
}

func TestUntarQuadlets_NonKatFile(t *testing.T) {
	inputFiles := map[string]string{"config.txt": "some data"}
	reader := createTestTarGz(t, inputFiles, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with non-.kat file did not return an error")
	}
	if !strings.Contains(err.Error(), "only .kat files are allowed") {
		t.Errorf("Expected 'only .kat files are allowed' error, got: %v", err)
	}
}

func TestUntarQuadlets_FileInSubdirectory(t *testing.T) {
	inputFiles := map[string]string{"subdir/workload.kat": "kind: Workload"}
	reader := createTestTarGz(t, inputFiles, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with file in subdirectory did not return an error")
	}
	if !strings.Contains(err.Error(), "subdirectories are not allowed") {
		t.Errorf("Expected 'subdirectories are not allowed' error, got: %v", err)
	}
}

func TestUntarQuadlets_PathTraversal(t *testing.T) {
	inputFiles := map[string]string{"../workload.kat": "kind: Workload"}
	reader := createTestTarGz(t, inputFiles, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with path traversal did not return an error")
	}
	if !strings.Contains(err.Error(), "contains '..'") {
		t.Errorf("Expected 'contains ..' error, got: %v", err)
	}
}

func TestUntarQuadlets_FileTooLarge(t *testing.T) {
	largeContent := strings.Repeat("a", int(maxQuadletFileSize)+1)
	inputFiles := map[string]string{"large.kat": largeContent}
	reader := createTestTarGz(t, inputFiles, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with large file did not return an error")
	}
	if !strings.Contains(err.Error(), "file large.kat in tar is too large") {
		t.Errorf("Expected 'file ... too large' error, got: %v", err)
	}
}

func TestUntarQuadlets_TotalSizeTooLarge(t *testing.T) {
	numFiles := (maxTotalQuadletSize / maxQuadletFileSize) * 4
	fileSize := maxQuadletFileSize / 2

	inputFiles := make(map[string]string)
	content := strings.Repeat("a", int(fileSize))
	for i := 0; i < int(numFiles); i++ {
		inputFiles[filepath.Join(".", "file"+string(rune(i+'0'))+".kat")] = content
	}

	reader := createTestTarGz(t, inputFiles, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with total large size did not return an error")
	}
	if !strings.Contains(err.Error(), "total size of files in tar is too large") {
		t.Errorf("Expected 'total size ... too large' error, got: %v", err)
	}
}

func TestUntarQuadlets_TooManyFiles(t *testing.T) {
	inputFiles := make(map[string]string)
	for i := 0; i <= maxQuadletFiles; i++ {
		inputFiles[filepath.Join(".", "file"+string(rune(i+'a'))+".kat")] = "content"
	}
	reader := createTestTarGz(t, inputFiles, nil)
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with too many files did not return an error")
	}
	if !strings.Contains(err.Error(), "too many files in quadlet bundle") {
		t.Errorf("Expected 'too many files' error, got: %v", err)
	}
}

func TestUntarQuadlets_UnsupportedFileType(t *testing.T) {
	reader := createTestTarGz(t, map[string]string{"link.kat": ""}, func(hdr *tar.Header) {
		hdr.Typeflag = tar.TypeSymlink
		hdr.Linkname = "target.kat"
		hdr.Size = 0
	})
	_, err := UntarQuadlets(reader)
	if err == nil {
		t.Fatal("UntarQuadlets() with symlink did not return an error")
	}
	if !strings.Contains(err.Error(), "unsupported file type") {
		t.Errorf("Expected 'unsupported file type' error, got: %v", err)
	}
}

func TestUntarQuadlets_CorruptedGzip(t *testing.T) {
	corruptedInput := bytes.NewBufferString("this is not a valid gzip stream")
	_, err := UntarQuadlets(corruptedInput)
	if err == nil {
		t.Fatal("UntarQuadlets() with corrupted gzip did not return an error")
	}
	if !strings.Contains(err.Error(), "failed to create gzip reader") && !strings.Contains(err.Error(), "gzip: invalid header") {
		t.Errorf("Expected 'gzip format' or 'invalid header' error, got: %v", err)
	}
}

func TestUntarQuadlets_CorruptedTar(t *testing.T) {
	var buf bytes.Buffer
	gzw := gzip.NewWriter(&buf)
	_, _ = gzw.Write([]byte("this is not a valid tar stream but inside gzip"))
	_ = gzw.Close()

	_, err := UntarQuadlets(&buf)
	if err == nil {
		t.Fatal("UntarQuadlets() with corrupted tar did not return an error")
	}
	if !strings.Contains(err.Error(), "tar") {
		t.Errorf("Expected error related to 'tar' format, got: %v", err)
	}
}