package filecheck

import (
	"crypto/md5"
	"encoding/hex"
	"os"
	"path/filepath"
	"testing"
)

func makeTestFile(t *testing.T, content string) string {
	t.Helper()
	filePath := filepath.Join(t.TempDir(), "testfile.txt")
	if err := os.WriteFile(filePath, []byte(content), 0o644); err != nil {
		t.Fatalf("failed to write test file: %v", err)
	}
	return filePath
}

func TestHashMatches(t *testing.T) {
	t.Run("matching hash", func(t *testing.T) {
		const content = "Hello, world!\n"
		filePath := makeTestFile(t, content)

		sum := md5.Sum([]byte(content))
		checksum := hex.EncodeToString(sum[:])

		match, err := HashMatches(filePath, checksum, md5.New())
		if err != nil {
			t.Fatalf("HashMatches() returned an error: %v", err)
		}
		if !match {
			t.Error("HashMatches() returned false for a matching checksum")
		}
	})

	t.Run("non-matching hash", func(t *testing.T) {
		filePath := makeTestFile(t, "FooBar\n")

		sum := md5.Sum([]byte("Some other content"))
		checksum := hex.EncodeToString(sum[:])

		match, err := HashMatches(filePath, checksum, md5.New())
		if err != nil {
			t.Fatalf("HashMatches() returned an error: %v", err)
		}
		if match {
			t.Error("HashMatches() returned true for a non-matching checksum")
		}
	})

	t.Run("missing file", func(t *testing.T) {
		match, err := HashMatches(filepath.Join(t.TempDir(), "missing"), "dummy", md5.New())
		if err == nil {
			t.Fatal("HashMatches() returned nil error for a missing file")
		}
		if match {
			t.Error("HashMatches() returned true for a missing file")
		}
	})

	t.Run("read error", func(t *testing.T) {
		match, err := HashMatches(t.TempDir(), "dummy", md5.New())
		if err == nil {
			t.Fatal("HashMatches() returned nil error for a directory")
		}
		if match {
			t.Error("HashMatches() returned true for an unreadable path")
		}
	})
}

func TestEqualContent(t *testing.T) {
	t.Run("matching content", func(t *testing.T) {
		a := makeTestFile(t, "same")
		b := makeTestFile(t, "same")

		match, err := EqualContent(a, b)
		if err != nil {
			t.Fatalf("EqualContent() returned an error: %v", err)
		}
		if !match {
			t.Error("EqualContent() returned false for matching files")
		}
	})

	t.Run("different content", func(t *testing.T) {
		a := makeTestFile(t, "same")
		b := makeTestFile(t, "different")

		match, err := EqualContent(a, b)
		if err != nil {
			t.Fatalf("EqualContent() returned an error: %v", err)
		}
		if match {
			t.Error("EqualContent() returned true for different files")
		}
	})

	t.Run("missing file", func(t *testing.T) {
		a := makeTestFile(t, "same")
		b := filepath.Join(t.TempDir(), "missing")

		match, err := EqualContent(a, b)
		if err == nil {
			t.Fatal("EqualContent() returned nil error for a missing file")
		}
		if match {
			t.Error("EqualContent() returned true for a missing file")
		}
	})

	t.Run("first file missing", func(t *testing.T) {
		a := filepath.Join(t.TempDir(), "missing")
		b := makeTestFile(t, "same")

		match, err := EqualContent(a, b)
		if err == nil {
			t.Fatal("EqualContent() returned nil error for a missing first file")
		}
		if match {
			t.Error("EqualContent() returned true for a missing first file")
		}
	})

	t.Run("first file read error", func(t *testing.T) {
		a := t.TempDir()
		b := makeTestFile(t, "")

		match, err := EqualContent(a, b)
		if err == nil {
			t.Fatal("EqualContent() returned nil error for an unreadable first file")
		}
		if match {
			t.Error("EqualContent() returned true for an unreadable first file")
		}
	})

	t.Run("second file read error", func(t *testing.T) {
		a := makeTestFile(t, "")
		b := t.TempDir()

		match, err := EqualContent(a, b)
		if err == nil {
			t.Fatal("EqualContent() returned nil error for an unreadable second file")
		}
		if match {
			t.Error("EqualContent() returned true for an unreadable second file")
		}
	})
}

func TestExists(t *testing.T) {
	filePath := makeTestFile(t, "content")
	if !Exists(filePath) {
		t.Error("Exists() returned false for an existing file")
	}
	if Exists(filepath.Join(t.TempDir(), "missing")) {
		t.Error("Exists() returned true for a missing file")
	}
}

func TestIsExecutable(t *testing.T) {
	t.Run("regular executable file", func(t *testing.T) {
		filePath := makeTestFile(t, "script")
		if err := os.Chmod(filePath, 0o755); err != nil {
			t.Fatalf("Chmod() failed: %v", err)
		}

		ok, err := IsExecutable(filePath)
		if err != nil {
			t.Fatalf("IsExecutable() returned an error: %v", err)
		}
		if !ok {
			t.Error("IsExecutable() returned false for an executable file")
		}
	})

	t.Run("regular non-executable file", func(t *testing.T) {
		filePath := makeTestFile(t, "plain")

		ok, err := IsExecutable(filePath)
		if err != nil {
			t.Fatalf("IsExecutable() returned an error: %v", err)
		}
		if ok {
			t.Error("IsExecutable() returned true for a non-executable file")
		}
	})

	t.Run("directory with execute bit", func(t *testing.T) {
		ok, err := IsExecutable(t.TempDir())
		if err != nil {
			t.Fatalf("IsExecutable() returned an error: %v", err)
		}
		if ok {
			t.Error("IsExecutable() returned true for a directory")
		}
	})

	t.Run("missing file", func(t *testing.T) {
		ok, err := IsExecutable(filepath.Join(t.TempDir(), "missing"))
		if err == nil {
			t.Fatal("IsExecutable() returned nil error for a missing file")
		}
		if ok {
			t.Error("IsExecutable() returned true for a missing file")
		}
	})
}
