package utils

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

func TestBanner(t *testing.T) {
	got := Banner("Test", "This is a test message")
	want := `
------------------------------------- Test -------------------------------------
This is a test message
--------------------------------------------------------------------------------`
	if got != want {
		t.Errorf("Banner() = %q, want %q", got, want)
	} else {
		t.Log("Banner() test passed with output:", got)
	}
}

func TestGetKernelVersion(t *testing.T) {
	VersionPattern := regexp.MustCompile(`\d+\.\d+\.\d+`)
	// Test the GetKernelVersion function
	if GetKernelVersion() == "" {
		t.Error("GetKernelVersion() returned an empty string")
	}
	if !VersionPattern.MatchString(GetKernelVersion()) {
		t.Error("GetKernelVersion() returned an invalid version")
	}
	t.Log("GetKernelVersion() test passed with version:", GetKernelVersion())
}

func TestStripCommentsAndTokenize(t *testing.T) {
	input := "foo bar # baz\nqux"
	tokens := StripCommentsAndTokenize(strings.NewReader(input))
	if len(tokens) != 3 {
		t.Error("StripCommentsAndTokenize() returned an incorrect number of tokens")
	}
	if tokens[0] != "foo" || tokens[1] != "bar" || tokens[2] != "qux" {
		t.Error("StripCommentsAndTokenize() returned incorrect tokens")
	}
	t.Log("StripCommentsAndTokenize() test passed with tokens:", tokens)
}

// Create a temporary file with known content
func makeTestFile(content string, t *testing.T) string {
	const filename = "testfile.txt"
	tempDir := t.TempDir()
	filePath := filepath.Join(tempDir, filename)
	if err := os.WriteFile(filePath, []byte(content), 0o644); err != nil {
		t.Fatalf("failed to write testfile.txt: %v", err)
	}
	return filePath
}

func TestIsMD5SumMatch(t *testing.T) {
	t.Run("Matching MD5", func(t *testing.T) {
		const content = "Hello, world!\n"
		filePath := makeTestFile(content, t)

		// Compute MD5 of that content ourselves
		sum := md5.Sum([]byte(content))
		md5Str := hex.EncodeToString(sum[:])

		// Now call IsMD5SumMatch with the correct MD5
		match := IsMD5SumMatch(md5Str, filePath)
		if !match {
			t.Errorf("expected true for correct MD5, got false")
		}
	})

	t.Run("Non-Matching MD5", func(t *testing.T) {
		filePath := makeTestFile("FooBar\n", t)

		// Compute MD5 of something else
		dummySum := md5.Sum([]byte("Some other content"))
		md5Str := hex.EncodeToString(dummySum[:])

		match := IsMD5SumMatch(md5Str, filePath)
		if match {
			t.Errorf("expected false for incorrect MD5, got true")
		}
	})

	t.Run("File Does Not Exist", func(t *testing.T) {
		defer func() {
			if r := recover(); r == nil {
				t.Error("expected a panic when file does not exist, got no panic")
			}
		}()

		fakePath := filepath.Join(t.TempDir(), "missing.txt")
		_ = IsMD5SumMatch("dummyMD5", fakePath)
		t.Error("should have panicked before this line (file not found)")
	})
}

func TestPromptUser(t *testing.T) {
	in := strings.NewReader("foo\n")
	var out bytes.Buffer
	buf := bufio.NewWriter(&out)
	prompt := "bar"
	res := PromptUser(buf, in, prompt)
	buf.Flush()
	if out.String() != prompt+" [y/N]: " {
		t.Error("PromptUser() did not write the correct prompt")
	}
	if res {
		t.Error("PromptUser() returned true for 'foo'")
	}
	if !PromptUser(buf, strings.NewReader("y\n"), prompt) {
		t.Error("PromptUser() returned false for 'y'")
	}
	if !PromptUser(buf, strings.NewReader("Y\n"), prompt) {
		t.Error("PromptUser() returned false for 'Y'")
	}
	r := strings.NewReader("N\nT")
	PromptUser(buf, r, prompt)
	if ReadChar(r) != 'T' {
		t.Error("Newline not correctly consumed")
	}
}

func TestReadWordsFromFile(t *testing.T) {
	// For more tests see TestStripCommentsAndTokenize
	filePath := makeTestFile("foo bar # baz\nqux", t)
	tokens := ReadWordsFromFile(filePath)
	switch {
	case len(tokens) != 3:
		t.Error("ReadWordsFromFile() returned an incorrect number of tokens")
	case tokens[0] != "foo" || tokens[1] != "bar" || tokens[2] != "qux":
		t.Error("ReadWordsFromFile() returned incorrect tokens")
	default:
		t.Log("ReadWordsFromFile() test passed with tokens:", tokens)
	}
}

func TestCount(t *testing.T) {
	got, want := Count(true, false, true), 2
	if got != want {
		t.Error("Count() returned an incorrect count")
	}
	t.Log("Count() test passed with count: ", got)
}

func TestJoin(t *testing.T) {
	got, want := Join([]string{"foo", "bar"}), "foo bar"
	if got != want {
		t.Error("Join() returned an incorrect path")
	}
	t.Log("Join() test passed with path: ", got)
}
