package main

import (
	"io/fs"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
)

func TestCopyFile_Normal(t *testing.T) {
	src := filepath.Join(t.TempDir(), "src.txt")
	dst := filepath.Join(t.TempDir(), "dst.txt")
	os.WriteFile(src, []byte("hello"), 0644)

	if err := copyFile(src, dst, 0644); err != nil {
		t.Fatal(err)
	}

	data, _ := os.ReadFile(dst)
	if string(data) != "hello" {
		t.Errorf("content = %q, want %q", string(data), "hello")
	}
}

func TestCopyFile_SrcNotExist(t *testing.T) {
	dst := filepath.Join(t.TempDir(), "dst.txt")
	err := copyFile("/nonexistent/file", dst, 0644)
	if err == nil {
		t.Fatal("expected error for nonexistent source")
	}
	if !os.IsNotExist(err) {
		t.Errorf("expected ErrNotExist, got: %v", err)
	}
}

func TestCopyFile_DstPermDenied(t *testing.T) {
	if os.Getuid() == 0 {
		t.Skip("running as root, permission test not applicable")
	}

	src := filepath.Join(t.TempDir(), "src.txt")
	os.WriteFile(src, []byte("hello"), 0644)

	dst := "/proc/nonexistent/file"
	err := copyFile(src, dst, 0644)
	if err == nil {
		t.Fatal("expected error for invalid dst")
	}
}

func TestRetry_TransientError(t *testing.T) {
	calls := 0
	err := retry("test", func() error {
		calls++
		if calls < 3 {
			return &fs.PathError{Op: "open", Path: "/tmp/test", Err: fs.ErrNotExist}
		}
		return nil
	})
	if err != nil {
		t.Errorf("expected success after retries, got: %v", err)
	}
	if calls != 3 {
		t.Errorf("calls = %d, want 3", calls)
	}
}

func TestRetry_PermanentError(t *testing.T) {
	calls := 0
	err := retry("test", func() error {
		calls++
		return &fs.PathError{Op: "write", Path: "/tmp/test", Err: syscall.ENOSPC}
	})
	if err == nil {
		t.Fatal("expected error")
	}
	if calls != 1 {
		t.Errorf("calls = %d, want 1 (no retries for ENOSPC)", calls)
	}
}

func TestIsRetryable(t *testing.T) {
	tests := []struct {
		err      error
		expected bool
	}{
		{fs.ErrNotExist, true},
		{&fs.PathError{Op: "open", Path: "/tmp", Err: fs.ErrNotExist}, true},
		{&fs.PathError{Op: "write", Path: "/tmp", Err: syscall.ENOSPC}, false},
		{syscall.ENOSPC, false},
		{syscall.EACCES, false},
	}

	for _, tt := range tests {
		if got := isRetryable(tt.err); got != tt.expected {
			t.Errorf("isRetryable(%v) = %v, want %v", tt.err, got, tt.expected)
		}
	}
}

func TestLogError_DiskFull(t *testing.T) {
	logSyncError("copy", "test.txt", syscall.ENOSPC)
}

func TestLogError_PermissionDenied(t *testing.T) {
	logSyncError("write", "test.txt", syscall.EACCES)
}

func TestLogError_NotExist(t *testing.T) {
	logSyncError("stat", "test.txt", fs.ErrNotExist)
}

func TestLogError_Nil(t *testing.T) {
	logSyncError("test", "file.txt", nil)
}

func TestSyncError(t *testing.T) {
	err := &SyncError{
		Path: "test.txt",
		Op:   "copy",
		Err:  syscall.ENOSPC,
	}

	if !strings.Contains(err.Error(), "copy") {
		t.Errorf("error should contain op, got: %s", err.Error())
	}
	if !strings.Contains(err.Error(), "test.txt") {
		t.Errorf("error should contain path, got: %s", err.Error())
	}
	if err.Unwrap() != syscall.ENOSPC {
		t.Errorf("unwrap should return underlying error")
	}
}

func TestCopyFileWithRetry_Success(t *testing.T) {
	src := filepath.Join(t.TempDir(), "src.txt")
	dst := filepath.Join(t.TempDir(), "dst.txt")
	os.WriteFile(src, []byte("content"), 0644)

	if err := copyFileWithRetry(src, dst, 0644); err != nil {
		t.Fatal(err)
	}

	data, _ := os.ReadFile(dst)
	if string(data) != "content" {
		t.Errorf("content = %q, want %q", string(data), "content")
	}
}
